diff options
author | Matthieu Aubry <matt@piwik.org> | 2015-12-04 08:36:20 +0300 |
---|---|---|
committer | Matthieu Aubry <matt@piwik.org> | 2015-12-04 08:36:20 +0300 |
commit | 05852332f2a693b0cc313152fcb742fc063aa995 (patch) | |
tree | 13cc3bf7a46ba342d1171221a9dfc76cce0dd049 /plugins/SitesManager | |
parent | 24839681ebba4d8cd021009b7261f459b91cca87 (diff) | |
parent | ea0ee79d05e7d27df4c360e5a28bcfa2834ab183 (diff) |
Merge pull request #9320 from piwik/website_path
Added support to specify a path in a website URL and Piwik will consider the path during tracking
Diffstat (limited to 'plugins/SitesManager')
-rw-r--r-- | plugins/SitesManager/API.php | 1 | ||||
-rw-r--r-- | plugins/SitesManager/Model.php | 16 | ||||
-rw-r--r-- | plugins/SitesManager/SiteUrls.php | 148 | ||||
-rw-r--r-- | plugins/SitesManager/tests/Integration/ModelTest.php | 52 | ||||
-rw-r--r-- | plugins/SitesManager/tests/Integration/SiteUrlsTest.php | 124 |
5 files changed, 331 insertions, 10 deletions
diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php index 9a6bf63469..739af7d5e9 100644 --- a/plugins/SitesManager/API.php +++ b/plugins/SitesManager/API.php @@ -645,6 +645,7 @@ class API extends \Piwik\Plugin\API { Site::clearCache(); Cache::regenerateCacheWebsiteAttributes($idSite); + Cache::clearCacheGeneral(); SiteUrls::clearSitesCache(); } diff --git a/plugins/SitesManager/Model.php b/plugins/SitesManager/Model.php index 93ddb01d8f..96062b608a 100644 --- a/plugins/SitesManager/Model.php +++ b/plugins/SitesManager/Model.php @@ -286,6 +286,22 @@ class Model return $urls; } + /** + * Returns the list of alias URLs registered for the given idSite. + * The website ID must be valid when calling this method! + * + * @param int $idSite + * @return array list of alias URLs + */ + public function getAllKnownUrlsForAllSites() + { + $db = $this->getDb(); + $mainUrls = $db->fetchAll("SELECT idsite, main_url as url FROM " . Common::prefixTable("site")); + $aliasUrls = $db->fetchAll("SELECT idsite, url FROM " . Common::prefixTable("site_url")); + + return array_merge($mainUrls, $aliasUrls); + } + public function updateSite($site, $idSite) { $idSite = (int) $idSite; diff --git a/plugins/SitesManager/SiteUrls.php b/plugins/SitesManager/SiteUrls.php index a1fac67a89..059521a4a9 100644 --- a/plugins/SitesManager/SiteUrls.php +++ b/plugins/SitesManager/SiteUrls.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\SitesManager; use Piwik\Cache; +use Piwik\Common; class SiteUrls { @@ -19,6 +20,96 @@ class SiteUrls self::getCache()->delete(self::$cacheId); } + /** + * Groups all URLs by host, path and idsite. + * + * @param array $urls An array containing URLs by idsite, + * eg array(array($idSite = 1 => array('apache.piwik', 'apache2.piwik'), 2 => array(), ...)) + * as returned by {@link getAllCachedSiteUrls()} and {@link getAllSiteUrls} + * @return array All urls grouped by host => path => idSites. Path having the most '/' will be listed first + * array( + 'apache.piwik' => array( + '/test/two' => $idsite = array(3), + '/test' => $idsite = array(1), + '/' => $idsite = array(2), + ), + 'test.apache.piwik' => array( + '/test/two' => $idsite = array(3), + '/test' => $idsite = array(1), + '/' => $idsite = array(2, 3), + ), + ); + */ + public function groupUrlsByHost($siteUrls) + { + if (empty($siteUrls)) { + return array(); + } + + $allUrls = array(); + + foreach ($siteUrls as $idSite => $urls) { + $idSite = (int) $idSite; + foreach ($urls as $url) { + $urlParsed = @parse_url($url); + + if ($urlParsed === false || !isset($urlParsed['host'])) { + continue; + } + + $host = $this->toCanonicalHost($urlParsed['host']); + $path = $this->getCanonicalPathFromParsedUrl($urlParsed); + + if (!isset($allUrls[$host])) { + $allUrls[$host] = array(); + } + + if (!isset($allUrls[$host][$path])) { + $allUrls[$host][$path] = array(); + } + + if (!in_array($idSite, $allUrls[$host][$path])) { + $allUrls[$host][$path][] = $idSite; + } + } + } + + foreach ($allUrls as $host => $paths) { + uksort($paths, array($this, 'sortByPathDepth')); + $allUrls[$host] = $paths; + } + + return $allUrls; + } + + public function getIdSitesMatchingUrl($parsedUrl, $urlsGroupedByHost) + { + if (empty($parsedUrl['host'])) { + return null; + } + + $urlHost = $this->toCanonicalHost($parsedUrl['host']); + $urlPath = $this->getCanonicalPathFromParsedUrl($parsedUrl); + + $matchingSites = null; + if (isset($urlsGroupedByHost[$urlHost])) { + $paths = $urlsGroupedByHost[$urlHost]; + + foreach ($paths as $path => $idSites) { + if (0 === strpos($urlPath, $path)) { + $matchingSites = $idSites; + break; + } + } + + if (!isset($matchingSites) && isset($paths['/'])) { + $matchingSites = $paths['/']; + } + } + + return $matchingSites; + } + public function getAllCachedSiteUrls() { $cache = $this->getCache(); @@ -35,23 +126,66 @@ class SiteUrls public function getAllSiteUrls() { $model = new Model(); - $siteIds = $model->getSitesId(); - $siteUrls = array(); + $siteUrls = $model->getAllKnownUrlsForAllSites(); - if (empty($siteIds)) { + if (empty($siteUrls)) { return array(); } - foreach ($siteIds as $siteId) { - $siteId = (int) $siteId; - $siteUrls[$siteId] = $model->getSiteUrlsFromId($siteId); + $urls = array(); + foreach ($siteUrls as $siteUrl) { + $siteId = (int) $siteUrl['idsite']; + + if (!isset($urls[$siteId])) { + $urls[$siteId] = array(); + } + + $urls[$siteId][] = $siteUrl['url']; } - return $siteUrls; + return $urls; } private static function getCache() { return Cache::getLazyCache(); } + + private function sortByPathDepth($pathA, $pathB) + { + // list first the paths with most '/' , and list path = '/' last + $numSlashA = substr_count($pathA, '/'); + $numSlashB = substr_count($pathB, '/'); + + if ($numSlashA === $numSlashB) { + return -1 * strcmp($pathA, $pathB); + } + + return $numSlashA > $numSlashB ? -1 : 1; + } + + private function toCanonicalHost($host) + { + $host = Common::mb_strtolower($host); + if (strpos($host, 'www.') === 0) { + $host = substr($host, 4); + } + + return $host; + } + + private function getCanonicalPathFromParsedUrl($urlParsed) + { + $path = '/'; + + if (isset($urlParsed['path'])) { + $path = Common::mb_strtolower($urlParsed['path']); + if (!Common::stringEndsWith($path, '/')) { + $path .= '/'; + } + } + + return $path; + } + } diff --git a/plugins/SitesManager/tests/Integration/ModelTest.php b/plugins/SitesManager/tests/Integration/ModelTest.php index 45ebce2be8..7291ac346f 100644 --- a/plugins/SitesManager/tests/Integration/ModelTest.php +++ b/plugins/SitesManager/tests/Integration/ModelTest.php @@ -56,10 +56,56 @@ class ModelTest extends IntegrationTestCase $this->assertSame(array('website', 'universal', 'mobileapp'), $this->model->getUsedTypeIds()); } - private function createMeasurable($type) + public function test_getAllKnownUrlsForAllSites_shouldReturnAllUrls() { - Fixture::createWebsite('2015-01-01 00:00:00', - $ecommerce = 0, $siteName = false, $siteUrl = false, + $idSite = $this->createMeasurable('website', 'http://apache.piwik'); + $this->model->insertSiteUrl($idSite, 'http://example.apache.piwik'); + $this->model->insertSiteUrl($idSite, 'http://example.org'); + + $idSite2 = $this->createMeasurable('website'); + $this->model->insertSiteUrl($idSite2, 'http://example.org'); + $this->model->insertSiteUrl($idSite2, 'http://example.com'); + + $idSite3 = $this->createMeasurable('website', 'http://example.pro'); + + $expected = array( + array( + 'idsite' => $idSite, + 'url' => 'http://apache.piwik' + ), + array( + 'idsite' => $idSite2, + 'url' => 'http://piwik.net' + ), + array( + 'idsite' => $idSite3, + 'url' => 'http://example.pro' + ), + array( + 'idsite' => $idSite, + 'url' => 'http://example.apache.piwik' + ), + array( + 'idsite' => $idSite, + 'url' => 'http://example.org' + ), + array( + 'idsite' => $idSite2, + 'url' => 'http://example.com' + ), + array( + 'idsite' => $idSite2, + 'url' => 'http://example.org' + ) + + ); + $this->assertEquals($expected, $this->model->getAllKnownUrlsForAllSites()); + } + + private function createMeasurable($type, $siteUrl = false) + { + return Fixture::createWebsite('2015-01-01 00:00:00', + $ecommerce = 0, $siteName = false, $siteUrl, $siteSearch = 1, $searchKeywordParameters = null, $searchCategoryParameters = null, $timezone = null, $type); } diff --git a/plugins/SitesManager/tests/Integration/SiteUrlsTest.php b/plugins/SitesManager/tests/Integration/SiteUrlsTest.php index f9362ae2f4..39d628197e 100644 --- a/plugins/SitesManager/tests/Integration/SiteUrlsTest.php +++ b/plugins/SitesManager/tests/Integration/SiteUrlsTest.php @@ -119,6 +119,130 @@ class SiteUrlsTest extends IntegrationTestCase $this->assertEquals($urlsToFake, $actual); } + public function test_groupUrlsByHost_shouldReturnEmptyArray_WhenNoUrlsGiven() + { + $this->assertSame(array(), $this->siteUrls->groupUrlsByHost(array())); + $this->assertSame(array(), $this->siteUrls->groupUrlsByHost(null)); + } + + public function test_groupUrlsByHost_shouldGroupByHost_WithOneSiteAndDifferentDomains_shouldRemoveWwwAndDefaultToPathSlash() + { + $idSite = 1; + $oneSite = array( + $idSite => array( + 'http://apache.piwik', + 'http://www.example.com', // should remove www. + 'https://example.org', // should handle https or other protocol + 'http://apache.piwik/', // same as initial one but with slash at the end, should not add idsite twice + 'http://third.www.com' // should not remove www. in the middle of a domain + ) + ); + + $expected = array( + 'apache.piwik' => array('/' => array($idSite)), + 'example.com' => array('/' => array($idSite)), + 'example.org' => array('/' => array($idSite)), + 'third.www.com' => array('/' => array($idSite)), + ); + + $this->assertSame($expected, $this->siteUrls->groupUrlsByHost($oneSite)); + } + + public function test_groupUrlsByHost_shouldGroupByHost_WithDifferentDomainsAndPathsShouldListPathByNumberOfDirectoriesAndConvertToLowerCase() + { + $idSite = 1; + $idSite2 = 2; + $idSite3 = 3; + $idSite4 = 4; + $idSite5 = 5; + + $urls = array( + $idSite => array( + 'http://apache.piwik/test', 'http://apache.piWik', 'http://apache.piwik/foo/bAr/', 'http://apache.piwik/Foo/SECOND' + ), + $idSite2 => array( + 'http://apache.piwik/test/', 'http://example.oRg', 'http://apache.piwik/foo/secOnd' + ), + $idSite3 => array( + 'http://apache.piwik/', 'http://apache.piwik/third', 'http://exampLe.com', 'http://example.org/foo/test/two' + ), + $idSite4 => array(), + $idSite5 => array('invalidUrl', 'ftp://example.org/'), + ); + + $expected = array( + 'apache.piwik' => array( + '/foo/second/' => array($idSite, $idSite2), + '/foo/bar/' => array($idSite), + '/third/' => array($idSite3), + '/test/' => array($idSite, $idSite2), + '/' => array($idSite, $idSite3) + ), + 'example.org' => array( + '/foo/test/two/' => array($idSite3), + '/' => array($idSite2, $idSite5) + ), + 'example.com' => array( + '/' => array($idSite3) + ), + ); + + $this->assertSame($expected, $this->siteUrls->groupUrlsByHost($urls)); + } + + /** + * @dataProvider getTestIdSitesMatchingUrl + */ + public function test_getIdSitesMatchingUrl($expectedMatchSites, $parsedUrl) + { + $urlsGroupedByHost = array( + 'apache.piwik' => array( + '/foo/second/' => array(2), + '/foo/sec/' => array(4), + '/foo/bar/' => array(1), + '/third/' => array(3), + '/test/' => array(1, 2), + '/' => array(1, 3) + ), + 'example.org' => array( + '/foo/test/two/' => array(3), + '/foo/second/' => array(6), + '/' => array(2, 5) + ), + 'example.com' => array( + '/' => array(3) + ), + ); + $matchedSites = $this->siteUrls->getIdSitesMatchingUrl($parsedUrl, $urlsGroupedByHost); + + $this->assertSame($expectedMatchSites, $matchedSites); + } + + public function getTestIdSitesMatchingUrl() + { + return array( + array(array(1,3), array('host' => 'apache.piwik')), + array(array(1,3), array('host' => 'apache.piwik', 'path' => '/')), + array(array(1,3), array('host' => 'apache.piwik', 'path' => 'nomatch')), // no other URL matches a site so we fall back to domain match + array(array(1,3), array('host' => 'apache.piwik', 'path' => '/nomatch')), + array(array(2), array('host' => 'apache.piwik', 'path' => '/foo/second')), + array(array(2), array('host' => 'apache.piwik', 'path' => '/foo/second/')), // it shouldn't matter if slash is at end or not + array(array(2), array('host' => 'apache.piwik', 'path' => '/foo/second/test')), // it should find best match + array(array(4), array('host' => 'apache.piwik', 'path' => '/foo/sec/test')), // it should not use /foo/second for these + array(array(4), array('host' => 'apache.piwik', 'path' => '/foo/sec/')), + array(array(4), array('host' => 'apache.piwik', 'path' => '/foo/sec')), + array(array(1,3), array('host' => 'apache.piwik', 'path' => '/foo')), + array(array(2,5), array('host' => 'example.org')), + array(array(2,5), array('host' => 'example.org', 'path' => '/')), + array(array(2,5), array('host' => 'example.org', 'path' => 'any/nonmatching/path')), + array(array(6), array('host' => 'example.org', 'path' => '/foo/second')), + array(array(6), array('host' => 'example.org', 'path' => '/foo/second/test')), + array(array(3), array('host' => 'example.com')), + array(null, array('host' => 'example.pro')), + array(null, array('host' => 'example.pro', 'path' => '/any')), + ); + } + private function assertSiteUrls($expectedUrls) { $urls = $this->siteUrls->getAllSiteUrls(); |