1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik\Plugins\Referrers;
10
11use Exception;
12use Piwik\API\Request;
13use Piwik\API\ResponseBuilder;
14use Piwik\Archive;
15use Piwik\Common;
16use Piwik\DataTable;
17use Piwik\DataTable\Filter\ColumnCallbackAddColumnPercentage;
18use Piwik\Date;
19use Piwik\Metrics;
20use Piwik\Piwik;
21use Piwik\Plugins\Referrers\DataTable\Filter\GroupDifferentSocialWritings;
22use Piwik\Site;
23
24/**
25 * The Referrers API lets you access reports about Websites, Search engines, Keywords, Campaigns used to access your website.
26 *
27 * For example, "getKeywords" returns all search engine keywords (with <a href='http://matomo.org/docs/analytics-api/reference/#toc-metric-definitions' rel='noreferrer' target='_blank'>general analytics metrics</a> for each keyword), "getWebsites" returns referrer websites (along with the full Referrer URL if the parameter &expanded=1 is set).
28 * "getReferrerType" returns the Referrer overview report. "getCampaigns" returns the list of all campaigns (and all campaign keywords if the parameter &expanded=1 is set).
29 *
30 * @method static \Piwik\Plugins\Referrers\API getInstance()
31 */
32class API extends \Piwik\Plugin\API
33{
34    public function get($idSite, $period, $date, $segment = false, $columns = false)
35    {
36        Piwik::checkUserHasViewAccess($idSite);
37
38        $dataTableReferrersType = $this->getReferrerType($idSite, $period, $date, $segment);
39        $dataTable = $this->createReferrerTypeTable($dataTableReferrersType);
40
41        $archive = Archive::build($idSite, $period, $date, $segment);
42
43        $numericArchives = $archive->getDataTableFromNumeric([
44            Archiver::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME,
45            Archiver::METRIC_DISTINCT_SOCIAL_NETWORK_RECORD_NAME,
46            Archiver::METRIC_DISTINCT_KEYWORD_RECORD_NAME,
47            Archiver::METRIC_DISTINCT_WEBSITE_RECORD_NAME,
48            Archiver::METRIC_DISTINCT_URLS_RECORD_NAME,
49            Archiver::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME,
50        ]);
51        $this->mergeNumericArchives($dataTable, $numericArchives);
52
53        $totalVisits = array_sum($dataTableReferrersType->getColumn(Metrics::INDEX_NB_VISITS));
54
55        $percentColumns = [
56            'Referrers_visitorsFromDirectEntry',
57            'Referrers_visitorsFromSearchEngines',
58            'Referrers_visitorsFromCampaigns',
59            'Referrers_visitorsFromSocialNetworks',
60            'Referrers_visitorsFromWebsites',
61        ];
62        foreach ($percentColumns as $column) {
63            $dataTable->filter(ColumnCallbackAddColumnPercentage::class, [
64                $column . '_percent',
65                $column,
66                $totalVisits,
67            ]);
68        }
69
70        if (!empty($requestedColumns)) {
71            $requestedColumns = Piwik::getArrayFromApiParameter($columns);
72            $dataTable->filter(DataTable\Filter\ColumnDelete::class, [[], $requestedColumns]);
73        }
74
75        return $dataTable;
76    }
77
78    /**
79     * @param string $name
80     * @param int $idSite
81     * @param string $period
82     * @param string|Date $date
83     * @param string $segment
84     * @param bool $expanded
85     * @param int|null $idSubtable
86     * @return DataTable
87     */
88    protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null)
89    {
90        $dataTable = Archive::createDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $flat=false, $idSubtable);
91        return $dataTable;
92    }
93
94    /**
95     * Returns a report describing visit information for each possible referrer type. The
96     * result is a datatable whose subtables are the reports for each parent row's referrer type.
97     *
98     * The subtable reports are: 'getKeywords' (for search engine referrer type), 'getWebsites',
99     * and 'getCampaigns'.
100     *
101     * @param string $idSite The site ID.
102     * @param string $period The period to get data for, either 'day', 'week', 'month', 'year',
103     *                       or 'range'.
104     * @param string $date The date of the period.
105     * @param bool|string $segment The segment to use.
106     * @param bool|int $typeReferrer (deprecated) If you want to get data only for a specific referrer
107     *                         type, supply a type for this parameter.
108     * @param bool|int $idSubtable For this report this value is a referrer type ID and not an actual
109     *                        subtable ID. The result when using this parameter will be the
110     *                        specific report for the given referrer type.
111     * @param bool $expanded Whether to get report w/ subtables loaded or not.
112     * @return DataTable
113     */
114    public function getReferrerType($idSite, $period, $date, $segment = false, $typeReferrer = false,
115                                    $idSubtable = false, $expanded = false, $_setReferrerTypeLabel = true)
116    {
117        Piwik::checkUserHasViewAccess($idSite);
118
119        $this->checkSingleSite($idSite, 'getReferrerType');
120
121        // if idSubtable is supplied, interpret idSubtable as referrer type and return correct report
122        if ($idSubtable !== false) {
123            $result = false;
124            switch ($idSubtable) {
125                case Common::REFERRER_TYPE_SEARCH_ENGINE:
126                    $result = $this->getKeywords($idSite, $period, $date, $segment);
127                    break;
128                case Common::REFERRER_TYPE_SOCIAL_NETWORK:
129                    $result = $this->getSocials($idSite, $period, $date, $segment);
130                    break;
131                case Common::REFERRER_TYPE_WEBSITE:
132                    $result = $this->getWebsites($idSite, $period, $date, $segment);
133                    break;
134                case Common::REFERRER_TYPE_CAMPAIGN:
135                    $result = $this->getCampaigns($idSite, $period, $date, $segment);
136                    break;
137                default: // invalid idSubtable, return whole report
138                    break;
139            }
140
141            if ($result) {
142                $result->filter('ColumnCallbackDeleteMetadata', array('segment'));
143                $result->filter('ColumnCallbackDeleteMetadata', array('segmentValue'));
144
145                return $this->removeSubtableIds($result); // this report won't return subtables of individual reports
146            }
147        }
148
149        // get visits by referrer type
150        $dataTable = $this->getDataTable(Archiver::REFERRER_TYPE_RECORD_NAME, $idSite, $period, $date, $segment);
151
152        // checks for  && $typeReferrer !== 'false' && $typeReferrer !== '0' added to cover intention when
153        // it is passed as a string in a GET or POST parameter
154        if ($typeReferrer !== false && $typeReferrer !== 'false' && $typeReferrer !== '0') // filter for a specific referrer type
155        {
156            $dataTable->filter('Pattern', array('label', $typeReferrer));
157        }
158
159        // set subtable IDs for each row to the label (which holds the int referrer type)
160        $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\SetGetReferrerTypeSubtables', array($idSite, $period, $date, $segment, $expanded));
161
162        $dataTable->filter('AddSegmentByLabelMapping', array(
163            'referrerType',
164            array(
165                Common::REFERRER_TYPE_DIRECT_ENTRY   => 'direct',
166                Common::REFERRER_TYPE_CAMPAIGN       => 'campaign',
167                Common::REFERRER_TYPE_SEARCH_ENGINE  => 'search',
168                Common::REFERRER_TYPE_SOCIAL_NETWORK => 'social',
169                Common::REFERRER_TYPE_WEBSITE        => 'website',
170            )
171        ));
172
173        // set referrer type column to readable value
174        if ($_setReferrerTypeLabel == 1) {
175            $dataTable->filter(DataTable\Filter\ColumnCallbackAddMetadata::class, ['label', 'referrer_type']);
176            $dataTable->filter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\getReferrerTypeLabel'));
177        }
178
179        return $dataTable;
180    }
181
182    private function checkSingleSite($idSite, $method)
183    {
184        $idSites = Site::getIdSitesFromIdSitesString($idSite);
185
186        if (count($idSites) > 1) {
187            throw new Exception("Referrers.$method with multiple sites is not supported (yet).");
188        }
189    }
190
191    /**
192     * Returns a report that shows
193     */
194    public function getAll($idSite, $period, $date, $segment = false)
195    {
196        Piwik::checkUserHasViewAccess($idSite);
197
198        $this->checkSingleSite($idSite, 'getAll');
199        $dataTable = Request::processRequest('Referrers.getReferrerType', [
200            'idSite' => $idSite,
201            'period' => $period,
202            'date' => $date,
203            'segment' => $segment,
204            'expanded' => true,
205            'disable_generic_filters' => true,
206            'disable_queued_filters' => true,
207            '_setReferrerTypeLabel' => 0,
208        ], []);
209
210        if ($dataTable instanceof DataTable\Map) {
211            throw new Exception("Referrers.getAll with multiple sites or dates is not supported (yet).");
212        }
213
214        $dataTable = $dataTable->mergeSubtables($labelColumn = 'referer_type', $useMetadataColumn = true);
215        $dataTable->queueFilter('ReplaceColumnNames');
216        $dataTable->queueFilter('ReplaceSummaryRowLabel');
217
218        return $dataTable;
219    }
220
221    public function getKeywords($idSite, $period, $date, $segment = false, $expanded = false, $flat = false)
222    {
223        Piwik::checkUserHasViewAccess($idSite);
224
225        $dataTable = Archive::createDataTableFromArchive(Archiver::KEYWORDS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat);
226
227        if ($flat) {
228            $dataTable->filterSubtables('Piwik\Plugins\Referrers\DataTable\Filter\SearchEnginesFromKeywordId', array($dataTable));
229        } else {
230            $dataTable->filter('AddSegmentValue');
231            $dataTable->queueFilter('PrependSegment', array('referrerType==search;'));
232        }
233
234        $dataTable->queueFilter('Piwik\Plugins\Referrers\DataTable\Filter\KeywordNotDefined');
235
236        return $dataTable;
237    }
238
239    const LABEL_KEYWORD_NOT_DEFINED = "";
240
241    /**
242     * @ignore
243     */
244    public static function getKeywordNotDefinedString()
245    {
246        return Piwik::translate('General_NotDefined', Piwik::translate('General_ColumnKeyword'));
247    }
248
249    /**
250     * @ignore
251     */
252    public static function getCleanKeyword($label)
253    {
254        return $label == self::LABEL_KEYWORD_NOT_DEFINED
255            ? self::getKeywordNotDefinedString()
256            : $label;
257    }
258
259    /**
260     * @param DataTable $table
261     */
262    private function filterOutKeywordNotDefined($table)
263    {
264        if ($table instanceof DataTable) {
265            $row = $table->getRowIdFromLabel('');
266            if ($row) {
267                $table->deleteRow($row);
268            }
269        }
270    }
271
272    protected function getLabelsFromTable($table)
273    {
274        $request = $_GET;
275        $request['serialize'] = 0;
276
277        // Apply generic filters
278        $response = new ResponseBuilder($format = 'original', $request);
279        $table = $response->getResponse($table);
280
281        // If period=lastX we only keep the first resultset as we want to return a plain list
282        if ($table instanceof DataTable\Map) {
283            $tables = $table->getDataTables();
284            $table = current($tables);
285        }
286        // Keep the response simple, only include keywords
287        $keywords = $table->getColumn('label');
288        return $keywords;
289    }
290
291    public function getSearchEnginesFromKeywordId($idSite, $period, $date, $idSubtable, $segment = false)
292    {
293        Piwik::checkUserHasViewAccess($idSite);
294        $dataTable = $this->getDataTable(Archiver::KEYWORDS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable);
295        $keywords  = $this->getKeywords($idSite, $period, $date, $segment);
296        $keyword   = $keywords->getRowFromIdSubDataTable($idSubtable)->getColumn('label');
297
298        $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\SearchEnginesFromKeywordId', array($keywords, $idSubtable));
299        $dataTable->filter('AddSegmentByLabel', array('referrerName'));
300        $dataTable->queueFilter('PrependSegment', array('referrerKeyword=='.$keyword.';referrerType==search;'));
301
302        return $dataTable;
303    }
304
305    public function getSearchEngines($idSite, $period, $date, $segment = false, $expanded = false, $flat = false)
306    {
307        Piwik::checkUserHasViewAccess($idSite);
308        $dataTable = Archive::createDataTableFromArchive(Archiver::SEARCH_ENGINES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat);
309
310        if ($flat) {
311            $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url', function ($url) { return SearchEngine::getInstance()->getUrlFromName($url); }));
312            $dataTable->filter('MetadataCallbackAddMetadata', array('url', 'logo', function ($url) { return SearchEngine::getInstance()->getLogoFromUrl($url); }));
313            $dataTable->filterSubtables('Piwik\Plugins\Referrers\DataTable\Filter\KeywordsFromSearchEngineId', array($dataTable));
314        } else {
315            $dataTable->filter('AddSegmentByLabel', array('referrerName'));
316            $dataTable->queueFilter('PrependSegment', array('referrerType==search;'));
317            $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', function ($url) { return SearchEngine::getInstance()->getUrlFromName($url); }));
318            $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', function ($url) { return SearchEngine::getInstance()->getLogoFromUrl($url); }));
319        }
320
321        return $dataTable;
322    }
323
324    public function getKeywordsFromSearchEngineId($idSite, $period, $date, $idSubtable, $segment = false)
325    {
326        Piwik::checkUserHasViewAccess($idSite);
327        $dataTable = $this->getDataTable(Archiver::SEARCH_ENGINES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable);
328
329        // get the search engine and create the URL to the search result page
330        $searchEngines = $this->getSearchEngines($idSite, $period, $date, $segment);
331        $searchEngines->applyQueuedFilters();
332        $searchEngine  = $searchEngines->getRowFromIdSubDataTable($idSubtable)->getColumn('label');
333
334        $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\KeywordsFromSearchEngineId', array($searchEngines, $idSubtable));
335        $dataTable->filter('AddSegmentByLabel', array('referrerKeyword'));
336        $dataTable->queueFilter('PrependSegment', array('referrerName=='.$searchEngine.';referrerType==search;'));
337
338        return $dataTable;
339    }
340
341    public function getCampaigns($idSite, $period, $date, $segment = false, $expanded = false)
342    {
343        Piwik::checkUserHasViewAccess($idSite);
344        $dataTable = $this->getDataTable(Archiver::CAMPAIGNS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded);
345
346        $dataTable->filter('AddSegmentByLabel', array('referrerName'));
347        $dataTable->queueFilter('PrependSegment', array('referrerType==campaign;'));
348
349        return $dataTable;
350    }
351
352    public function getKeywordsFromCampaignId($idSite, $period, $date, $idSubtable, $segment = false)
353    {
354        Piwik::checkUserHasViewAccess($idSite);
355        $campaigns = $this->getCampaigns($idSite, $period, $date, $segment);
356        $campaigns->applyQueuedFilters();
357        $row = $campaigns->getRowFromIdSubDataTable($idSubtable);
358        $campaign = $row ? $row->getColumn('label') : '';
359
360        $dataTable = $this->getDataTable(Archiver::CAMPAIGNS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable);
361        $dataTable->filter('AddSegmentByLabel', array('referrerKeyword'));
362        $dataTable->queueFilter('PrependSegment', array('referrerName=='.$campaign.';referrerType==campaign;'));
363        return $dataTable;
364    }
365
366    public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false, $flat = false)
367    {
368        Piwik::checkUserHasViewAccess($idSite);
369        $dataTable = Archive::createDataTableFromArchive(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable = null);
370
371        if ($flat) {
372            $dataTable->filterSubtables('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId');
373        } else {
374            $dataTable->filter('AddSegmentByLabel', array('referrerName'));
375        }
376
377        return $dataTable;
378    }
379
380    public function getUrlsFromWebsiteId($idSite, $period, $date, $idSubtable, $segment = false)
381    {
382        Piwik::checkUserHasViewAccess($idSite);
383        $dataTable = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable);
384        $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId');
385        $dataTable->filter('MetadataCallbackAddMetadata', array('url', 'segment', function($url) {
386            return 'referrerUrl==' . urlencode($url);
387        }));
388
389        return $dataTable;
390    }
391
392    /**
393     * Returns report comparing the number of visits (and other info) for social network referrers.
394     * This is a view of the getWebsites report.
395     *
396     * @param string $idSite
397     * @param string $period
398     * @param string $date
399     * @param string|bool $segment
400     * @param bool $expanded
401     * @param bool $flat
402     * @return DataTable
403     */
404    public function getSocials($idSite, $period, $date, $segment = false, $expanded = false, $flat = false)
405    {
406        Piwik::checkUserHasViewAccess($idSite);
407
408        $dataTable = Archive::createDataTableFromArchive(Archiver::SOCIAL_NETWORKS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat);
409
410        $dataTable->filter(GroupDifferentSocialWritings::class);
411
412        $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url', function ($name) {
413            return Social::getInstance()->getMainUrlFromName($name);
414        }));
415
416        $dataTable = $this->completeSocialTablesWithOldReports($dataTable, $idSite, $period, $date, $segment, $expanded, $flat);
417
418        $dataTable->filter('MetadataCallbackAddMetadata', array('url', 'logo', function ($url) { return Social::getInstance()->getLogoFromUrl($url); }));
419
420        return $dataTable;
421    }
422
423    private function completeSocialTablesWithOldReports($dataTable, $idSite, $period, $date, $segment, $expanded, $flat)
424    {
425        return $this->combineDataTables($dataTable, function() use ($idSite, $period, $date, $segment, $expanded, $flat) {
426            $dataTableFiltered = Archive::createDataTableFromArchive(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, false);
427
428            $this->filterWebsitesForSocials($dataTableFiltered, $idSite, $period, $date, $segment, $expanded, $flat);
429
430            return $dataTableFiltered;
431        });
432    }
433
434    protected function combineDataTables($dataTable, $callbackForAdditionalData)
435    {
436        $isMap = false;
437        $hasEmptyTable = false;
438        if ($dataTable instanceof DataTable\Map) {
439            $isMap = true;
440            $dataTables = $dataTable->getDataTables();
441        } else {
442            $dataTables = [$dataTable];
443        }
444
445        foreach ($dataTables as $table) {
446            if ($table instanceof DataTable && !$table->getRowsCountWithoutSummaryRow()) {
447                $hasEmptyTable = true;
448                break;
449            }
450        }
451
452        if ($hasEmptyTable) {
453
454            $dataTablesForCompletion = $callbackForAdditionalData();
455
456            if (!$isMap) {
457                $dataTable = $dataTablesForCompletion;
458            } else {
459                $filteredTables = $dataTablesForCompletion->getDataTables();
460                foreach ($dataTable as $label => $table) {
461                    if ($table instanceof DataTable && !$table->getRowsCountWithoutSummaryRow() && !empty($filteredTables[$label])) {
462                        $dataTable->addTable($filteredTables[$label], $label);
463                    }
464                }
465            }
466        }
467
468        return $dataTable;
469    }
470
471    /**
472     * @param DataTable $dataTable
473     */
474    protected function filterWebsitesForSocials($dataTable, $idSite, $period, $date, $segment, $expanded, $flat)
475    {
476        $dataTable->filter('ColumnCallbackDeleteRow', array('label', function ($url) {
477            return !Social::getInstance()->isSocialUrl($url);
478        }));
479        $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url', function ($url) {
480            return Social::getInstance()->getMainUrl($url);
481        }));
482        $dataTable->filter('GroupBy', array('label', function ($url) {
483            return Social::getInstance()->getSocialNetworkFromDomain($url);
484        }));
485
486        $this->setSocialIdSubtables($dataTable);
487        $this->removeSubtableMetadata($dataTable);
488
489        if ($flat) {
490            $this->buildExpandedTableForFlattenGetSocials($idSite, $period, $date, $segment, $expanded, $dataTable);
491        }
492    }
493
494    /**
495     * Returns report containing individual referrer URLs for a specific social networking
496     * site.
497     *
498     * @param string $idSite
499     * @param string $period
500     * @param string $date
501     * @param bool|string $segment
502     * @param bool|int $idSubtable This ID does not reference a real DataTable record. Instead, it
503     *                              is the array index of an item in the Socials list file.
504     *                              The urls are filtered by the social network at this index.
505     *                              If false, no filtering is done and every social URL is returned.
506     * @return DataTable
507     */
508    public function getUrlsForSocial($idSite, $period, $date, $segment = false, $idSubtable = false)
509    {
510        Piwik::checkUserHasViewAccess($idSite);
511
512        $dataTable = $this->getDataTable(Archiver::SOCIAL_NETWORKS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = true, $idSubtable);
513
514        if (!$idSubtable) {
515            $dataTable = $dataTable->mergeSubtables();
516        }
517
518        $dataTable = $this->combineDataTables($dataTable, function() use ($idSite, $period, $date, $segment, $idSubtable) {
519            $dataTableFiltered = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = true);
520
521            $socialNetworks = array_values(Social::getInstance()->getDefinitions());
522            $social = isset($socialNetworks[$idSubtable - 1]) ? $socialNetworks[$idSubtable - 1] : false;
523
524            // filter out everything but social network indicated by $idSubtable
525            $dataTableFiltered->filter(
526                'ColumnCallbackDeleteRow',
527                array('label',
528                    function ($url) use ($social) {
529                        return !Social::getInstance()->isSocialUrl($url, $social);
530                    }
531                )
532            );
533
534            return $dataTableFiltered->mergeSubtables();
535        });
536
537        $dataTable->filter('AddSegmentByLabel', array('referrerUrl'));
538        $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\UrlsForSocial', array(true));
539        $dataTable->queueFilter('ReplaceColumnNames');
540        return $dataTable;
541    }
542
543    public function getNumberOfDistinctSearchEngines($idSite, $period, $date, $segment = false)
544    {
545        return $this->getNumeric(Archiver::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME, $idSite, $period, $date, $segment);
546    }
547
548    public function getNumberOfDistinctSocialNetworks($idSite, $period, $date, $segment = false)
549    {
550        return $this->getNumeric(Archiver::METRIC_DISTINCT_SOCIAL_NETWORK_RECORD_NAME, $idSite, $period, $date, $segment);
551    }
552
553    public function getNumberOfDistinctKeywords($idSite, $period, $date, $segment = false)
554    {
555        return $this->getNumeric(Archiver::METRIC_DISTINCT_KEYWORD_RECORD_NAME, $idSite, $period, $date, $segment);
556    }
557
558    public function getNumberOfDistinctCampaigns($idSite, $period, $date, $segment = false)
559    {
560        return $this->getNumeric(Archiver::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME, $idSite, $period, $date, $segment);
561    }
562
563    public function getNumberOfDistinctWebsites($idSite, $period, $date, $segment = false)
564    {
565        return $this->getNumeric(Archiver::METRIC_DISTINCT_WEBSITE_RECORD_NAME, $idSite, $period, $date, $segment);
566    }
567
568    public function getNumberOfDistinctWebsitesUrls($idSite, $period, $date, $segment = false)
569    {
570        return $this->getNumeric(Archiver::METRIC_DISTINCT_URLS_RECORD_NAME, $idSite, $period, $date, $segment);
571    }
572
573    private function getNumeric($name, $idSite, $period, $date, $segment)
574    {
575        Piwik::checkUserHasViewAccess($idSite);
576        $archive = Archive::build($idSite, $period, $date, $segment);
577        return $archive->getDataTableFromNumeric($name);
578    }
579
580    /**
581     * Removes idsubdatatable_in_db metadata from a DataTable. Used by Social tables since
582     * they use fake subtable IDs.
583     *
584     * @param DataTable $dataTable
585     */
586    private function removeSubtableMetadata($dataTable)
587    {
588        if ($dataTable instanceof DataTable\Map) {
589            foreach ($dataTable->getDataTables() as $childTable) {
590                $this->removeSubtableMetadata($childTable);
591            }
592        } else {
593            foreach ($dataTable->getRows() as $row) {
594                $row->deleteMetadata('idsubdatatable_in_db');
595            }
596        }
597    }
598
599    /**
600     * Sets the subtable IDs for the DataTable returned by getSocial.
601     *
602     * The IDs are int indexes into the array in of defined socials.
603     *
604     * @param DataTable $dataTable
605     */
606    private function setSocialIdSubtables($dataTable)
607    {
608        if ($dataTable instanceof DataTable\Map) {
609            foreach ($dataTable->getDataTables() as $childTable) {
610                $this->setSocialIdSubtables($childTable);
611            }
612        } else {
613            foreach ($dataTable->getRows() as $row) {
614                $socialName = $row->getColumn('label');
615
616                $i = 1; // start at one because idSubtable=0 is equivalent to idSubtable=false
617                foreach (Social::getInstance()->getDefinitions() as $name) {
618                    if ($name == $socialName) {
619                        $row->setNonLoadedSubtableId($i);
620                        break;
621                    }
622
623                    ++$i;
624                }
625            }
626        }
627    }
628
629    /**
630     * Utility function that removes the subtable IDs for the subtables of the
631     * getReferrerType report. This avoids infinite recursion in said report (ie,
632     * the grandchildren of the report will be the original report, and it will
633     * recurse when trying to get a flat report).
634     *
635     * @param DataTable $table
636     * @return DataTable Returns $table for convenience.
637     */
638    private function removeSubtableIds($table)
639    {
640        if ($table instanceof DataTable\Map) {
641            foreach ($table->getDataTables() as $childTable) {
642                $this->removeSubtableIds($childTable);
643            }
644        } else {
645            foreach ($table->getRows() as $row) {
646                $row->removeSubtable();
647            }
648        }
649
650        return $table;
651    }
652
653    /**
654     * @param int $idSite
655     * @param string $period
656     * @param string $date
657     * @param string|false $segment
658     * @param bool $expanded
659     * @param DataTable $dataTable
660     */
661    private function buildExpandedTableForFlattenGetSocials($idSite, $period, $date, $segment, $expanded, $dataTable)
662    {
663        $urlsTable = Archive::createDataTableFromArchive(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat = true);
664        $urlsTable->filter('ColumnCallbackDeleteRow', array('label', function ($url) {
665            return !Social::getInstance()->isSocialUrl($url);
666        }));
667        $urlsTable = $urlsTable->mergeSubtables();
668
669        if ($dataTable instanceof DataTable\Map) {
670            $dataTables = $dataTable->getDataTables();
671            $urlsTables = $urlsTable->getDataTables();
672        } else {
673            $dataTables = [$dataTable];
674            $urlsTables = [$urlsTable];
675        }
676
677        foreach ($dataTables as $label => $dataTable) {
678            foreach ($dataTable->getRows() as $row) {
679                $row->removeSubtable();
680
681                $social = $row->getColumn('label');
682                $newTable = $urlsTables[$label]->getEmptyClone();
683
684                $rows = $urlsTables[$label]->getRows();
685                foreach ($rows as $id => $urlsTableRow) {
686                    $url = $urlsTableRow->getColumn('label');
687                    if (Social::getInstance()->isSocialUrl($url, $social)) {
688                        $newTable->addRow($urlsTableRow);
689                        $urlsTables[$label]->deleteRow($id);
690                    }
691                }
692
693                if ($newTable->getRowsCount()) {
694                    $newTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\UrlsForSocial', array($expanded));
695                    $row->setSubtable($newTable);
696                }
697            }
698        }
699
700        Common::destroy($urlsTable);
701        $urlsTable = null;
702    }
703
704    private function createReferrerTypeTable(DataTable\DataTableInterface $table)
705    {
706        if ($table instanceof DataTable) {
707            $nameToColumnId = array(
708                Common::REFERRER_TYPE_SEARCH_ENGINE => 'Referrers_visitorsFromSearchEngines',
709                Common::REFERRER_TYPE_SOCIAL_NETWORK => 'Referrers_visitorsFromSocialNetworks',
710                Common::REFERRER_TYPE_DIRECT_ENTRY => 'Referrers_visitorsFromDirectEntry',
711                Common::REFERRER_TYPE_WEBSITE => 'Referrers_visitorsFromWebsites',
712                Common::REFERRER_TYPE_CAMPAIGN => 'Referrers_visitorsFromCampaigns',
713            );
714
715            $newRow = array_fill_keys(array_values($nameToColumnId), 0);
716            foreach ($table->getRows() as $row) {
717                $referrerType = $row->getMetadata('referrer_type');
718                if (empty($nameToColumnId[$referrerType])) {
719                    continue;
720                }
721
722                $nameVar = $nameToColumnId[$referrerType];
723                $value = $row->getColumn(Metrics::INDEX_NB_VISITS);
724                $newRow[$nameVar] = $value;
725            }
726
727            $result = new DataTable\Simple();
728            $result->addRowFromSimpleArray($newRow);
729            return $result;
730        } else if ($table instanceof DataTable\Map) {
731            $result = new DataTable\Map();
732            $result->setKeyName($table->getKeyName());
733            foreach ($table->getDataTables() as $label => $childTable) {
734                if ($childTable->getRowsCount() > 0) {
735                    $referrerTypeTable = $this->createReferrerTypeTable($childTable);
736                    $result->addTable($referrerTypeTable, $label);
737                } else {
738                    $result->addTable(new DataTable(), $label);
739                }
740            }
741        } else {
742            throw new \Exception("Unexpected DataTable type: " . get_class($table)); // sanity check
743        }
744        return $result;
745    }
746
747    private function mergeNumericArchives(DataTable\DataTableInterface $table, DataTable\DataTableInterface $numericArchives = null)
748    {
749        if ($table instanceof DataTable) {
750            /** @var DataTable $numericArchives */
751            if (empty($numericArchives)) {
752                return;
753            }
754
755            $table->setAllTableMetadata($numericArchives->getAllTableMetadata());
756
757            if ($numericArchives->getRowsCount() == 0) {
758                return;
759            }
760
761            if ($table->getRowsCountWithoutSummaryRow() == 0) {
762                $table->addRow(new DataTable\Row());
763            }
764
765            $row = $table->getFirstRow();
766            foreach ($numericArchives->getFirstRow() as $name => $value) {
767                $row->setColumn($name, $value);
768            }
769        } else if ($table instanceof DataTable\Map) {
770            foreach ($table->getDataTables() as $label => $childTable) {
771                $numericArchiveChildTable = $numericArchives->getTable($label);
772                $this->mergeNumericArchives($childTable, $numericArchiveChildTable);
773            }
774        } else {
775            throw new \Exception("Unexpected DataTable type: " . get_class($table)); // sanity check
776        }
777    }
778}
779