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