001/* 002 * Copyright 2025 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.linkdirectory.tracking; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Map; 021 022import org.apache.avalon.framework.service.ServiceException; 023import org.apache.avalon.framework.service.ServiceManager; 024import org.apache.avalon.framework.service.Serviceable; 025import org.apache.commons.lang3.StringUtils; 026 027import org.ametys.cms.tag.Tag; 028import org.ametys.core.userpref.UserPreferencesException; 029import org.ametys.core.userpref.UserPreferencesManager; 030import org.ametys.core.util.I18nUtils; 031import org.ametys.plugins.linkdirectory.DirectoryHelper; 032import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper; 033import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ConfiguredThemesInputData; 034import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ThemesInputData; 035import org.ametys.plugins.linkdirectory.theme.ThemesDAO; 036import org.ametys.plugins.repository.AmetysObjectIterable; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 039import org.ametys.plugins.webanalytics.matomo.tracking.AbstractMatomoEventTrackingProvider; 040import org.ametys.web.repository.page.ModifiableZoneItem; 041import org.ametys.web.repository.site.Site; 042import org.ametys.web.repository.sitemap.Sitemap; 043import org.ametys.web.userpref.FOUserPreferencesConstants; 044 045/** 046 * The Matomo tracking provider for the link directory 047 */ 048public class LinkDirectoryMatomoTrackingProvider extends AbstractMatomoEventTrackingProvider implements Serviceable 049{ 050 /** The user preferences manager. */ 051 protected UserPreferencesManager _userPreferencesManager; 052 053 /** The link directory themes input data helper */ 054 protected LinkDirectoryThemesInputDataHelper _linkDirectoryThemesInputDataHelper; 055 056 /** Themes DAO */ 057 protected ThemesDAO _themesDAO; 058 059 /** The i18n utils */ 060 protected I18nUtils _i18nUtils; 061 062 /** The ametys object resolver */ 063 protected AmetysObjectResolver _resolver; 064 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 _userPreferencesManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE + ".FO"); 068 _linkDirectoryThemesInputDataHelper = (LinkDirectoryThemesInputDataHelper) manager.lookup(LinkDirectoryThemesInputDataHelper.ROLE); 069 _themesDAO = (ThemesDAO) manager.lookup(ThemesDAO.ROLE); 070 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 071 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 072 } 073 074 @Override 075 protected List<MatomoEvent> getEvents(Site site) 076 { 077 List<MatomoEvent> requests = new ArrayList<>(); 078 079 AmetysObjectIterable<Sitemap> sitemaps = site.getSitemaps(); 080 for (Sitemap sitemap : sitemaps) 081 { 082 requests.addAll(_getConfigurableLinkServiceEvents(site, sitemap)); 083 requests.addAll(_getConfigurableLinkInputDataEvents(site, sitemap)); 084 } 085 086 return requests; 087 } 088 089 private List<MatomoEvent> _getConfigurableLinkServiceEvents(Site site, Sitemap sitemap) 090 { 091 List<MatomoEvent> requests = new ArrayList<>(); 092 093 String siteName = site.getName(); 094 String language = sitemap.getSitemapName(); 095 096 for (ModifiableZoneItem zoneItem : _getLinkDirectoryServiceZoneItems(siteName)) 097 { 098 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 099 String[] themeIds = serviceParameters.getValue("themes"); 100 for (String themeId : themeIds) 101 { 102 try 103 { 104 String themeLabel = _getThemeLabel(themeId, siteName, language); 105 if (StringUtils.isNotBlank(themeLabel)) 106 { 107 requests.add(new MatomoEvent( 108 language + "/" + themeLabel, 109 _getNbUsersWithCustomLinks(siteName, language, zoneItem.getId()) 110 )); 111 } 112 } 113 catch (UserPreferencesException e) 114 { 115 getLogger().error("An error occurred getting user preferences for link directories. No request is sent to Matomo.", e); 116 } 117 } 118 119 } 120 121 return requests; 122 } 123 124 private AmetysObjectIterable<ModifiableZoneItem> _getLinkDirectoryServiceZoneItems(String siteName) 125 { 126 String xpathQuery = "//element(" + siteName + ", ametys:site)//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.plugins.linkdirectory.DirectoryService' and ametys:service_parameters/@ametys:configurable = 'true']"; 127 return _resolver.query(xpathQuery); 128 } 129 130 private List<MatomoEvent> _getConfigurableLinkInputDataEvents(Site site, Sitemap sitemap) 131 { 132 List<MatomoEvent> requests = new ArrayList<>(); 133 134 String siteName = site.getName(); 135 String language = sitemap.getSitemapName(); 136 137 ConfiguredThemesInputData configuredThemes = _linkDirectoryThemesInputDataHelper.getThemesInputData(site.getSkinId()); 138 String error = configuredThemes.error(); 139 if (StringUtils.isBlank(error) && configuredThemes.themesInputDatas() != null) 140 { 141 for (ThemesInputData themesInputData : configuredThemes.themesInputDatas()) 142 { 143 try 144 { 145 if (themesInputData.configurable()) // Send only configurable themes 146 { 147 String themeId = themesInputData.themes() 148 .stream() 149 .filter(t -> t.get("lang") == null || t.get("lang").equals(language)) 150 .map(t -> t.get("id")) 151 .findFirst() 152 .orElse(null); 153 154 String themeLabel = _getThemeLabel(themeId, siteName, language); 155 if (StringUtils.isNotBlank(themeLabel)) 156 { 157 requests.add(new MatomoEvent( 158 language + "/" + themeLabel, 159 _getNbUsersWithCustomLinks(siteName, language, themesInputData.id()) 160 )); 161 } 162 } 163 } 164 catch (UserPreferencesException e) 165 { 166 getLogger().error("An error occurred getting user preferences for link directories. No request is sent to Matomo.", e); 167 } 168 } 169 } 170 171 return requests; 172 } 173 174 private String _getThemeLabel(String themeId, String siteName, String language) 175 { 176 Tag theme = StringUtils.isNotBlank(themeId) 177 ? _themesDAO.getTag(themeId, Map.of("language", language, "siteName", siteName)) 178 : null; 179 180 return theme != null ? _i18nUtils.translate(theme.getTitle(), language) : null; 181 } 182 183 private long _getNbUsersWithCustomLinks(String siteName, String language, String thematicId) throws UserPreferencesException 184 { 185 String storageContext = siteName + "/" + language + "/" + thematicId; 186 Map<String, String> contextVars = Map.of( 187 FOUserPreferencesConstants.CONTEXT_VAR_SITENAME, siteName, 188 FOUserPreferencesConstants.CONTEXT_VAR_LANGUAGE, language 189 ); 190 191 return _userPreferencesManager.getAllUnTypedUserPrefs(storageContext, contextVars) 192 .values() 193 .stream() 194 .filter(this::_hasCustomLinks) 195 .count(); 196 } 197 198 private boolean _hasCustomLinks(Map<String, String> userPrefs) 199 { 200 String orderedLinksAsString = userPrefs.get(DirectoryHelper.USER_PREF_ORDERED_LINK_ATTR); 201 String[] orderedLinks = StringUtils.split(orderedLinksAsString, ","); 202 203 String hiddenLinksAsString = userPrefs.get(DirectoryHelper.USER_PREF_HIDDEN_LINK_ATTR); 204 String[] hiddenLinks = StringUtils.split(hiddenLinksAsString, ","); 205 206 // Links are customized if there is some ordered links or some hidden links in the users preferences 207 return orderedLinks != null && orderedLinks.length > 0 || hiddenLinks != null && hiddenLinks.length > 0; 208 } 209}