*/ public function registerEvents(): array { return [ 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles', 'PrivacyManager.deleteLogsOlderThan' => 'deleteLogsOlderThan', 'PrivacyManager.deleteDataSubjectsForDeletedSites' => 'deleteDataSubjectsForDeletedSites', 'Tracker.isBotRequest' => 'isBotRequest', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', 'Metrics.getEvolutionUnit' => 'getEvolutionUnit', 'Metrics.getDefaultMetricTranslations' => 'addMetricTranslations', 'Metrics.getDefaultMetricDocumentationTranslations' => 'addMetricDocumentationTranslations', 'Metrics.getDefaultMetricSemanticTypes' => 'addMetricSemanticTypes', 'API.addGlossaryItems' => 'addGlossaryItems', ]; } /** * @return void */ public function install() { (new BotRequestsDao())->createTable(); } /** * @return void */ public function uninstall() { (new BotRequestsDao())->dropTable(); } public function deleteLogsOlderThan(Date $dateUpperLimit): void { (new BotRequestsDao())->deleteOldRecords($dateUpperLimit); } /** * @param array $result */ public function deleteDataSubjectsForDeletedSites(array &$result): void { $allExistingIdSites = API::getInstance()->getAllSitesId(); $allExistingIdSites = array_map('intval', $allExistingIdSites); $maxIdSite = max($allExistingIdSites); if (empty($maxIdSite)) { return; } $dao = new BotRequestsDao(); $idSitesInTable = $dao->getDistinctIdSitesInTable($maxIdSite); $idSitesNoLongerExisting = array_diff($idSitesInTable, $allExistingIdSites); if (count($idSitesNoLongerExisting) > 0) { $result[$dao::getTableName()] = $dao->deleteRecordsForIdSites($idSitesNoLongerExisting); } } /** * @todo Remove, once Device Detector is able to detect all known ai bots */ public function isBotRequest(bool &$isBot, Request $request): void { $botDetector = new BotDetector($request->getUserAgent()); if ($botDetector->isBot()) { $isBot = true; } } public function getEvolutionUnit(?string &$unit, string $column): void { if ($column === Metrics::METRIC_AI_CHATBOTS_CLICK_THROUGH_RATE) { $unit = '%'; } } /** * @param array $translations */ public function addMetricTranslations(array &$translations): void { $translations = array_merge($translations, BotMetrics::getMetricTranslations()); // Register a default name for the generic 'requests' column used by the new content-URL // reports. Without this the Glossary majority heuristic renders name = null for the // 'requests' metric because three reports expose it under different per-report scoped // documentation strings, leaving no single translation with a majority. Registering the // default here mirrors the documentation registration in addMetricDocumentationTranslations. if (!isset($translations[Metrics::COLUMN_REQUESTS])) { $translations[Metrics::COLUMN_REQUESTS] = Piwik::translate('BotTracking_ColumnRequests'); } } /** * @param array $translations */ public function addMetricDocumentationTranslations(array &$translations): void { $translations = array_merge($translations, BotMetrics::getMetricDocumentation()); // Register a default documentation for the generic 'requests' column used by the // new content-URL reports. Without this default the Glossary majority heuristic // (array_sum - max < max) would not pick a winner because the three reports that // expose 'requests' each use a different documentation string (generic, page-scoped, // document-scoped), leaving no single value with a majority. Setting a default here // ensures the metric always appears in the glossary with the generic tooltip. if (!isset($translations[Metrics::COLUMN_REQUESTS])) { $translations[Metrics::COLUMN_REQUESTS] = Piwik::translate('BotTracking_ColumnRequestsDocumentation'); } } /** * @param array $types */ public function addMetricSemanticTypes(array &$types): void { $types = array_merge($types, BotMetrics::getMetricSemanticTypes()); } /** * @param array>}> $glossaryItems */ public function addGlossaryItems(array &$glossaryItems): void { Piwik::checkUserHasSomeViewAccess(); $category = Piwik::translate('General_AIAssistants'); foreach (AIChatbotsRealTimeWidgets::getAllWidgets() as $widget) { $glossaryItems['reports']['entries'][] = [ 'name' => sprintf('%s (%s)', Piwik::translate($widget['name']), $category), 'documentation' => Piwik::translate($widget['documentation']), ]; } } /** * @param string[] $translationKeys * @return void */ public function getClientSideTranslationKeys(&$translationKeys) { $translationKeys[] = 'BotTracking_DetectingYourSite'; $translationKeys[] = 'BotTracking_SiteWithoutDataBackToMatomo'; $translationKeys[] = 'BotTracking_SiteWithoutDataChooseTrackingMethod'; $translationKeys[] = 'BotTracking_SiteWithoutDataChooseTrackingMethodPreamble1'; $translationKeys[] = 'BotTracking_SiteWithoutDataChooseTrackingMethodPreamble2'; $translationKeys[] = 'BotTracking_SiteWithoutDataInstallWithX'; $translationKeys[] = 'BotTracking_SiteWithoutDataNotYetReady'; $translationKeys[] = 'BotTracking_SiteWithoutDataOtherInstallMethods'; $translationKeys[] = 'BotTracking_SiteWithoutDataOtherInstallMethodsIntro'; $translationKeys[] = 'BotTracking_SiteWithoutDataInstallWithXRecommendation'; $translationKeys[] = 'BotTracking_SiteWithoutDataRecommendationText'; $translationKeys[] = 'General_ErrorRequest'; $translationKeys[] = 'General_Refresh'; $translationKeys[] = 'Mobile_NavigationBack'; $translationKeys[] = 'BotTracking_NoRecentAIBotRequests'; } /** * @param string[] $stylesheets * @return void */ public function getStylesheetFiles(&$stylesheets) { $stylesheets[] = "plugins/BotTracking/stylesheets/BotTracking.less"; } }