PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 / SiteContentDetector.php
matomo / app / core Last commit date
API 3 months ago Access 3 months ago Application 3 months ago Archive 3 months ago ArchiveProcessor 3 months ago Archiver 2 years ago AssetManager 3 months ago Auth 6 months ago Category 6 months ago Changes 3 months ago CliMulti 1 year ago Columns 3 months ago Concurrency 3 months ago Config 3 months ago Container 3 months ago CronArchive 3 months ago DataAccess 3 months ago DataFiles 2 years ago DataTable 3 months ago Db 3 months ago DeviceDetector 1 year ago Email 2 years ago Exception 4 months ago Http 4 months ago Intl 3 months ago Log 2 years ago Mail 1 year ago Measurable 6 months ago Menu 3 months ago Metrics 3 months ago Notification 6 months ago Period 3 months ago Plugin 3 months ago Policy 3 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 3 months ago Segment 3 months ago Session 3 months ago Settings 3 months ago Tracker 3 months ago Translation 3 months ago Twig 1 year ago UpdateCheck 3 months ago Updater 4 months ago Updates 3 months ago Validators 1 year ago View 6 months ago ViewDataTable 3 months ago Visualization 1 year ago Widget 3 months ago .htaccess 2 years ago Access.php 3 months ago Archive.php 3 months ago ArchiveProcessor.php 4 months ago AssetManager.php 3 months ago Auth.php 6 months ago AuthResult.php 6 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 4 months ago CliMulti.php 3 months ago Common.php 3 months ago Config.php 3 months ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 3 months ago DI.php 3 months ago DataArray.php 5 months ago DataTable.php 3 months ago Date.php 3 months ago Db.php 3 months ago DbHelper.php 3 months ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 4 months ago FileIntegrity.php 3 months ago Filechecks.php 1 year ago Filesystem.php 3 months ago FrontController.php 4 months ago Http.php 4 months ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 3 months ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 6 months ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 3 months ago Piwik.php 3 months ago Plugin.php 3 months ago Process.php 1 year ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 5 months ago ReportRenderer.php 3 months ago Request.php 3 months ago Segment.php 3 months ago Sequence.php 6 months ago Session.php 3 months ago SettingsPiwik.php 3 months ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 4 months ago SiteContentDetector.php 3 months ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 3 months ago Twig.php 3 months ago Unzip.php 1 year ago UpdateCheck.php 3 months ago Updater.php 3 months ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 3 months ago Version.php 3 months ago View.php 3 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 6 months ago
SiteContentDetector.php
315 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik;
10
11 use Matomo\Cache\Lazy;
12 use Piwik\Config\GeneralConfig;
13 use Piwik\Container\StaticContainer;
14 use Piwik\Plugins\SitesManager\SiteContentDetection\ConsentManagerDetectionAbstract;
15 use Piwik\Plugins\SitesManager\SiteContentDetection\SiteContentDetectionAbstract;
16 /**
17 * This class provides detection functions for specific content on a site. It can be used to easily detect the
18 * presence of known third party code.
19 *
20 * Note: Calling the `detectContent()` method will create a HTTP request to the site to retrieve data, only the main site URL
21 * will be checked
22 *
23 * Usage:
24 *
25 * $contentDetector = new SiteContentDetector();
26 * $contentDetector->detectContent([GoogleAnalytics3::getId()]);
27 * if ($contentDetector->ga3) {
28 * // site is using GA3
29 * }
30 *
31 * @api
32 */
33 class SiteContentDetector
34 {
35 /**
36 * @var array<string, array<string, SiteContentDetectionAbstract>>
37 */
38 public $detectedContent = [SiteContentDetectionAbstract::TYPE_TRACKER => [], SiteContentDetectionAbstract::TYPE_CMS => [], SiteContentDetectionAbstract::TYPE_JS_FRAMEWORK => [], SiteContentDetectionAbstract::TYPE_CONSENT_MANAGER => [], SiteContentDetectionAbstract::TYPE_JS_CRASH_ANALYTICS => [], SiteContentDetectionAbstract::TYPE_OTHER => []];
39 public $connectedConsentManagers = [];
40 private $siteResponse = ['data' => '', 'headers' => []];
41 /** @var Lazy */
42 private $cache;
43 public function __construct(?Lazy $cache = null)
44 {
45 if ($cache === null) {
46 $this->cache = \Piwik\Cache::getLazyCache();
47 } else {
48 $this->cache = $cache;
49 }
50 }
51 /**
52 * @return array<string, SiteContentDetectionAbstract[]>
53 */
54 public static function getSiteContentDetectionsByType() : array
55 {
56 $instancesByType = [];
57 $classes = self::getAllSiteContentDetectionClasses();
58 foreach ($classes as $className) {
59 $instancesByType[$className::getContentType()][] = StaticContainer::get($className);
60 }
61 return $instancesByType;
62 }
63 /**
64 * Returns the site content detection object with the provided id, or null if it can't be found
65 *
66 */
67 public function getSiteContentDetectionById(string $id) : ?SiteContentDetectionAbstract
68 {
69 $classes = $this->getAllSiteContentDetectionClasses();
70 foreach ($classes as $className) {
71 if ($className::getId() === $id) {
72 return StaticContainer::get($className);
73 }
74 }
75 return null;
76 }
77 /**
78 * @return string[]
79 */
80 protected static function getAllSiteContentDetectionClasses() : array
81 {
82 return \Piwik\Plugin\Manager::getInstance()->findMultipleComponents('SiteContentDetection', SiteContentDetectionAbstract::class);
83 }
84 /**
85 * Reset the detections
86 *
87 */
88 private function resetDetections() : void
89 {
90 $this->detectedContent = [SiteContentDetectionAbstract::TYPE_TRACKER => [], SiteContentDetectionAbstract::TYPE_CMS => [], SiteContentDetectionAbstract::TYPE_JS_FRAMEWORK => [], SiteContentDetectionAbstract::TYPE_CONSENT_MANAGER => [], SiteContentDetectionAbstract::TYPE_JS_CRASH_ANALYTICS => [], SiteContentDetectionAbstract::TYPE_OTHER => []];
91 $this->connectedConsentManagers = [];
92 }
93 /**
94 * This will query the site and populate the class properties with
95 * the details of the detected content
96 *
97 * @param array $detectContent Array of content type for which to check, defaults to all, limiting this list
98 * will speed up the detection check.
99 * Allowed values are:
100 * * empty array - to run all detections
101 * * an array containing ids of detections, e.g. Wordpress::getId() or any of the
102 * type constants, e.g. SiteContentDetectionAbstract::TYPE_TRACKER
103 * @param ?int $idSite Override the site ID, will use the site from the current request if null
104 * @param ?array $siteResponse String containing the site data to search, if blank then data will be retrieved
105 * from the current request site via an http request
106 * @param int $timeOut How long to wait for the site to response, defaults to 5 seconds
107 */
108 public function detectContent(array $detectContent = [], ?int $idSite = null, ?array $siteResponse = null, int $timeOut = 5) : void
109 {
110 $this->resetDetections();
111 // If site data was passed in, then just run the detection checks against it and return.
112 if ($siteResponse) {
113 $this->siteResponse = $siteResponse;
114 $this->detectionChecks($detectContent);
115 return;
116 }
117 // Get the site id from the request object if not explicitly passed
118 if ($idSite === null) {
119 $idSite = \Piwik\Request::fromRequest()->getIntegerParameter('idSite', 0);
120 if (!$idSite) {
121 return;
122 }
123 }
124 $url = \Piwik\Site::getMainUrlFor($idSite);
125 // Check and load previously cached site content detection data if it exists
126 $cacheKey = 'SiteContentDetection_' . md5($url);
127 $siteContentDetectionCache = $this->cache->fetch($cacheKey);
128 if ($siteContentDetectionCache !== \false) {
129 if ($this->checkCacheHasRequiredProperties($detectContent, $siteContentDetectionCache)) {
130 $this->detectedContent = $siteContentDetectionCache['detectedContent'];
131 $this->connectedConsentManagers = $siteContentDetectionCache['connectedConsentManagers'];
132 return;
133 }
134 }
135 // No cache hit, no passed data, so make a request for the site content
136 $siteResponse = $this->requestSiteResponse($url, $timeOut);
137 // Abort if still no site data
138 if (empty($siteResponse['data'])) {
139 return;
140 }
141 $this->siteResponse = $siteResponse;
142 // We now have site data to analyze, so run the detection checks
143 $this->detectionChecks($detectContent);
144 // A request was made to get this data and it isn't currently cached, so write it to the cache now
145 $cacheLife = 60 * 60 * 24 * 7;
146 $this->saveToCache($cacheKey, $cacheLife);
147 }
148 /**
149 * Returns if the detection with the provided id was detected or not
150 *
151 * Note: self::detectContent needs to be called before.
152 *
153 */
154 public function wasDetected(string $detectionClassId) : bool
155 {
156 foreach ($this->detectedContent as $type => $detectedClassIds) {
157 if (array_key_exists($detectionClassId, $detectedClassIds)) {
158 return $detectedClassIds[$detectionClassId] ?? \false;
159 }
160 }
161 return \false;
162 }
163 /**
164 * Returns an array containing ids of all detected detections of the given type
165 *
166 * @param int $type One of the SiteContentDetectionAbstract::TYPE_* constants
167 * @return array
168 */
169 public function getDetectsByType(int $type) : array
170 {
171 $detected = [];
172 foreach ($this->detectedContent[$type] as $objId => $wasDetected) {
173 if (\true === $wasDetected) {
174 $detected[] = $objId;
175 }
176 }
177 return $detected;
178 }
179 /**
180 * Checks that all required detections are in the cache array
181 *
182 * @param array $detectContent
183 * @param array $cache
184 *
185 */
186 private function checkCacheHasRequiredProperties(array $detectContent, array $cache) : bool
187 {
188 if (empty($detectContent)) {
189 foreach (self::getSiteContentDetectionsByType() as $type => $entries) {
190 foreach ($entries as $entry) {
191 if (!isset($cache['detectedContent'][$type][$entry::getId()])) {
192 return \false;
193 // random detection missing
194 }
195 }
196 }
197 return \true;
198 }
199 foreach ($detectContent as $requestedDetection) {
200 if (is_string($requestedDetection)) {
201 // specific detection
202 $detectionObj = $this->getSiteContentDetectionById($requestedDetection);
203 if (null !== $detectionObj && !isset($cache['detectedContent'][$detectionObj::getContentType()][$detectionObj::getId()])) {
204 return \false;
205 // specific detection was run before
206 }
207 } elseif (is_int($requestedDetection)) {
208 // detection type requested
209 $detectionsByType = self::getSiteContentDetectionsByType();
210 if (isset($detectionsByType[$requestedDetection])) {
211 foreach ($detectionsByType[$requestedDetection] as $detectionObj) {
212 if (!isset($cache['detectedContent'][$requestedDetection][$detectionObj::getId()])) {
213 return \false;
214 // random detection missing
215 }
216 }
217 }
218 }
219 }
220 return \true;
221 }
222 /**
223 * Save data to the cache
224 *
225 *
226 */
227 private function saveToCache(string $cacheKey, int $cacheLife) : void
228 {
229 $cacheData = ['detectedContent' => [], 'connectedConsentManagers' => []];
230 // Load any existing cached values
231 $siteContentDetectionCache = $this->cache->fetch($cacheKey);
232 if (is_array($siteContentDetectionCache)) {
233 $cacheData = $siteContentDetectionCache;
234 }
235 foreach ($this->detectedContent as $type => $detections) {
236 if (!isset($cacheData['detectedContent'][$type])) {
237 $cacheData['detectedContent'][$type] = [];
238 }
239 foreach ($detections as $detectionId => $wasDetected) {
240 if (null !== $wasDetected) {
241 $cacheData['detectedContent'][$type][$detectionId] = $wasDetected;
242 }
243 }
244 }
245 $cacheData['connectedConsentManagers'] = array_merge($cacheData['connectedConsentManagers'], $this->connectedConsentManagers);
246 $this->cache->save($cacheKey, $cacheData, $cacheLife);
247 }
248 /**
249 * Run various detection checks for site content
250 *
251 * @param array $detectContent Array of detection types used to filter the checks that are run
252 *
253 */
254 private function detectionChecks(array $detectContent) : void
255 {
256 $detections = $this->getSiteContentDetectionsByType();
257 foreach ($detections as $type => $typeDetections) {
258 foreach ($typeDetections as $typeDetection) {
259 $this->detectedContent[$type][$typeDetection::getId()] = null;
260 if (in_array($type, $detectContent) || in_array($typeDetection::getId(), $detectContent) || empty($detectContent)) {
261 $this->detectedContent[$type][$typeDetection::getId()] = \false;
262 if ($typeDetection->isDetected($this->siteResponse['data'], $this->siteResponse['headers'])) {
263 if ($typeDetection instanceof ConsentManagerDetectionAbstract && $typeDetection->checkIsConnected($this->siteResponse['data'], $this->siteResponse['headers'])) {
264 $this->connectedConsentManagers[] = $typeDetection::getId();
265 }
266 $this->detectedContent[$type][$typeDetection::getId()] = \true;
267 }
268 }
269 }
270 }
271 }
272 /**
273 * Retrieve data from the specified site using an HTTP request
274 *
275 *
276 * @return array
277 */
278 private function requestSiteResponse(string $url, int $timeOut) : array
279 {
280 if (!$url) {
281 return [];
282 }
283 // If internet features are disabled, we don't try to fetch any site content
284 if (0 === (int) GeneralConfig::getConfigValue('enable_internet_features')) {
285 return [];
286 }
287 $siteData = [];
288 try {
289 $siteData = \Piwik\Http::sendHttpRequestBy(\Piwik\Http::getTransportMethod(), $url, $timeOut, null, null, null, 0, \false, \true, \false, \true);
290 } catch (\Exception $e) {
291 }
292 return $siteData;
293 }
294 /**
295 * Return an array of consent manager definitions which can be used to detect their presence on the site and show
296 * the associated guide links
297 *
298 * Note: This list is also used to display the known / supported consent managers on the "Ask for Consent" page
299 * For adding a new consent manager to this page, it needs to be added here. If a consent manager can't be detected
300 * automatically, simply leave the detections empty.
301 *
302 * @return array[]
303 */
304 public static function getKnownConsentManagers() : array
305 {
306 $detections = self::getSiteContentDetectionsByType();
307 $cmDetections = $detections[SiteContentDetectionAbstract::TYPE_CONSENT_MANAGER];
308 $consentManagers = [];
309 foreach ($cmDetections as $detection) {
310 $consentManagers[$detection::getId()] = ['name' => $detection::getName(), 'instructionUrl' => $detection::getInstructionUrl()];
311 }
312 return $consentManagers;
313 }
314 }
315