3f08e8a24916a75afd749c152097f58344819a3a
max
  Thu Mar 2 07:11:02 2023 -0800
Ignore the previous commit in code review, that was all wrong, I was looking at the wrong analytics page and thought it was working. The GA4 system is entirely different and the code now spread out over five source files. Also: The part of cart.c that outputs the <HEAD> is getting worse and worse. To test the new code, set the analyticsKey to the value in the cgi-bin-max sandbox and go to https://analytics.google.com/analytics/web/#/p356133243/realtime. All staff should have access (if not email me or Lou or Hiram), refs #30725

diff --git src/lib/htmshell.c src/lib/htmshell.c
index 23fd280..750086e 100644
--- src/lib/htmshell.c
+++ src/lib/htmshell.c
@@ -15,30 +15,32 @@
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "errAbort.h"
 #include "dnautil.h"
 #include "base64.h"
 
 
 jmp_buf htmlRecover;
 
 boolean htmlWarnBoxSetUpAlready=FALSE;
 
 static bool NoEscape = FALSE;
 
 static bool errorsNoHeader = FALSE;
 
+static char *analyticsKey = NULL;
+
 void htmlSuppressErrors()
 /* Do not output a http header for error messages. Makes sure that very early
  * errors are not shown back to the user but trigger a 500 error, */
 {
 errorsNoHeader = TRUE;
 }
 
 void htmlNoEscape()
 {
 NoEscape = TRUE;
 }
 
 void htmlDoEscape()
 {
 NoEscape = FALSE;
@@ -792,30 +794,36 @@
 static char *htmlStyleSheet = NULL;
 void htmlSetStyleSheet(char *styleSheet)
 /* Set document wide style sheet by adding css name to HEAD part.
  * Needs to be called before htmlStart or htmShell. */
 {
 htmlStyleSheet = styleSheet;
 }
 
 static char *htmlFormClass = NULL;
 void htmlSetFormClass(char *formClass)
 /* Set class in the BODY part. */
 {
 htmlFormClass = formClass;
 }
 
+void htmlSetGa4Key(char *key)
+/* Set GA4 key. Needs to be called before htmlStart or htmShell. */
+{
+analyticsKey = key;
+}
+
 void htmlSetStyleTheme(char *style)
 /* Set theme style. Needs to be called before htmlStart or htmShell. */
 {
 htmlStyleTheme = style;
 }
 
 static char *htmlBackground = NULL;
 
 void htmlSetBackground(char *imageFile)
 /* Set background - needs to be called before htmlStart
  * or htmShell. */
 {
 htmlBackground = imageFile;
 }
 
@@ -950,30 +958,31 @@
 /* more secure method not used yet 
 dyStringAppend(policy, "default-src 'self';");
 
 dyStringAppend(policy, "  child-src 'self';");
 */
 
 dyStringAppend(policy, " script-src 'self' blob:");
 // Trick for backwards compatibility with browsers that understand CSP1 but not nonces (CSP2).
 dyStringAppend(policy, " 'unsafe-inline'");
 // For browsers that DO understand nonces and CSP2, they ignore 'unsafe-inline' in script if nonce is present.
 char *noncePolicy=getNoncePolicy();
 dyStringPrintf(policy, " %s", noncePolicy);
 freeMem(noncePolicy);
 dyStringAppend(policy, " code.jquery.com");          // used by hgIntegrator jsHelper and others
 dyStringAppend(policy, " www.google-analytics.com"); // used by google analytics
+dyStringAppend(policy, " www.googletagmanager.com"); // used by google tag manager (new version of analytics)
 // cirm cdw lib and web browse
 dyStringAppend(policy, " www.samsarin.com/project/dagre-d3/latest/dagre-d3.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/d3/3.4.4/d3.min.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/bowser/1.6.1/bowser.min.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/jstree/3.3.4/jstree.min.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js");
 dyStringAppend(policy, " login.persona.org/include.js");
 dyStringAppend(policy, " cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js");
 // expMatrix
 dyStringAppend(policy, " ajax.googleapis.com");
 dyStringAppend(policy, " maxcdn.bootstrapcdn.com");
 dyStringAppend(policy, " d3js.org/d3.v3.min.js");
 // jsHelper
@@ -1082,30 +1091,32 @@
 fputs("<HEAD>\n", f);
 // CSP header
 generateCspMetaHeader(f);
 
 fputs(head, f);
 htmlFprintf(f,"<TITLE>%s</TITLE>\n", title); 
 if (endsWith(title,"Login - UCSC Genome Browser")) 
     fprintf(f,"\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;CHARSET=iso-8859-1\">\n");
 fprintf(f, "\t<META http-equiv=\"Content-Script-Type\" content=\"text/javascript\">\n");
 if (htmlStyle != NULL)
     fputs(htmlStyle, f);
 if (htmlStyleSheet != NULL)
     fprintf(f,"<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\">\n", htmlStyleSheet);
 if (htmlStyleTheme != NULL)
     fputs(htmlStyleTheme, f);
+if (analyticsKey)
+    fprintf(f, "<script async src=\"https://www.googletagmanager.com/gtag/js?id=%s\"></script>\n", analyticsKey);
 
 fputs("</HEAD>\n\n",f);
 printBodyTag(f);
 htmlWarnBoxSetup(f);
 }
 
 
 void htmlStart(char *title)
 /* Write the start of an html from CGI */
 {
 puts("Content-Type:text/html");
 puts("\n");
 _htmStartWithHead(stdout, "", title, TRUE, 1);
 }