001/* 002 * Copyright 2013 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.newsletter.ga; 017 018import java.net.URI; 019import java.net.URISyntaxException; 020import java.util.Random; 021 022import org.apache.avalon.framework.component.Component; 023import org.apache.avalon.framework.logger.AbstractLogEnabled; 024 025import org.ametys.core.util.URIUtils; 026 027/** 028 * Component which helps building google analytics event URIs. 029 */ 030public class GAUriBuilder extends AbstractLogEnabled implements Component 031{ 032 033 /** The avalon component role. */ 034 public static final String ROLE = GAUriBuilder.class.getName(); 035 036 /** The analytics GIF URL. */ 037 public static final String GA_GIF_URL = "https://www.google-analytics.com/__utm.gif"; 038 039 /** The analytics API version. */ 040 public static final String GA_VERSION = "5.3.8"; 041 042 /** The domain hash. */ 043 public static final String DOMAIN_HASH = "109627128"; // TODO Really hash the domain name? 044 045 /** Token to replace in the url by a random VisitorID value */ 046 public static final String GA_VISITORID_TOKEN = "#ga_visitorid_token#"; 047 048 /** Token to replace in the url by a random UTMN value */ 049 public static final String GA_UTMN_TOKEN = "#ga_utmn_token#"; 050 051 /** Token to replace in the url by a random UTMHID value */ 052 public static final String GA_UTMHID_TOKEN = "#ga_utmhid_token#"; 053 054 private static final Random _RNG = new Random((long) Math.random() * System.nanoTime()); 055 056 /** 057 * Get an event GIF URI. 058 * @param gaWebId the GA web ID. 059 * @param category the event category. 060 * @param action the event action. 061 * @param label the event label. 062 * @return the event GIF URI. 063 */ 064 public String getEventGifUri(String gaWebId, String category, String action, String label) 065 { 066 return getEventGifUri(gaWebId, category, action, label, 0); 067 } 068 069 /** 070 * Get an event GIF URI. 071 * @param gaWebId the GA web ID. 072 * @param category the event category. 073 * @param action the event action. 074 * @param label the event label. 075 * @param value the event value. 076 * @return the event GIF URI. 077 */ 078 public String getEventGifUri(String gaWebId, String category, String action, String label, int value) 079 { 080 return getEventGifUri(gaWebId, category, action, label, value, false); 081 } 082 083 /** 084 * Get an event GIF URI. 085 * @param gaWebId the GA web ID. 086 * @param category the event category. 087 * @param action the event action. 088 * @param label the event label. 089 * @param value the event value. 090 * @param nonInteraction true if the event does not trigger an interaction. 091 * @return the event GIF URI. 092 */ 093 public String getEventGifUri(String gaWebId, String category, String action, String label, int value, boolean nonInteraction) 094 { 095 String uri = ""; 096 097 try 098 { 099 uri = getEventGifUri(gaWebId, getEventIdentifier(category, action, label, value, nonInteraction)); 100 } 101 catch (URISyntaxException e) 102 { 103 // Just log a warning 104 getLogger().warn("Error building URI.", e); 105 } 106 107 return uri; 108 } 109 110 /** 111 * Get an event GIF URI. 112 * @param gaWebId the GA web ID. 113 * @param eventIdentifier the event identifier. 114 * @return the event GIF URI. 115 */ 116 public String getEventGifUri(String gaWebId, String eventIdentifier) 117 { 118 return getEventGifUri(gaWebId, eventIdentifier, true); 119 } 120 121 /** 122 * Get an event GIF URI. 123 * @param gaWebId the GA web ID. 124 * @param eventIdentifier the event identifier. 125 * @param usePlaceholderTokens True to use tokens instead of randomly generated values 126 * @return the event GIF URI. 127 */ 128 public String getEventGifUri(String gaWebId, String eventIdentifier, boolean usePlaceholderTokens) 129 { 130 long now = System.currentTimeMillis() / 1000L; 131 132 StringBuilder cookieValue = new StringBuilder(); 133 // UTMA 134 cookieValue.append("__utma=").append(DOMAIN_HASH).append('.').append(usePlaceholderTokens ? GA_VISITORID_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 135 cookieValue.append('.').append(now).append('.').append(now).append('.').append(now).append(".1"); 136 // UTMZ 137 cookieValue.append(";+__utmz=").append(DOMAIN_HASH).append('.').append(now); 138 cookieValue.append(".1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none);"); 139 140 StringBuilder uri = new StringBuilder(); 141 142 uri.append(GA_GIF_URL); 143 uri.append("?utmwv=").append(GA_VERSION); 144 uri.append("&utmn=").append(usePlaceholderTokens ? GA_UTMN_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 145 uri.append("&utmt=event"); 146 uri.append("&utme=").append(eventIdentifier); 147 uri.append("&utmcs=UTF-8"); 148 uri.append("&utmje=1"); 149 uri.append("&utmhid=").append(usePlaceholderTokens ? GA_UTMHID_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 150 // TODO utmhn with main domain? 151 uri.append("&utmr=-"); 152 uri.append("&utmac=").append(gaWebId); 153 154 // Cookie 155 String encodedCookie = URIUtils.encodeParameter(cookieValue.toString()); 156 uri.append("&utmcc=").append(encodedCookie); 157 158 return uri.toString(); 159 } 160 161 /** 162 * Compute an event identifier. 163 * @param category the event category. 164 * @param action the event action. 165 * @param label the event label. 166 * @return the event GIF URI. 167 * @throws URISyntaxException if an error occurs encoding the event identifier. 168 */ 169 public String getEventIdentifier(String category, String action, String label) throws URISyntaxException 170 { 171 return getEventIdentifier(category, action, label, 0); 172 } 173 174 /** 175 * Compute an event identifier. 176 * @param category the event category. 177 * @param action the event action. 178 * @param label the event label. 179 * @param value the event value. 180 * @return the event GIF URI. 181 * @throws URISyntaxException if an error occurs encoding the event identifier. 182 */ 183 public String getEventIdentifier(String category, String action, String label, int value) throws URISyntaxException 184 { 185 return getEventIdentifier(category, action, label, value, false); 186 } 187 188 /** 189 * Compute an event identifier. 190 * @param category the event category. 191 * @param action the event action. 192 * @param label the event label. 193 * @param value the event value. 194 * @param nonInteraction true if the event does not trigger an interaction. 195 * @return the event GIF URI. 196 * @throws URISyntaxException if an error occurs encoding the event identifier. 197 */ 198 public String getEventIdentifier(String category, String action, String label, int value, boolean nonInteraction) throws URISyntaxException 199 { 200 StringBuilder eventId = new StringBuilder(); 201 202 eventId.append("5("); 203 eventId.append(encodeValue(category)); 204 eventId.append('*').append(encodeValue(action)); 205 eventId.append('*').append(encodeValue(label)); 206 eventId.append(')'); 207 if (value > 0) 208 { 209 eventId.append('(').append(value).append(')'); 210 } 211 212 // TODO nonInteraction. 213 214 return eventId.toString(); 215 } 216 217 /** 218 * Encode a value to use as an identifier component. 219 * @param value the value to encode. 220 * @return the encoded value. 221 * @throws URISyntaxException if an error occurs encoding the value. 222 */ 223 public String encodeValue(String value) throws URISyntaxException 224 { 225 URI uri = new URI(null, null, null, value, null); 226 return uri.getRawQuery().replace("/", "%2F"); 227 } 228 229}