diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php index 8402dc5bd1441a8014c16d49155c7bf288c525f8..8b554e1ae40e217272c7c7a8f478d4732924dbbe 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -92,6 +92,7 @@ class Mysql implements SchemaInterface sitesearch_category_parameters TEXT NOT NULL, timezone VARCHAR( 50 ) NOT NULL, currency CHAR( 3 ) NOT NULL, + exclude_unknown_urls TINYINT(1) DEFAULT 0, excluded_ips TEXT NOT NULL, excluded_parameters TEXT NOT NULL, excluded_user_agents TEXT NOT NULL, diff --git a/core/Tracker/VisitExcluded.php b/core/Tracker/VisitExcluded.php index 028c44460cfaf6569a044fb514a9cc72e90220ff..ef0cd00b6dc8aca4dedfe6a65637a02205b29f96 100644 --- a/core/Tracker/VisitExcluded.php +++ b/core/Tracker/VisitExcluded.php @@ -128,6 +128,14 @@ class VisitExcluded } } + // Check if request URL is excluded + if (!$excluded) { + $excluded = $this->isUrlExcluded(); + if ($excluded) { + Common::printDebug("Unknown URL is not allowed to track."); + } + } + if (!$excluded) { if ($this->isPrefetchDetected()) { $excluded = true; @@ -274,6 +282,27 @@ class VisitExcluded return false; } + /** + * Checks if request URL is excluded + * @return bool + */ + protected function isUrlExcluded() + { + $site = Cache::getCacheWebsiteAttributes($this->idSite); + + if (!empty($site['exclude_unknown_urls']) && !empty($site['hosts'])) { + $trackingHost = parse_url($this->request->getParam('url'), PHP_URL_HOST); + foreach ($site['hosts'] as $siteHost) { + if ($trackingHost == $siteHost) { + return false; + } + } + return true; + } + + return false; + } + /** * Returns true if the specified user agent should be excluded for the current site or not. * diff --git a/core/Updates/2.14.1-b2.php b/core/Updates/2.14.1-b2.php new file mode 100644 index 0000000000000000000000000000000000000000..8762fedc18d194fd4d6cd90e019d1a904484251f --- /dev/null +++ b/core/Updates/2.14.1-b2.php @@ -0,0 +1,41 @@ +<?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\Updates; + +use Piwik\Common; +use Piwik\Updater; +use Piwik\Updates; + +/** + * Update for version 2.14.0. + */ +class Updates_2_14_1_b2 extends Updates +{ + /** + * Here you can define one or multiple SQL statements that should be executed during the update. + * @return array + */ + static function getSql() + { + $updateSql = array( + 'ALTER TABLE `' . Common::prefixTable('site') . '` ADD COLUMN `exclude_unknown_urls` TINYINT(1) DEFAULT 0 AFTER `currency`' => array(1060) + ); + return $updateSql; + } + + /** + * Here you can define any action that should be performed during the update. For instance executing SQL statements, + * renaming config entries, updating files, etc. + */ + static function update() + { + Updater::updateDatabase(__FILE__, self::getSql()); + } +} diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php index 8c182c47a519598de92262124b3ff7360a3e05fe..8d13eb1e1132d0901a1fadb4acef32d97b0119c0 100644 --- a/plugins/SitesManager/API.php +++ b/plugins/SitesManager/API.php @@ -506,6 +506,7 @@ class API extends \Piwik\Plugin\API * @param array|null $settings JSON serialized settings eg {settingName: settingValue, ...} * @see getKeepURLFragmentsGlobal. * @param string $type The website type, defaults to "website" if not set. + * @param bool|null $excludeUnknownUrls Track only URL matching one of website URLs * * @return int the website ID created */ @@ -524,7 +525,8 @@ class API extends \Piwik\Plugin\API $excludedUserAgents = null, $keepURLFragments = null, $type = null, - $settings = null) + $settings = null, + $excludeUnknownUrls = null) { Piwik::checkUserHasSuperUserAccess(); @@ -555,6 +557,7 @@ class API extends \Piwik\Plugin\API $bind = array('name' => $siteName, 'main_url' => $url); + $bind['exclude_unknown_urls'] = (bool)$excludeUnknownUrls; $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); @@ -1087,6 +1090,7 @@ class API extends \Piwik\Plugin\API * will be removed. If 0, the default global behavior will be used. * @param string $type The Website type, default value is "website" * @param array|null $settings JSON serialized settings eg {settingName: settingValue, ...} + * @param bool|null $excludeUnknownUrls Track only URL matching one of website URLs * @throws Exception * @see getKeepURLFragmentsGlobal. If null, the existing value will * not be modified. @@ -1109,7 +1113,8 @@ class API extends \Piwik\Plugin\API $excludedUserAgents = null, $keepURLFragments = null, $type = null, - $settings = null) + $settings = null, + $excludeUnknownUrls = null) { Piwik::checkUserHasAdminAccess($idSite); @@ -1156,6 +1161,7 @@ class API extends \Piwik\Plugin\API if (!is_null($startDate)) { $bind['ts_created'] = Date::factory($startDate)->getDatetime(); } + $bind['exclude_unknown_urls'] = (bool)$excludeUnknownUrls; $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php index a2982346406667332468f759113690a355996ee7..16c330d6c1cef067e306f0b28d9120fd5e2fdb90 100644 --- a/plugins/SitesManager/SitesManager.php +++ b/plugins/SitesManager/SitesManager.php @@ -117,6 +117,7 @@ class SitesManager extends \Piwik\Plugin $array['hosts'] = $this->getTrackerHosts($idSite); $website = API::getInstance()->getSiteFromId($idSite); + $array['exclude_unknown_urls'] = $website['exclude_unknown_urls']; $array['excluded_ips'] = $this->getTrackerExcludedIps($website); $array['excluded_parameters'] = self::getTrackerExcludedQueryParameters($website); $array['excluded_user_agents'] = self::getExcludedUserAgents($website); @@ -286,6 +287,8 @@ class SitesManager extends \Piwik\Plugin $translationKeys[] = "SitesManager_Currency"; $translationKeys[] = "SitesManager_ShowTrackingTag"; $translationKeys[] = "SitesManager_AliasUrlHelp"; + $translationKeys[] = "SitesManager_OnlyMatchedUrlsAllowed"; + $translationKeys[] = "SitesManager_OnlyMatchedUrlsAllowedHelp"; $translationKeys[] = "SitesManager_KeepURLFragmentsLong"; $translationKeys[] = "SitesManager_HelpExcludedIps"; $translationKeys[] = "SitesManager_ListOfQueryParametersToExclude"; diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js index 21dc868f7ae59c62d920a00c8917d69be5105b8a..d759ec940f02ad77be9118b9b22fbf1d0f1ab66e 100644 --- a/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js +++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js @@ -111,6 +111,7 @@ searchKeywordParameters: sendSiteSearchKeywordParams ? $scope.site.sitesearch_keyword_parameters.join(',') : null, searchCategoryParameters: sendSearchCategoryParameters ? $scope.site.sitesearch_category_parameters.join(',') : null, urls: $scope.site.alias_urls, + excludeUnknownUrls: $scope.site.exclude_unknown_urls, settings: flatSettings }, 'POST'); @@ -133,6 +134,7 @@ "http://siteUrl.com/", "http://siteUrl2.com/" ]; + $scope.site.exclude_unknown_urls = 0; $scope.site.keep_url_fragment = "0"; $scope.site.excluded_ips = []; $scope.site.excluded_parameters = []; diff --git a/plugins/SitesManager/lang/en.json b/plugins/SitesManager/lang/en.json index 20977db504988f9b8ae4d50ef0ec1290d82fd901..218140c307ccb5ef83228d35a00291d37ae26371 100644 --- a/plugins/SitesManager/lang/en.json +++ b/plugins/SitesManager/lang/en.json @@ -74,6 +74,8 @@ "TrackingTags": "Tracking code for %s", "Urls": "URLs", "UTCTimeIs": "UTC time is %s.", + "OnlyMatchedUrlsAllowed": "Only track visits for actions on any of the website URLs", + "OnlyMatchedUrlsAllowedHelp": "When enabled, Piwik will only track the data when the Page URL is one of the known URLs for your website.", "WebsitesManagement": "Websites Management", "XManagement": "Manage %s", "ChooseMeasurableTypeHeadline": "What would you like to measure?", diff --git a/plugins/SitesManager/templates/sites-list/site-fields.html b/plugins/SitesManager/templates/sites-list/site-fields.html index 888d9f7b1fdf6ebd3109a0d5ca9f874ba9c16177..dada85cc225cb49be691ba4123e3205adb31482b 100644 --- a/plugins/SitesManager/templates/sites-list/site-fields.html +++ b/plugins/SitesManager/templates/sites-list/site-fields.html @@ -82,6 +82,13 @@ {{ 'SitesManager_AliasUrlHelp' | translate }} </div> <div sites-manager-multiline-field field="site.alias_urls" cols="25" rows="3"></div> + + <div class="form-help"> + {{ 'SitesManager_OnlyMatchedUrlsAllowedHelp' | translate }} + </div> + <label class="checkbox"> + <input type="checkbox" ng-model="site.exclude_unknown_urls" ng-true-value="1" ng-false-value="0"> {{ 'SitesManager_OnlyMatchedUrlsAllowed' | translate:'':'' }} + </label> </div> <div class="form-group"> diff --git a/plugins/SitesManager/tests/Integration/ApiTest.php b/plugins/SitesManager/tests/Integration/ApiTest.php index 0bb70872bba5e03c3f3fbb9cb1b0dede792fa9f8..9d8542526daf24214c6a362c925aa4242cdfd864 100644 --- a/plugins/SitesManager/tests/Integration/ApiTest.php +++ b/plugins/SitesManager/tests/Integration/ApiTest.php @@ -550,8 +550,8 @@ class ApiTest extends IntegrationTestCase API::getInstance()->addSite("site3", array("http://piwik.org")); $resultWanted = array( - 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), - 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), + 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), + 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), ); FakeAccess::setIdSitesAdmin(array(1, 3)); @@ -659,8 +659,8 @@ class ApiTest extends IntegrationTestCase API::getInstance()->addSite("site3", array("http://piwik.org")); $resultWanted = array( - 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 0, 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', "excluded_ips" => "", 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), - 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', "excluded_ips" => "", 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), + 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 0, 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', "excluded_ips" => "", 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), + 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', "excluded_ips" => "", 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), ); FakeAccess::setIdSitesView(array(1, 3)); @@ -695,8 +695,8 @@ class ApiTest extends IntegrationTestCase API::getInstance()->addSite("site3", array("http://piwik.org")); $resultWanted = array( - 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 1, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), - 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website'), + 0 => array("idsite" => 1, "name" => "site1", "main_url" => "http://piwik.net", "ecommerce" => 1, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), + 1 => array("idsite" => 3, "name" => "site3", "main_url" => "http://piwik.org", "ecommerce" => 0, "excluded_ips" => "", 'sitesearch' => 1, 'sitesearch_keyword_parameters' => '', 'sitesearch_category_parameters' => '', 'excluded_parameters' => '', 'excluded_user_agents' => '', 'timezone' => 'UTC', 'currency' => 'USD', 'group' => '', 'keep_url_fragment' => 0, 'type' => 'website', 'exclude_unknown_urls' => 0), ); FakeAccess::setIdSitesView(array(1, 3)); diff --git a/plugins/SitesManager/tests/System/expected/test_SitesManager__SitesManager.getPatternMatchSites.xml b/plugins/SitesManager/tests/System/expected/test_SitesManager__SitesManager.getPatternMatchSites.xml index 1fa55da06a40476a35c173de440b50fff85fbd79..fbcef21650755bd7d6c7e1e2c9086f052287eac0 100644 --- a/plugins/SitesManager/tests/System/expected/test_SitesManager__SitesManager.getPatternMatchSites.xml +++ b/plugins/SitesManager/tests/System/expected/test_SitesManager__SitesManager.getPatternMatchSites.xml @@ -17,6 +17,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>10</idsite> @@ -35,6 +36,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>11</idsite> @@ -53,6 +55,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>12</idsite> @@ -71,6 +74,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>13</idsite> @@ -89,6 +93,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>14</idsite> @@ -107,6 +112,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>15</idsite> @@ -125,6 +131,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>16</idsite> @@ -143,6 +150,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>17</idsite> @@ -161,6 +169,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>18</idsite> @@ -179,6 +188,7 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> <row> <idsite>19</idsite> @@ -197,5 +207,6 @@ <group /> <type>website</type> <keep_url_fragment>0</keep_url_fragment> + <exclude_unknown_urls>0</exclude_unknown_urls> </row> </result> \ No newline at end of file diff --git a/tests/PHPUnit/Integration/Tracker/VisitTest.php b/tests/PHPUnit/Integration/Tracker/VisitTest.php index 78410327c556388e21bd469466f19c635f6d3a99..107c16c0835e731479845a8a855a260ff6d87f14 100644 --- a/tests/PHPUnit/Integration/Tracker/VisitTest.php +++ b/tests/PHPUnit/Integration/Tracker/VisitTest.php @@ -99,6 +99,45 @@ class VisitTest extends IntegrationTestCase } } + public function getExcludeByUrlData() + { + return array( + array(array('http://test.com'), true, array( + 'http://test.com' => true, + 'https://test.com' => true, + 'http://test.com/uri' => true, + 'http://test.com/?query' => true, + 'http://xtest.com' => false, + 'http://x.test.com' => false, + 'http://x.com/test.com' => false, + )), + array(array('http://test.com', 'http://localhost'), true, array( + 'http://test.com' => true, + 'http://localhost' => true, + 'http://x.com' => false, + )), + array(array('http://test.com'), false, array( + 'http://x.com' => true, + )), + ); + } + + /** + * @dataProvider getExcludeByUrlData + */ + public function testExcludeByUrl($siteUrls, $excludeUnknownUrls, array $urlsTracked) + { + $siteId = API::getInstance()->addSite('name', $siteUrls, $ecommerce = null, $siteSearch = null, $searchKeywordParameters = null, $searchCategoryParameters = null, null, null, null, null, null, null, null, null, null, null, $excludeUnknownUrls); + foreach ($urlsTracked as $url => $isTracked) { + $visitExclude = new VisitExcluded(new Request(array( + 'idsite' => $siteId, + 'rec' => 1, + 'url' => $url + ))); + $this->assertEquals($isTracked, !$visitExclude->isExcluded()); + } + } + /** * @dataProvider getChromeDataSaverData */