diff --git a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java index 958e82179fda774cc75c16bca6a2b05d05f29103..cced372b598d7e5caaa9c88f4a7ce4540ab5e1ce 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java @@ -372,7 +372,9 @@ public class BaseFullWebappController extends BasicController implements DTabs, // Add JS analytics code, e.g. for google analytics if (analyticsModule.isAnalyticsEnabled()) { AnalyticsSPI analyticsSPI = analyticsModule.getAnalyticsProvider(); - mainVc.contextPut("analytics",analyticsSPI.analyticsInitPageJavaScript()); + if(analyticsSPI != null) { + mainVc.contextPut("analytics",analyticsSPI.analyticsInitPageJavaScript()); + } } // content panel diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java index d5b2f8f5862ac10da82b95a190d79c4447053879..d3ee2bfb1b8f42f784da1cb6dd150db96cd0ceb7 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java @@ -26,6 +26,7 @@ package org.olat.core.commons.modules.bc.commands; +import java.io.IOException; import java.io.InputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,6 +41,8 @@ import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.NotFoundMediaResource; import org.olat.core.gui.media.StringMediaResource; import org.olat.core.gui.translator.Translator; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.CoreLoggingResourceable; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.FileUtils; @@ -52,6 +55,8 @@ import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; import org.olat.core.util.vfs.meta.MetaInfo; public class CmdServeResource implements FolderCommand { + + private static final OLog log = Tracing.createLoggerFor(CmdServeResource.class); private static final String DEFAULT_ENCODING = "iso-8859-1"; private static final Pattern PATTERN_ENCTYPE = Pattern.compile("<meta.*charset=([^\"]*)\"", Pattern.CASE_INSENSITIVE); @@ -102,51 +107,54 @@ public class CmdServeResource implements FolderCommand { if (path.toLowerCase().endsWith(".html") || path.toLowerCase().endsWith(".htm")) { // set the http content-type and the encoding // try to load in iso-8859-1 - InputStream is = vfsfile.getInputStream(); - if(is == null) { - mr = new NotFoundMediaResource(); - } else { - String page = FileUtils.load(is, DEFAULT_ENCODING); - // search for the <meta content="text/html; charset=utf-8" - // http-equiv="Content-Type" /> tag - // if none found, assume iso-8859-1 - String enc = DEFAULT_ENCODING; - boolean useLoaded = false; - // <meta.*charset=([^"]*)" - Matcher m = PATTERN_ENCTYPE.matcher(page); - boolean found = m.find(); - if (found) { - String htmlcharset = m.group(1); - enc = htmlcharset; - if (htmlcharset.equals(DEFAULT_ENCODING)) { - useLoaded = true; - } + try(InputStream is = vfsfile.getInputStream()) { + if(is == null) { + mr = new NotFoundMediaResource(); } else { - useLoaded = true; - } - // set the new encoding to remember for any following .js file loads - g_encoding = enc; - if (useLoaded) { - StringMediaResource smr = new StringMediaResource(); - String mimetype = forceDownload ? VFSMediaResource.MIME_TYPE_FORCE_DOWNLOAD : "text/html;charset=" + enc; - smr.setContentType(mimetype); - smr.setEncoding(enc); - smr.setData(page); - if(forceDownload) { - smr.setDownloadable(true, vfsfile.getName()); + String page = FileUtils.load(is, DEFAULT_ENCODING); + // search for the <meta content="text/html; charset=utf-8" + // http-equiv="Content-Type" /> tag + // if none found, assume iso-8859-1 + String enc = DEFAULT_ENCODING; + boolean useLoaded = false; + // <meta.*charset=([^"]*)" + Matcher m = PATTERN_ENCTYPE.matcher(page); + boolean found = m.find(); + if (found) { + String htmlcharset = m.group(1); + enc = htmlcharset; + if (htmlcharset.equals(DEFAULT_ENCODING)) { + useLoaded = true; + } + } else { + useLoaded = true; } - mr = smr; - } else { - // found a new charset other than iso-8859-1 -> let it load again - // as bytes (so we do not need to convert to string and back - // again) - VFSMediaResource vmr = new VFSMediaResource(vfsfile); - vmr.setEncoding(enc); - if(forceDownload) { - vmr.setDownloadable(true); + // set the new encoding to remember for any following .js file loads + g_encoding = enc; + if (useLoaded) { + StringMediaResource smr = new StringMediaResource(); + String mimetype = forceDownload ? VFSMediaResource.MIME_TYPE_FORCE_DOWNLOAD : "text/html;charset=" + enc; + smr.setContentType(mimetype); + smr.setEncoding(enc); + smr.setData(page); + if(forceDownload) { + smr.setDownloadable(true, vfsfile.getName()); + } + mr = smr; + } else { + // found a new charset other than iso-8859-1 -> let it load again + // as bytes (so we do not need to convert to string and back + // again) + VFSMediaResource vmr = new VFSMediaResource(vfsfile); + vmr.setEncoding(enc); + if(forceDownload) { + vmr.setDownloadable(true); + } + mr = vmr; } - mr = vmr; } + } catch (IOException e) { + log.error("", e); } } else if (path.endsWith(".js")) { // a javascript library VFSMediaResource vmr = new VFSMediaResource(vfsfile); diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java index f84e04866a90da08556ca9e521d012194b0cd2b9..697d570fe3a645294081d7573a13fd895121632d 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java +++ b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java @@ -34,11 +34,14 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.commons.modules.bc.FolderLoggingAction; import org.olat.core.commons.modules.bc.FolderRunController; import org.olat.core.commons.modules.bc.commands.FolderCommandFactory; import org.olat.core.commons.modules.bc.comparators.LockComparator; +import org.olat.core.commons.services.analytics.AnalyticsModule; +import org.olat.core.commons.services.analytics.AnalyticsSPI; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.AbstractComponent; import org.olat.core.gui.components.ComponentRenderer; @@ -99,6 +102,8 @@ public class FolderComponent extends AbstractComponent { private VFSItemExcludePrefixFilter exclFilter; private CustomLinkTreeModel customLinkTreeModel; private final VFSContainer externContainerForCopy; + + private final AnalyticsSPI analyticsSpi; /** * Wraps the folder module as a component. @@ -138,6 +143,8 @@ public class FolderComponent extends AbstractComponent { setCurrentContainerPath("/"); dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); + + analyticsSpi = CoreSpringFactory.getImpl(AnalyticsModule.class).getAnalyticsProvider(); } @Override @@ -200,6 +207,10 @@ public class FolderComponent extends AbstractComponent { public void setCanMail(boolean canMail) { this.canMail = canMail; } + + public AnalyticsSPI getAnalyticsSPI() { + return analyticsSpi; + } /** * Sorts the bc folder components table diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java index 55a441ce039cb9d2cc412b0f170b287813e2f07a..96690738428976cc75fada9f6af26ff82f225b86 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java +++ b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java @@ -267,6 +267,11 @@ public class ListRenderer { } else { sb.append(" target=\"_blank\""); } + if(fc.getAnalyticsSPI() != null) { + sb.append(" onclick=\""); + fc.getAnalyticsSPI().analyticsCountOnclickJavaScript(sb); + sb.append("\""); + } } sb.append(">"); diff --git a/src/main/java/org/olat/core/commons/services/analytics/AnalyticsSPI.java b/src/main/java/org/olat/core/commons/services/analytics/AnalyticsSPI.java index d8d01855fc762a5cf6139104a7dcea48ecb80e11..914219f14e2799a4945fbe0c132dacee45de4f49 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/AnalyticsSPI.java +++ b/src/main/java/org/olat/core/commons/services/analytics/AnalyticsSPI.java @@ -22,6 +22,7 @@ package org.olat.core.commons.services.analytics; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.render.StringOutput; /** * The AnalyticsSPI offers methods to analyse site usage and patterns based on a @@ -84,5 +85,12 @@ public interface AnalyticsSPI { * The location as an URL part */ public void analyticsCountPageJavaScript(StringBuilder sb, String title, String url); + + /** + * The script can rely on the download attribute. + * + * @param sb The string builder + */ + public void analyticsCountOnclickJavaScript(StringOutput sb); } diff --git a/src/main/java/org/olat/core/commons/services/analytics/spi/GoogleAnalyticsSPI.java b/src/main/java/org/olat/core/commons/services/analytics/spi/GoogleAnalyticsSPI.java index 268482fb3abd32d7a981475a35b27a79ede766e2..f70f86046734174b2054c0e9e6fc85b6cc3c46ba 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/spi/GoogleAnalyticsSPI.java +++ b/src/main/java/org/olat/core/commons/services/analytics/spi/GoogleAnalyticsSPI.java @@ -25,9 +25,11 @@ import org.olat.core.configuration.AbstractSpringModule; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.render.StringOutput; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** @@ -54,6 +56,7 @@ public class GoogleAnalyticsSPI extends AbstractSpringModule implements Analytic * Constructor, used by spring. Implements the module interface to store configuration * @param coordinatorManager */ + @Autowired public GoogleAnalyticsSPI(CoordinatorManager coordinatorManager) { super(coordinatorManager); } @@ -140,6 +143,14 @@ public class GoogleAnalyticsSPI extends AbstractSpringModule implements Analytic } } + @Override + public void analyticsCountOnclickJavaScript(StringOutput sb) { + if (isValid()) { + // Currently only send page views with url and title. No support for tags so far + sb.append("ga('send', 'pageview', { page: o_info.businessPath, title: jQuery(this).attr('download') });"); + } + } + /** * Helper method to build the tracker page initialization code. Does not * change often, thus store in variable for reuse diff --git a/src/main/java/org/olat/core/commons/services/analytics/spi/MatomoSPI.java b/src/main/java/org/olat/core/commons/services/analytics/spi/MatomoSPI.java new file mode 100644 index 0000000000000000000000000000000000000000..a0cff536836862e3ac0ccac67b17865da0610f9b --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/analytics/spi/MatomoSPI.java @@ -0,0 +1,170 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.analytics.spi; + +import org.olat.core.commons.services.analytics.AnalyticsSPI; +import org.olat.core.commons.services.analytics.ui.MatomoConfigFormController; +import org.olat.core.configuration.AbstractSpringModule; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 25 janv. 2019<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class MatomoSPI extends AbstractSpringModule implements AnalyticsSPI { + + private static final String TRACKER_CODE = "matomoTrackerCode"; + private static final String SITE_ID = "matomoSiteId"; + private static final String TRACKER_URL = "matomoTrackerurl"; + + private String siteId; + private String trackerUrl; + private String trackerJsCode; + + @Autowired + public MatomoSPI(CoordinatorManager coordinatorManager) { + super(coordinatorManager); + } + + @Override + public void init() { + updateProperties(); + } + + @Override + protected void initFromChangedProperties() { + updateProperties(); + } + + private void updateProperties() { + String trackerObj = getStringPropertyValue(TRACKER_CODE, true); + if (StringHelper.containsNonWhitespace(trackerObj)) { + trackerJsCode = trackerObj; + } + + String siteIdObj = getStringPropertyValue(SITE_ID, true); + if (StringHelper.containsNonWhitespace(siteIdObj)) { + siteId = siteIdObj; + } + + String trackerUrlObj = getStringPropertyValue(TRACKER_URL, true); + if (StringHelper.containsNonWhitespace(trackerUrlObj)) { + trackerUrl = trackerUrlObj; + } + } + + @Override + public String getId() { + return "matomo"; + } + + @Override + public String getName() { + return "Matomo (Piwik)"; + } + + public String getTrackerJsCode() { + return trackerJsCode; + } + + public void setTrackerJsCode(String trackerJsCode) { + this.trackerJsCode = trackerJsCode; + setStringProperty(TRACKER_CODE, trackerJsCode, true); + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + setStringProperty(SITE_ID, siteId, true); + } + + public String getTrackerUrl() { + return trackerUrl; + } + + public void setTrackerUrl(String trackerUrl) { + this.trackerUrl = trackerUrl; + setStringProperty(TRACKER_URL, trackerUrl, true); + } + + @Override + public Controller createAdminController(UserRequest ureq, WindowControl wControl) { + return new MatomoConfigFormController(ureq, wControl); + } + + @Override + public boolean isValid() { + return StringHelper.isLong(siteId) && StringHelper.containsNonWhitespace(trackerUrl); + } + + @Override + public String analyticsInitPageJavaScript() { + StringBuilder sb = new StringBuilder(); + if(isValid()) { + sb.append("var _paq = window._paq || [];\n") + .append("_paq.push(['trackPageView']);\n") + .append("_paq.push(['enableLinkTracking']);\n") + .append(" (function() {\n") + .append(" var u='").append(trackerUrl).append("';\n") + .append(" _paq.push(['setTrackerUrl', u+'matomo.php']);\n") + .append(" _paq.push(['setSiteId', '").append(siteId).append("']);\n") + .append(" var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];\n") + .append(" g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);\n") + .append(" })();\n"); + } + return sb.toString(); + } + + @Override + public void analyticsCountOnclickJavaScript(StringOutput sb) { + if(!isValid()) return; + + sb.append("try{") + .append("_paq.push(['setDocumentTitle', jQuery(this).attr('download')]);") + .append("_paq.push(['setCustomUrl', o_info.businessPath]);") + .append("_paq.push(['trackPageView']);") + .append("} catch(e) { if(window.console) console.log(e) }"); + } + + @Override + public void analyticsCountPageJavaScript(StringBuilder sb, String title, String url) { + if(!isValid()) return; + + sb.append("try{\n") + .append("_paq.push([\"setDocumentTitle\", \"").append(Formatter.escapeDoubleQuotes(title)).append("\"]);\n") + .append("_paq.push([\"setCustomUrl\", \"").append(url).append("\"]);\n") + .append("_paq.push([\"trackPageView\"]);\n") + .append("} catch(e) { if(window.console) console.log(e) }"); + } +} diff --git a/src/main/java/org/olat/core/commons/services/analytics/ui/GoogleAnalyticsConfigFormController.java b/src/main/java/org/olat/core/commons/services/analytics/ui/GoogleAnalyticsConfigFormController.java index 980a7378e27fc643df34a37463d0a73d2f484411..401f6989780adb2cdbbc386bb8fb8e60969c000b 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/ui/GoogleAnalyticsConfigFormController.java +++ b/src/main/java/org/olat/core/commons/services/analytics/ui/GoogleAnalyticsConfigFormController.java @@ -75,19 +75,19 @@ public class GoogleAnalyticsConfigFormController extends FormBasicController { FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); formLayout.add(buttonsCont); - uifactory.addFormSubmitButton("save", buttonsCont); uifactory.addFormResetButton("reset", "reset", buttonsCont); + uifactory.addFormSubmitButton("save", buttonsCont); } @Override protected boolean validateFormLogic(UserRequest ureq) { - boolean allOk = true; + boolean allOk = super.validateFormLogic(ureq); analyticsTrackingIdEl.clearError(); if(!StringHelper.containsNonWhitespace(analyticsTrackingIdEl.getValue())) { analyticsTrackingIdEl.setErrorKey("form.legende.mandatory", null); allOk &= false; } - return allOk & super.validateFormLogic(ureq); + return allOk; } @Override diff --git a/src/main/java/org/olat/core/commons/services/analytics/ui/MatomoConfigFormController.java b/src/main/java/org/olat/core/commons/services/analytics/ui/MatomoConfigFormController.java new file mode 100644 index 0000000000000000000000000000000000000000..ed58c9481ba4ca431273e8bb72930134d06bbd58 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/analytics/ui/MatomoConfigFormController.java @@ -0,0 +1,100 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.analytics.ui; + +import org.olat.core.commons.services.analytics.spi.MatomoSPI; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 25 janv. 2019<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class MatomoConfigFormController extends FormBasicController { + + private TextElement siteIdEl; + private TextElement trackerUrlEl; + + @Autowired + private MatomoSPI matomoModule; + + public MatomoConfigFormController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("matomo.title"); + setFormDescription("matomo.desc"); + + String siteId = matomoModule.getSiteId(); + siteIdEl = uifactory.addTextElement("matomo.site.id", 6, siteId, formLayout); + String trackerUrl = matomoModule.getTrackerUrl(); + trackerUrlEl = uifactory.addTextElement("matomo.tracker.url", 128, trackerUrl, flc); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonsCont); + uifactory.addFormSubmitButton("save", buttonsCont); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + siteIdEl.clearError(); + if(!StringHelper.containsNonWhitespace(siteIdEl.getValue())) { + siteIdEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else if(!StringHelper.isLong(siteIdEl.getValue())) { + siteIdEl.setErrorKey("form.error.nointeger", null); + allOk &= false; + } + + trackerUrlEl.clearError(); + if(!StringHelper.containsNonWhitespace(trackerUrlEl.getValue())) { + trackerUrlEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } + + @Override + protected void formOK(UserRequest ureq) { + matomoModule.setSiteId(siteIdEl.getValue()); + matomoModule.setTrackerUrl(trackerUrlEl.getValue()); + } + + @Override + protected void doDispose() { + // + } +} diff --git a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_de.properties index 4a37012ef8f7bb4a057922d577a9eeb597f8f28f..af50aabdcd72d43d39e21fdd23d6f3cbb6394105 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_de.properties @@ -1,12 +1,15 @@ admin.menu.title=Analytics admin.menu.title.alt=Analyse des Benutzeverhaltens - analytics.title=Analytics Modul analytics.desc=Wählen Sie einen optionalen Analytics Service aus um das Benutzerverhalten auf einem externen Analytics Server auszuwerten. analytics.privacy=Wir weisen Sie darauf hin, dass Sie als Betreiberin der Plattform verpflichtet sind Ihre Benutzer auf die Verwendung eines Analytics Services hinzuweisen. analytics.disabled=Analytics Module nicht verwenden analytics.service=Analytics Service - analytics.google.title=Google Analytics Konfiguration analytics.google.desc=Wenn Sie ein Google Analytics Konto besitzen können Sie hier die Google Tracking ID eingeben um detaillierte statistische und real-time Auswertungen über die Nutzung Ihrer OpenOLAT Installation zu erhalten. analytics.google.tracking.id=Tracking ID +matomo.title=Matomo (Piwik) +matomo.desc=Matomo Beschreibung +matomo.site.id=Site ID +matomo.tracker.code=JavaScript-Tracking-Code +matomo.tracker.url=Matomo URL diff --git a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_en.properties index 0ec1dfaaff97adcff6c7a6bf537afc0bdeea0631..5b080b39ed5d14ca6f9d37383efc2357050fd500 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_en.properties @@ -1,12 +1,15 @@ admin.menu.title=Analytics admin.menu.title.alt=User behavior analytics - analytics.title=Analytics module analytics.desc=Select an optional analytics service to analyse user behavior using an external analytics server. analytics.privacy=Please note that you as the operator of the platform are legally obliged to inform your users about the usage of google analytics. analytics.disabled=Disable analytics module analytics.service=Analytics service - analytics.google.title=Google analytics configuration analytics.google.desc=If you have a google analytics account you can configure your google Tracking ID to perform detailed statistical and realt-time data about the usage of your OpenOLAT installation. -analytics.google.tracking.id=Tracking ID \ No newline at end of file +analytics.google.tracking.id=Tracking ID +matomo.title=Matomo (Piwik) +matomo.desc=Matomo description +matomo.site.id=Site ID +matomo.tracker.code=JavaScript-Tracking-Code +matomo.tracker.url=Matomo URL \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_fr.properties index 1d9492b58bc2ab2ee824eddf81b6c29208b4bbad..44d06e96f3e8bf99fe8782a5066d260d4c7b610b 100644 --- a/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/core/commons/services/analytics/ui/_i18n/LocalStrings_fr.properties @@ -9,3 +9,8 @@ analytics.google.tracking.id=Tracking ID analytics.privacy=Nous vous rendons attentif au fait qu'en tant qu'op\u00E9rateur de la plateforme vous devez vous engag\u00E9 \u00E0 informer vos utilisateurs de l'emploi de services d'analyse d'audience. analytics.service=Service d'analyses analytics.title=Module d'analyse d'audience +matomo.title=Matomo (Piwik) +matomo.desc=Matomo description +matomo.site.id=Site ID +matomo.tracker.code=JavaScript-Tracking-Code +matomo.tracker.url=Matomo URL diff --git a/src/main/java/org/olat/core/servlets/HeadersFilter.java b/src/main/java/org/olat/core/servlets/HeadersFilter.java index b0df9635b57b369851ddd93a539256b6507aa371..b0ecc8c371101c8a2d2202d2951035efb003ce4c 100644 --- a/src/main/java/org/olat/core/servlets/HeadersFilter.java +++ b/src/main/java/org/olat/core/servlets/HeadersFilter.java @@ -20,7 +20,9 @@ import javax.servlet.http.HttpServletResponse; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.analytics.AnalyticsModule; +import org.olat.core.commons.services.analytics.AnalyticsSPI; import org.olat.core.commons.services.analytics.spi.GoogleAnalyticsSPI; +import org.olat.core.commons.services.analytics.spi.MatomoSPI; import org.olat.core.commons.services.csp.CSPModule; import org.olat.core.helpers.Settings; import org.olat.core.logging.OLog; @@ -167,7 +169,7 @@ public class HeadersFilter implements Filter { sb.append(" ").append(securityModule.getContentSecurityPolicyConnectSrc()); } - appendGoogleAnalyticsUrl(sb); + appendAnalyticsUrl(sb); appendEdusharingUrl(sb); sb.append(";"); } @@ -180,7 +182,7 @@ public class HeadersFilter implements Filter { } appendMathJaxUrl(sb); - appendGoogleAnalyticsUrl(sb); + appendAnalyticsUrl(sb); appendEdusharingUrl(sb); sb.append(";"); } @@ -191,7 +193,7 @@ public class HeadersFilter implements Filter { if(!standard && StringHelper.containsNonWhitespace(securityModule.getContentSecurityPolicyImgSrc())) { sb.append(" ").append(securityModule.getContentSecurityPolicyImgSrc()); } - appendGoogleAnalyticsUrl(sb); + appendAnalyticsUrl(sb); appendEdubaseUrl(sb); appendEdusharingUrl(sb); sb.append(";"); @@ -258,9 +260,17 @@ public class HeadersFilter implements Filter { } } - private void appendGoogleAnalyticsUrl(StringBuilder sb) { - if(analyticsModule != null && analyticsModule.getAnalyticsProvider() instanceof GoogleAnalyticsSPI) { - sb.append(" ").append("https://www.google-analytics.com"); + private void appendAnalyticsUrl(StringBuilder sb) { + if(analyticsModule != null) { + AnalyticsSPI spi = analyticsModule.getAnalyticsProvider(); + if(spi instanceof GoogleAnalyticsSPI) { + sb.append(" ").append("https://www.google-analytics.com"); + } else if(spi instanceof MatomoSPI) { + String trackerUrl = ((MatomoSPI)spi).getTrackerUrl(); + if(StringHelper.containsNonWhitespace(trackerUrl)) { + sb.append(" ").append(trackerUrl); + } + } } } diff --git a/src/main/java/org/olat/core/util/URIHelper.java b/src/main/java/org/olat/core/util/URIHelper.java deleted file mode 100644 index 999f4e0a1469c6adf31dd1a1abe57181688afae7..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/core/util/URIHelper.java +++ /dev/null @@ -1,203 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.core.util; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * With the URIHelper it is very simple to modify URL query parameters. - * <P> - * Initial Date: 11.07.2006 <br> - * - * @author Carsten Weisse - */ -public class URIHelper { - - private String encoding; - - private URI uri; - private String string; - private String query; - private Map<String, String> params; - private boolean modified; - - public URIHelper(String str) throws URISyntaxException { - this(str, "UTF-8"); - } - - private URIHelper(String str, String enc) throws URISyntaxException { - this.uri = new URI(str); - this.encoding = enc; - this.modified = true; - parseQuery(); - } - - /** - * Remove a single parameter, if exists. - */ - public URIHelper removeParameter(String name) { - if (params != null && !params.isEmpty()) { - // don't reset the modification state, because of initial construction - modified |= (params.remove(name) != null); - } - return this; - } - - /** - * Return the value of a single parameter. - * @return value; may be <code>null</code> if the parameter doesn't exist. - */ - public String getParameter(String name) { - if (params == null || params.isEmpty()) { - return null; - } else { - return params.get(name); - } - } - - public String toString() { - if (modified) { - updateQuery(); - updateString(); - modified = false; - } - return string; - } - - private void updateString() { - StringBuilder sb = new StringBuilder(); - if (uri.getScheme() != null) { - sb.append(uri.getScheme()); - sb.append(':'); - } - if (uri.isOpaque()) { - sb.append(uri.getRawSchemeSpecificPart()); - } else { - String host = uri.getHost(); - if (host != null) { - sb.append("//"); - if (uri.getRawUserInfo() != null) { - sb.append(uri.getRawUserInfo()); - sb.append('@'); - } - boolean needBrackets = ((host.indexOf(':') >= 0) && !host.startsWith("[") && !host.endsWith("]")); - if (needBrackets) sb.append('['); - sb.append(host); - if (needBrackets) sb.append(']'); - if (uri.getPort() != -1) { - sb.append(':'); - sb.append(uri.getPort()); - } - } else if (uri.getRawAuthority() != null) { - sb.append("//"); - sb.append(uri.getRawAuthority()); - } - } - if (uri.getRawPath() != null) sb.append(uri.getRawPath()); - if (query != null) { - sb.append('?'); - sb.append(query); - } - if (uri.getRawFragment() != null) { - sb.append('#'); - sb.append(uri.getRawFragment()); - } - string = sb.toString(); - } - - private void parseQuery() { - query = uri.getRawQuery(); - if (query == null) return; - // build the map - modified = true; - params = new HashMap<String,String>(); - - // Split off the given URL from its query string - StringTokenizer pairParser = new StringTokenizer(query, "&"); - - while (pairParser.hasMoreTokens()) { - try { - String pair = pairParser.nextToken(); - StringTokenizer valueParser = new StringTokenizer(pair, "="); - - String name = valueParser.nextToken(); - String value = valueParser.nextToken(); - - params.put(decode(name), decode(value)); - } catch (Throwable t) { - // If we cannot parse a parameter, ignore it - } - } - } - - private void updateQuery() { - // delete query if there are no parameters - if (params == null || params.isEmpty()) { - query = null; - return; - } - - // build the query string from parameter map - StringBuilder sb = new StringBuilder(); - for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) { - String name = it.next(); - String value = params.get(name); - if (value.length() == 0) continue; - sb.append(encode(name)).append('=').append(encode(value)); - sb.append('&'); - } - // remove the last '&' - sb.deleteCharAt(sb.length() - 1); - query = sb.toString(); - } - - private String encode(String orig) { - try { - return URLEncoder.encode(orig, encoding); - } catch (UnsupportedEncodingException e) { - // can't be but return the orig - return orig; - } - } - - private String decode(String orig) { - try { - return URLDecoder.decode(orig, encoding); - } catch (UnsupportedEncodingException e) { - // can't be but return the orig - return orig; - } - } -}