PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.3.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.3.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Tracker / PageUrl.php
matomo / app / core / Tracker Last commit date
Db 5 years ago Handler 5 years ago TableLogAction 5 years ago Visit 5 years ago Action.php 5 years ago ActionPageview.php 5 years ago Cache.php 5 years ago Db.php 5 years ago Failures.php 6 years ago FingerprintSalt.php 6 years ago GoalManager.php 5 years ago Handler.php 5 years ago IgnoreCookie.php 5 years ago LogTable.php 5 years ago Model.php 5 years ago PageUrl.php 5 years ago Request.php 5 years ago RequestProcessor.php 5 years ago RequestSet.php 5 years ago Response.php 5 years ago ScheduledTasksRunner.php 5 years ago Settings.php 5 years ago TableLogAction.php 5 years ago TrackerCodeGenerator.php 5 years ago TrackerConfig.php 5 years ago Visit.php 5 years ago VisitExcluded.php 5 years ago VisitInterface.php 5 years ago Visitor.php 5 years ago VisitorNotFoundInDb.php 5 years ago VisitorRecognizer.php 5 years ago
PageUrl.php
434 lines
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 */
9
10 namespace Piwik\Tracker;
11
12 use Piwik\Cache;
13 use Piwik\CacheId;
14 use Piwik\Tracker\Cache as TrackerCache;
15 use Piwik\Common;
16 use Piwik\Config;
17 use Piwik\Piwik;
18 use Piwik\Plugins\SitesManager\API as APISitesManager;
19 use Piwik\UrlHelper;
20
21 class PageUrl
22 {
23
24 /**
25 * Map URL prefixes to integers.
26 * @see self::normalizeUrl(), self::reconstructNormalizedUrl()
27 */
28 public static $urlPrefixMap = array(
29 'http://www.' => 1,
30 'http://' => 0,
31 'https://www.' => 3,
32 'https://' => 2
33 );
34
35 /**
36 * Given the Input URL, will exclude all query parameters set for this site
37 *
38 * @static
39 * @param string $originalUrl
40 * @param $idSite
41 * @param array $additionalParametersToExclude
42 * @return bool|string Returned URL is HTML entities decoded
43 */
44 public static function excludeQueryParametersFromUrl($originalUrl, $idSite, $additionalParametersToExclude = [])
45 {
46 $originalUrl = self::cleanupUrl($originalUrl);
47
48 $parsedUrl = @parse_url($originalUrl);
49 $parsedUrl = self::cleanupHostAndHashTag($parsedUrl, $idSite);
50 $parametersToExclude = array_merge(self::getQueryParametersToExclude($idSite), $additionalParametersToExclude);
51
52 if (empty($parsedUrl['query'])) {
53 if (empty($parsedUrl['fragment'])) {
54 return UrlHelper::getParseUrlReverse($parsedUrl);
55 }
56
57 // Exclude from the hash tag as well
58 $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['fragment']);
59 $parsedUrl['fragment'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
60 $url = UrlHelper::getParseUrlReverse($parsedUrl);
61
62 return $url;
63 }
64
65 $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['query']);
66 $parsedUrl['query'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
67 $url = UrlHelper::getParseUrlReverse($parsedUrl);
68
69 return $url;
70 }
71
72 /**
73 * Returns the array of parameters names that must be excluded from the Query String in all tracked URLs
74 * @static
75 * @param $idSite
76 * @return array
77 */
78 public static function getQueryParametersToExclude($idSite)
79 {
80 $campaignTrackingParameters = Common::getCampaignParameters();
81
82 $campaignTrackingParameters = array_merge(
83 $campaignTrackingParameters[0], // campaign name parameters
84 $campaignTrackingParameters[1] // campaign keyword parameters
85 );
86
87 $website = TrackerCache::getCacheWebsiteAttributes($idSite);
88 $excludedParameters = self::getExcludedParametersFromWebsite($website);
89
90 $parametersToExclude = array_merge($excludedParameters,
91 self::getUrlParameterNamesToExcludeFromUrl(),
92 $campaignTrackingParameters);
93
94 /**
95 * Triggered before setting the action url in Piwik\Tracker\Action so plugins can register
96 * parameters to be excluded from the tracking URL (e.g. campaign parameters).
97 *
98 * @param array &$parametersToExclude An array of parameters to exclude from the tracking url.
99 */
100 Piwik::postEvent('Tracker.PageUrl.getQueryParametersToExclude', array(&$parametersToExclude));
101
102 if (!empty($parametersToExclude)) {
103 Common::printDebug('Excluding parameters "' . implode(',', $parametersToExclude) . '" from URL');
104 }
105
106 $parametersToExclude = array_map('strtolower', $parametersToExclude);
107 return $parametersToExclude;
108 }
109
110 /**
111 * Returns the list of URL query parameters that should be removed from the tracked URL query string.
112 *
113 * @return array
114 */
115 protected static function getUrlParameterNamesToExcludeFromUrl()
116 {
117 $paramsToExclude = Config::getInstance()->Tracker['url_query_parameter_to_exclude_from_url'];
118 $paramsToExclude = explode(",", $paramsToExclude);
119 $paramsToExclude = array_map('trim', $paramsToExclude);
120 return $paramsToExclude;
121 }
122
123 /**
124 * Returns true if URL fragments should be removed for a specific site,
125 * false if otherwise.
126 *
127 * This function uses the Tracker cache and not the MySQL database.
128 *
129 * @param $idSite int The ID of the site to check for.
130 * @return bool
131 */
132 public static function shouldRemoveURLFragmentFor($idSite)
133 {
134 $websiteAttributes = TrackerCache::getCacheWebsiteAttributes($idSite);
135 return empty($websiteAttributes['keep_url_fragment']);
136 }
137
138 /**
139 * Cleans and/or removes the URL fragment of a URL.
140 *
141 * @param $urlFragment string The URL fragment to process.
142 * @param $idSite int|bool If not false, this function will check if URL fragments
143 * should be removed for the site w/ this ID and if so,
144 * the returned processed fragment will be empty.
145 *
146 * @return string The processed URL fragment.
147 */
148 public static function processUrlFragment($urlFragment, $idSite = false)
149 {
150 // if we should discard the url fragment for this site, return an empty string as
151 // the processed url fragment
152 if ($idSite !== false
153 && PageUrl::shouldRemoveURLFragmentFor($idSite)
154 ) {
155 return '';
156 } else {
157 // Remove trailing Hash tag in ?query#hash#
158 if (substr($urlFragment, -1) == '#') {
159 $urlFragment = substr($urlFragment, 0, strlen($urlFragment) - 1);
160 }
161 return $urlFragment;
162 }
163 }
164
165 /**
166 * Will cleanup the hostname (some browser do not strolower the hostname),
167 * and deal ith the hash tag on incoming URLs based on website setting.
168 *
169 * @param $parsedUrl
170 * @param $idSite int|bool The site ID of the current visit. This parameter is
171 * only used by the tracker to see if we should remove
172 * the URL fragment for this site.
173 * @return array
174 */
175 protected static function cleanupHostAndHashTag($parsedUrl, $idSite = false)
176 {
177 if (empty($parsedUrl)) {
178 return $parsedUrl;
179 }
180
181 if (!empty($parsedUrl['host'])) {
182 $parsedUrl['host'] = Common::mb_strtolower($parsedUrl['host']);
183 }
184
185 if (!empty($parsedUrl['fragment'])) {
186 $parsedUrl['fragment'] = PageUrl::processUrlFragment($parsedUrl['fragment'], $idSite);
187 }
188
189 return $parsedUrl;
190 }
191
192 /**
193 * Converts Matrix URL format
194 * from http://example.org/thing;paramA=1;paramB=6542
195 * to http://example.org/thing?paramA=1&paramB=6542
196 *
197 * @param string $originalUrl
198 * @return string
199 */
200 public static function convertMatrixUrl($originalUrl)
201 {
202 $posFirstSemiColon = strpos($originalUrl, ";");
203
204 if (false === $posFirstSemiColon) {
205 return $originalUrl;
206 }
207
208 $posQuestionMark = strpos($originalUrl, "?");
209 $replace = (false === $posQuestionMark);
210
211 if ($posQuestionMark > $posFirstSemiColon) {
212 $originalUrl = substr_replace($originalUrl, ";", $posQuestionMark, 1);
213 $replace = true;
214 }
215
216 if ($replace) {
217 $originalUrl = substr_replace($originalUrl, "?", strpos($originalUrl, ";"), 1);
218 $originalUrl = str_replace(";", "&", $originalUrl);
219 }
220
221 return $originalUrl;
222 }
223
224 /**
225 * Clean up string contents (filter, truncate, ...)
226 *
227 * @param string $string Dirty string
228 * @return string
229 */
230 public static function cleanupString($string)
231 {
232 $string = trim($string);
233 $string = str_replace(array("\n", "\r", "\0"), '', $string);
234
235 $limit = Config::getInstance()->Tracker['page_maximum_length'];
236 $clean = substr($string, 0, $limit);
237 return $clean;
238 }
239
240 protected static function reencodeParameterValue($value, $encoding)
241 {
242 if (is_string($value)) {
243 $decoded = urldecode($value);
244 try {
245 if (function_exists('mb_check_encoding')
246 && @mb_check_encoding($decoded, $encoding)) {
247 $value = urlencode(mb_convert_encoding($decoded, 'UTF-8', $encoding));
248 }
249 } catch (\Error $e) {
250 // mb_check_encoding might throw an ValueError on PHP 8 if the given encoding does not exist
251 // we can't simply catch ValueError as it was introduced in PHP 8
252 }
253 }
254
255 return $value;
256 }
257
258 protected static function reencodeParametersArray($queryParameters, $encoding)
259 {
260 foreach ($queryParameters as &$value) {
261 if (is_array($value)) {
262 $value = self::reencodeParametersArray($value, $encoding);
263 } else {
264 $value = PageUrl::reencodeParameterValue($value, $encoding);
265 }
266 }
267
268 return $queryParameters;
269 }
270
271 /**
272 * Checks if query parameters are of a non-UTF-8 encoding and converts the values
273 * from the specified encoding to UTF-8.
274 * This method is used to workaround browser/webapp bugs (see #3450). When
275 * browsers fail to encode query parameters in UTF-8, the tracker will send the
276 * charset of the page viewed and we can sometimes work around invalid data
277 * being stored.
278 *
279 * @param array $queryParameters Name/value mapping of query parameters.
280 * @param bool|string $encoding of the HTML page the URL is for. Used to workaround
281 * browser bugs & mis-coded webapps. See #3450.
282 *
283 * @return array
284 */
285 public static function reencodeParameters(&$queryParameters, $encoding = false)
286 {
287 if (function_exists('mb_check_encoding')) {
288 // if query params are encoded w/ non-utf8 characters (due to browser bug or whatever),
289 // encode to UTF-8.
290 if (strtolower($encoding) != 'utf-8'
291 && $encoding != false
292 ) {
293 Common::printDebug("Encoding page URL query parameters to $encoding.");
294
295 $queryParameters = PageUrl::reencodeParametersArray($queryParameters, $encoding);
296 }
297 } else {
298 Common::printDebug("Page charset supplied in tracking request, but mbstring extension is not available.");
299 }
300
301 return $queryParameters;
302 }
303
304 public static function cleanupUrl($url)
305 {
306 $url = Common::unsanitizeInputValue($url);
307 $url = PageUrl::cleanupString($url);
308 $url = PageUrl::convertMatrixUrl($url);
309
310 return $url;
311 }
312
313 /**
314 * Build the full URL from the prefix ID and the rest.
315 *
316 * @param string $url
317 * @param integer $prefixId
318 * @return string
319 */
320 public static function reconstructNormalizedUrl($url, $prefixId)
321 {
322 $map = array_flip(self::$urlPrefixMap);
323
324 if ($prefixId !== null && isset($map[$prefixId])) {
325 $fullUrl = $map[$prefixId] . $url;
326 } else {
327 $fullUrl = $url;
328 }
329
330 // Clean up host & hash tags, for URLs
331 $parsedUrl = @parse_url($fullUrl);
332 $parsedUrl = PageUrl::cleanupHostAndHashTag($parsedUrl);
333 $url = UrlHelper::getParseUrlReverse($parsedUrl);
334
335 if (!empty($url)) {
336 return $url;
337 }
338
339 return $fullUrl;
340 }
341
342 /**
343 * Returns if the given host is also configured as https in page urls of given site
344 *
345 * @param $idSite
346 * @param $host
347 * @return false|mixed
348 * @throws \Exception
349 */
350 public static function shouldUseHttpsHost($idSite, $host)
351 {
352 $cache = Cache::getTransientCache();
353
354 $cacheKeySiteUrls = CacheId::siteAware('siteurls', [$idSite]);
355 $cacheKeyHttpsForHost = CacheId::siteAware(sprintf('shouldusehttps-%s', $host), [$idSite]);
356
357 $siteUrlCache = $cache->fetch($cacheKeySiteUrls);
358
359 if (empty($siteUrlCache)) {
360 $siteUrlCache = APISitesManager::getInstance()->getSiteUrlsFromId($idSite);
361 $cache->save($cacheKeySiteUrls, $siteUrlCache);
362 }
363
364 if (!$cache->contains($cacheKeyHttpsForHost)) {
365 $hostSiteCache = false;
366
367 foreach ($siteUrlCache as $siteUrl) {
368 if (strpos(Common::mb_strtolower($siteUrl), Common::mb_strtolower('https://' . $host)) === 0) {
369 $hostSiteCache = true;
370 break;
371 }
372 }
373
374 $cache->save($cacheKeyHttpsForHost, $hostSiteCache);
375 }
376
377 return $cache->fetch($cacheKeyHttpsForHost);
378 }
379
380 /**
381 * Extract the prefix from a URL.
382 * Return the prefix ID and the rest.
383 *
384 * @param string $url
385 * @return array
386 */
387 public static function normalizeUrl($url)
388 {
389 foreach (self::$urlPrefixMap as $prefix => $id) {
390 if (strtolower(substr($url, 0, strlen($prefix))) == $prefix) {
391 return array(
392 'url' => substr($url, strlen($prefix)),
393 'prefixId' => $id
394 );
395 }
396 }
397
398 return array('url' => $url, 'prefixId' => null);
399 }
400
401 public static function getUrlIfLookValid($url)
402 {
403 $url = PageUrl::cleanupString($url);
404
405 if (!UrlHelper::isLookLikeUrl($url)) {
406 Common::printDebug("WARNING: URL looks invalid and is discarded");
407
408 return false;
409 }
410
411 return $url;
412 }
413
414 private static function getExcludedParametersFromWebsite($website)
415 {
416 if (isset($website['excluded_parameters'])) {
417 return $website['excluded_parameters'];
418 }
419
420 return array();
421 }
422
423 public static function urldecodeValidUtf8($value)
424 {
425 $value = urldecode($value);
426 if (function_exists('mb_check_encoding')
427 && !@mb_check_encoding($value, 'utf-8')
428 ) {
429 return urlencode($value);
430 }
431 return $value;
432 }
433 }
434