d33d0e1c19117a0347dbadb99a77e9f12483c735 max Tue Sep 1 06:56:09 2026 -0700 hgLogin: let users set or change their recovery email address, refs #38197 Adds a page where a signed-in user can set or change the recovery address on their account, offered in the account menu next to "Change email" -- both in the top right blue bar popup and on the session page. It is off by default: set login.recovEmailChange=on in hg.conf to offer it. The page also needs login.cookieSalt, working outbound mail and the recovEmailVerified column, and stays hidden where any of those is missing. The new address is confirmed by mail before it takes effect, so whatever is on the account keeps working until the link is opened and a typo costs the user nothing. An account that has a password must supply it, since a confirmed recovery address can sign in. Once the address does change, the account's main address is told, the same notice that a change of the main address already sends. One signature now covers both the address given at signup and a later change, so there is a single confirmation path rather than two. diff --git src/hg/lib/web.c src/hg/lib/web.c index 4efdfd2e875..3956dfe8a48 100644 --- src/hg/lib/web.c +++ src/hg/lib/web.c @@ -1,1976 +1,1980 @@ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include <regex.h> #include "hCommon.h" #include "obscure.h" #include "dnautil.h" #include "errAbort.h" #include "htmshell.h" #include "web.h" #include "dupTrack.h" #include "hPrint.h" #include "hdb.h" #include "hui.h" #include "hgConfig.h" #include "hVarSubst.h" #include "cheapcgi.h" #include "dbDb.h" #include "hgColors.h" #include "hubConnect.h" #include "search.h" #include "geoMirror.h" #include "trackHub.h" #include "versionInfo.h" #include "asmAlias.h" #include "cart.h" #ifndef GBROWSE #include "axtInfo.h" #include "wikiLink.h" #include "googleAnalytics.h" #include "jsHelper.h" #include "jsonParse.h" #endif /* GBROWSE */ /* flag that tell if the CGI header has already been outputed */ boolean webHeadAlreadyOutputed = FALSE; /* flag that tell if text CGI header has been outputed */ boolean webInTextMode = FALSE; struct hash *includedResourceFiles = NULL; static char *dbCgiName = "db"; static char *orgCgiName = "org"; static char *cladeCgiName = "clade"; static char *extraStyle = NULL; /* globals: a cart and db for use in error handlers. */ static struct cart *errCart = NULL; static char *errDb = NULL; void textVaWarn(char *format, va_list args) { vprintf(format, args); puts("\n"); } void softAbort() { if (!webInTextMode) webEnd(); exit(0); } void webPushErrHandlers(void) /* Push warn and abort handler for errAbort(). */ { if (webInTextMode) pushWarnHandler(textVaWarn); else pushWarnHandler(webVaWarn); pushAbortHandler(softAbort); hDumpStackPushAbortHandler(); } void webPushErrHandlersCartDb(struct cart *cart, char *db) /* Push warn and abort handler for errAbort(); save cart and db for use in handlers. */ { errCart = cart; errDb = db; webPushErrHandlers(); } void webPopErrHandlers(void) /* Pop warn and abort handler for errAbort(). */ { popWarnHandler(); hDumpStackPopAbortHandler(); popAbortHandler(); } void webSetStyle(char *style) /* set a style to add to the header */ { extraStyle = style; } void webPragmasEtc() /* Print out stuff that tells people not to cache us, and that we use the * usual character set and scripting langauge. (Normally done by webStartWrap) */ { printf("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;CHARSET=iso-8859-1\">" "\n" "<META http-equiv=\"Content-Script-Type\" content=\"text/javascript\">" "\n" "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" "\n" "<META HTTP-EQUIV=\"Expires\" CONTENT=\"-1\">" "\n" ); } void webCirmPragmasEtc() /* Print out stuff similar to webPragmasEtc (don't cache us, character set, etc.), but * use values appropriate for a more modern website (like CIRM). */ { printf("\t\t<meta charset=\"windows-1252\">\n" // Be nice to be utf-8, but that's a bigger issue to tackle "\t\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" "\t\t<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->\n"); } void webStartText() /* output the head for a text page */ { webHeadAlreadyOutputed = TRUE; webInTextMode = TRUE; webPushErrHandlers(); } static void webStartWrapperDetailedInternal(struct cart *theCart, char *db, char *headerText, char *textOutBuf, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader, boolean withHtmlHeader) /* output a CGI and HTML header with the given title in printf format */ { char uiState[256]; char *scriptName = cgiScriptName(); char *textOutBufDb = cloneString(textOutBuf); boolean isEncode = FALSE; if (theCart) { char *theGenome = NULL; char *genomeEnc = NULL; getDbAndGenome(theCart, &db, &theGenome, NULL); genomeEnc = cgiEncode(theGenome); safef(uiState, sizeof(uiState), "?%s=%s&%s=%s&%s=%s", orgCgiName, genomeEnc, dbCgiName, db, cartSessionVarName(), cartSessionId(theCart)); } else { uiState[0] = 0; uiState[1] = 0; } if (db == NULL) db = hDefaultDb(); // boolean dbIsFound = hDbExists(db); // boolean haveBlat = FALSE; unfortunately this feature has disappeared // if (dbIsFound) this needs to be resurrected in the new menu // haveBlat = hIsBlatIndexedDatabase(db); bar system in menuBar() if (scriptName == NULL) scriptName = cloneString(""); /* don't output two headers */ if(webHeadAlreadyOutputed) return; if (sameString(cgiUsualString("action",""),"encodeReleaseLog") || rStringIn("EncodeDataVersions", scriptName)) isEncode = TRUE; /* Preamble. */ dnaUtilOpen(); if (withHttpHeader) { char *cookieName = hUserCookie(); cartWriteHeaderAndCont(theCart, cookieName, NULL); } // If the database name is not already in the title string, add it now if (endsWith(scriptName, "hgc") && db != NULL && !stringIn(db, textOutBufDb)) { struct dyString *newTitle = dyStringNew(0); dyStringPrintf(newTitle, "%s %s", trackHubSkipHubName(db), textOutBufDb); textOutBufDb = dyStringCannibalize(&newTitle); } if (withHtmlHeader) { char *newString, *ptr1, *ptr2; char *browserVersion; if (btIE == cgiClientBrowser(&browserVersion, NULL, NULL) && *browserVersion < '8') puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"); else puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " "\"http://www.w3.org/TR/html4/loose.dtd\">"); // Strict would be nice since it fixes atleast one IE problem (use of :hover CSS pseudoclass) puts( "<HTML>" "\n" "<HEAD>" "\n" ); generateCspMetaHeader(stdout); htmlPrintAnalyticsLink(stdout); printf("\t%s\n", headerText); webPragmasEtc(); printf("\t<TITLE>"); /* we need to take any HTML formatting out of the titlebar string */ newString = cloneString(textOutBufDb); for(ptr1=newString, ptr2=textOutBufDb; *ptr2 ; ptr2++) { if (*ptr2 == '<') { for(; *ptr2 && (*ptr2 != '>'); ptr2++) ; } else *ptr1++ = *ptr2; } *ptr1 = 0; htmlTextOut(newString); printf(" </TITLE>\n "); webIncludeResourceFile("HGStyle.css"); if (extraStyle != NULL) puts(extraStyle); printf("</HEAD>\n"); printBodyTag(stdout); htmlWarnBoxSetup(stdout);// Sets up a warning box which can be filled with errors as they occur puts(commonCssStyles()); } /* Put up the hot links bar. */ char *menuStr = menuBar(theCart, db); if(menuStr) { puts(menuStr); } puts("<main id=\"mainContent\">"); webStartSectionTables(); if (withLogo) { puts("<TR><TH COLSPAN=1 ALIGN=\"left\">"); if (isEncode) { puts("<A HREF=\"http://www.genome.gov/10005107\" TARGET=\"_BLANK\">" "<IMG SRC=\"../images/ENCODE_scaleup_logo.png\" height=50 ALT=\"ENCODE Project at NHGRI\">" "</A>"); puts("<IMG SRC=\"../images/encodeDcc.jpg\" ALT=\"ENCODE Project at UCSC\">"); } else { puts("<IMG SRC=\"../images/title.jpg\">"); } puts("</TH></TR>" "\n" "" "\n" ); } if(!skipSectionHeader) /* this HTML must be in calling code if skipSectionHeader is TRUE */ { webFirstSection(textOutBufDb); }; webPushErrHandlers(); /* set the flag */ webHeadAlreadyOutputed = TRUE; errAbortSetDoContentType(FALSE); } /* static void webStartWrapperDetailedInternal() */ void webStartWrapperDetailedArgs(struct cart *theCart, char *db, char *headerText, char *format, va_list args, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader, boolean withHtmlHeader) /* output a CGI and HTML header with the given title in printf format */ { char textOutBuf[1024]; va_list argscp; va_copy(argscp,args); vasafef(textOutBuf, sizeof(textOutBuf), format, argscp); va_end(argscp); webStartWrapperDetailedInternal(theCart, db, headerText, textOutBuf, withHttpHeader, withLogo, skipSectionHeader, withHtmlHeader); } void webStartWrapperDetailedNoArgs(struct cart *theCart, char *db, char *headerText, char *format, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader, boolean withHtmlHeader) /* output a CGI and HTML header with the given title in printf format */ { char textOutBuf[512]; safecpy(textOutBuf, sizeof(textOutBuf), format); webStartWrapperDetailedInternal(theCart, db, headerText, textOutBuf, withHttpHeader, withLogo, skipSectionHeader, withHtmlHeader); } void webStartWrapperGatewayHeader(struct cart *theCart, char *db, char *headerText, char *format, va_list args, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader) { webStartWrapperDetailedArgs(theCart, db, headerText, format, args, withHttpHeader, withLogo, skipSectionHeader, TRUE); } void webStartWrapperGateway(struct cart *theCart, char *db, char *format, va_list args, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader) /* output a CGI and HTML header with the given title in printf format */ { webStartWrapperGatewayHeader(theCart, db, "", format, args, withHttpHeader, withLogo, skipSectionHeader); } void webStartWrapper(struct cart *theCart, char *db, char *format, va_list args, boolean withHttpHeader, boolean withLogo) /* allows backward compatibility with old webStartWrapper that doesn't contain the "skipHeader" arg */ /* output a CGI and HTML header with the given title in printf format */ { webStartWrapperGatewayHeader(theCart, db, "", format, args, withHttpHeader, withLogo, FALSE); } void webStartExt(boolean withHttpHeader, struct cart *theCart, char *db, char *format, ...) /* Print out pretty wrapper around things when not * from cart. Do not output an http header.*/ { va_list args; va_start(args, format); webStartWrapper(theCart, db, format, args, withHttpHeader, TRUE); va_end(args); } void webStart(struct cart *theCart, char *db, char *format, ...) /* Print out pretty wrapper around things when not * from cart. */ { va_list args; va_start(args, format); webStartWrapper(theCart, db, format, args, TRUE, FALSE); va_end(args); } void webStartHeader(struct cart *theCart, char *db, char *headerText, char *format, ...) /* Print out pretty wrapper around things when not from cart. * Include headerText in the html header. */ { va_list args; va_start(args, format); webStartWrapperGatewayHeader(theCart, db, headerText, format, args, TRUE, TRUE, FALSE); va_end(args); } void webEndSection() /* Close down a section */ { puts( "" "\n" " </TD><TD WIDTH=15></TD></TR></TABLE>" "\n" // "<BR>" " </TD></TR></TABLE>" "\n" " </TD></TR></TABLE>" "\n" " " ); puts("</div>"); } void webStartSectionTables() /* Put up start of nepharious table layout stuff. (Normally done by webStartWrap). */ { puts( "<A NAME=\"TOP\"></A>" "\n" "<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=\"100%\">" "\n"); } void webFirstSection(char *title) /* Put up the first section (normally done by webStartWrap). */ { puts( // TODO: Replace nested tables with CSS (difficulty is that tables are closed elsewhere) "<!-- +++++++++++++++++++++ CONTENT TABLES +++++++++++++++++++ -->" "\n" "<TR><TD COLSPAN=3>\n" "<div id=firstSection>" " <!--outer table is for border purposes-->\n" " <TABLE WIDTH='100%' BGCOLOR='#" HG_COL_BORDER "' BORDER='0' CELLSPACING='0' " "CELLPADDING='1'><TR><TD>\n" " <TABLE CLASS='hgInside' BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' BORDER='0' CELLSPACING='0' " "CELLPADDING='0'><TR><TD>\n" " <div class='subheadingBar'><div class='windowSize' id='sectTtl'>" ); // FIXME: Need a more general solution here to support styling in blue bar title. // Perhaps support escaped < and > by doubling or backslash htmlTextOut(title); puts(" </div></div>\n" " <TABLE CLASS='hgInside' BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' CELLPADDING=0>" "<TR><TH HEIGHT=10></TH></TR>\n" " <TR><TD WIDTH=10> </TD><TD>\n\n" ); } void webNewSectionHeaderStart() /* Start the header for a new section on the web page. * May be used to maintain table layout without a proper section header */ { webEndSection(); puts("<div>"); puts("<!-- +++++++++++++++++++++ START NEW SECTION +++++++++++++++++++ -->"); puts( // TODO: Replace nested tables with CSS (difficulty is that tables are closed elsewhere) "<BR>\n\n" " <!--outer table is for border purposes-->\n" " <TABLE WIDTH='100%' BGCOLOR='#" HG_COL_BORDER "' BORDER='0' CELLSPACING='0' CELLPADDING='1'><TR><TD>\n" " <TABLE BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' BORDER='0' CELLSPACING='0' CELLPADDING='0'><TR><TD>\n"); puts("<div class='subheadingBar windowSize'>"); } void webNewHiddenSectionHeaderEnd() /* Properly close header of hidden collapsible section on web page */ { puts(" </div>\n" " <TABLE style='display:none' BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' CELLPADDING=0>" "<TR><TH HEIGHT=10></TH></TR>\n" " <TR><TD WIDTH=10> </TD><TD>\n\n"); } void webNewHiddenSection(char* format, ...) /* create a new hidden section on the web page */ { va_list args; va_start(args, format); webNewSectionHeaderStart(); vprintf(format, args); webNewHiddenSectionHeaderEnd(); va_end(args); } void webNewSectionHeaderEnd() /* Properly close header of collapsible section on web page */ { puts(" </div>\n" " <TABLE BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' CELLPADDING=0>" "<TR><TH HEIGHT=10></TH></TR>\n" " <TR><TD WIDTH=10> </TD><TD>\n\n"); } void webNewSection(char* format, ...) /* create a new section on the web page */ { va_list args; va_start(args, format); webNewSectionHeaderStart(); vprintf(format, args); webNewSectionHeaderEnd(); va_end(args); } void webNewEmptySection() /* create a new section on the web page to maintain table layout */ { webNewSectionHeaderStart(); webNewSectionHeaderEnd(); } void webEndSectionTables() /* Finish with section tables (but don't do /BODY /HTML like * webEnd does. */ { webEndSection(); puts("</TD></TR></TABLE>\n"); } void webEnd() /* output the footer of the HTML page */ { if(!webInTextMode) { webEndSectionTables(); puts("</main>"); #ifndef GBROWSE googleAnalytics(); #endif /* GBROWSE */ jsInlineFinish(); puts( "</BODY></HTML>"); webPopErrHandlers(); } } void webEndExtra(char *footer) /* output the footer of the HTML page with extra footer as desired */ { if(!webInTextMode) { webEndSectionTables(); puts("</main>"); #ifndef GBROWSE googleAnalytics(); #endif /* GBROWSE */ jsInlineFinish(); if (footer) puts(footer); puts( "</BODY></HTML>"); webPopErrHandlers(); } } static void webStartGbOptionalBanner(struct cart *cart, char *db, char *title, boolean doBanner, boolean hgGateway) /* Start HTML with new header and footer design by JWest. Optionally display banner above menubar. Use flag with hgGateway, till that is migrated. */ { char *cookieName = hUserCookie(); cartWriteHeaderAndCont(cart, cookieName, NULL); char *csp = getCspMetaHeader(); if (hgGateway) { printf( #include "jWestHeader.h" , csp, title , webTimeStampedLinkToResource("HGStyle.css", TRUE) , webTimeStampedLinkToResource("jWest.css", TRUE)); } else { printf( #include "gbHeader.h" , csp, title); } if (doBanner) { printf( #include "jWestBanner.h" , title); } freeMem(csp); webPushErrHandlersCartDb(cart, db); htmlWarnBoxSetup(stdout); // Add hotlinks bar char *navBar = menuBar(cart, db); if (navBar) { puts(navBar); // Override nice-menu.css's menu background and fonts: webIncludeResourceFile("gbAfterMenu.css"); } puts("<main id=\"mainContent\">"); webHeadAlreadyOutputed = TRUE; errAbortSetDoContentType(FALSE); } void webStartGbNoBanner(struct cart *cart, char *db, char *title) /* Start HTML with new header and footer design by jWest, but no banner */ { webStartGbOptionalBanner(cart, db, title, FALSE, FALSE); } void webEndGb() /* End HTML that was started with webStartJWest. */ { puts("</main>"); googleAnalytics(); jsInlineFinish(); puts("</body></html>"); webPopErrHandlers(); } void webStartJWest(struct cart *cart, char *db, char *title) /* Start HTML with new banner and footer design by jWest (with modifications). */ { webStartGbOptionalBanner(cart, db, title, TRUE, TRUE); } void webEndJWest() /* End HTML that was started with webStartJWest. */ { webEndGb(); } static boolean gotWarnings = FALSE; void webVaWarn(char *format, va_list args) /* Warning handler that closes out page and stuff in * the fancy form. */ { gotWarnings = TRUE; boolean needStart = !webHeadAlreadyOutputed; if (needStart) { // All callers of this (via webPushErrHandlersCartDb) have skipped Content-type // because they want to output text unless we hit this condition: puts("Content-type:text/html\n"); cartWebStart(errCart, errDb, "Error"); } htmlVaWarn(format, args); printf("\n<!-- HGERROR -->\n\n\n"); } boolean webGotWarnings() /* Return TRUE if webVaWarn has been called. */ { return gotWarnings; } void webAbortExt(boolean withHttpHeader, char* title, char *format, va_list args) /* an abort function that outputs a error page */ { /* output the header */ if(!webHeadAlreadyOutputed) webStartExt(withHttpHeader, errCart, NULL, "%s", title); /* in text mode, have a different error */ if(webInTextMode) printf("\n\n\n %s\n\n", title); vprintf(format, args); printf("<!-- HGERROR -->\n"); printf("\n\n"); webEnd(); exit(0); } void webAbortNoHttpHeader(char* title, char* format, ...) /* an abort function that outputs a error page. No http header output. */ { va_list args; va_start(args, format); webAbortExt(FALSE, title, format, args); va_end(args); } void webAbort(char* title, char* format, ...) /* an abort function that outputs a error page */ { va_list args; va_start(args, format); webAbortExt(TRUE, title, format, args); va_end(args); } void printCladeListHtml(char *genome, char *event, char *javascript) /* Make an HTML select input listing the clades. */ { char **row = NULL; char *clades[4096]; char *labels[4096]; char *defaultClade = hClade(genome); char *defaultLabel = NULL; int numClades = 0; if (hubConnectIsCurated(trackHubSkipHubName(genome))) defaultClade = hClade(trackHubSkipHubName(genome)); else defaultClade = hClade(genome); struct sqlConnection *conn = hConnectCentral(); // after hClade since it access hgcentral too // get only the clades that have actual active genomes char query[4096]; sqlSafef(query, sizeof query, "SELECT DISTINCT(c.name), c.label " // mysql 5.7: SELECT list w/DISTINCT must include all fields in ORDER BY list (#18626) ", c.priority " "FROM %s c, %s g, %s d WHERE c.name=g.clade AND d.organism=g.genome AND d.active=1 " "ORDER BY c.priority", cladeTable(), genomeCladeTable(), dbDbTable()); struct sqlResult *sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) { clades[numClades] = cloneString(row[0]); labels[numClades] = cloneString(row[1]); if (sameWord(defaultClade, clades[numClades])) defaultLabel = clades[numClades]; numClades++; if (numClades >= ArraySize(clades)) internalErr(); } sqlFreeResult(&sr); hDisconnectCentral(&conn); struct slPair *names = trackHubGetCladeLabels(); for(; names; names = names->next) { clades[numClades] = names->name; labels[numClades] = names->val; if (sameWord(defaultClade, clades[numClades])) defaultLabel = clades[numClades]; numClades++; if (numClades >= ArraySize(clades)) internalErr(); } cgiMakeDropListFull(cladeCgiName, labels, clades, numClades, defaultLabel, event, javascript); } static void printSomeGenomeListHtmlNamedMaybeCheck(char *customOrgCgiName, char *db, struct dbDb *dbList, char *event, char *javascript, boolean doCheck) /* Prints to stdout the HTML to render a dropdown list * containing a list of the possible genomes to choose from. * param db - a database whose genome will be the default genome. * If NULL, no default selection. * param onChangeText - Optional (can be NULL) text to pass in * any onChange javascript. */ { char *orgList[1024]; int numGenomes = 0; struct dbDb *cur = NULL; struct hash *hash = hashNew(10); // 2^^10 entries = 1024 char *selGenome = hGenome(db); char *values [4096]; char *cgiName; for (cur = dbList; cur != NULL; cur = cur->next) { if (!hashFindVal(hash, cur->genome) && (!doCheck || hDbExists(cur->name) || startsWith("GC", cur->name))) { hashAdd(hash, cur->genome, cur); orgList[numGenomes] = trackHubSkipHubName(cur->genome); values[numGenomes] = cur->genome; numGenomes++; if (numGenomes >= ArraySize(orgList)) internalErr(); } } cgiName = (customOrgCgiName != NULL) ? customOrgCgiName : orgCgiName; cgiMakeDropListFull(cgiName, orgList, values, numGenomes, selGenome, event, javascript); hashFree(&hash); } void printSomeGenomeListHtmlNamed(char *customOrgCgiName, char *db, struct dbDb *dbList, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list * containing a list of the possible genomes to choose from. * param db - a database whose genome will be the default genome. * If NULL, no default selection. * param event e.g. "change" * javascript - Optional (can be NULL) onEvent javascript. */ { return printSomeGenomeListHtmlNamedMaybeCheck(customOrgCgiName, db, dbList, event, javascript, TRUE); } void printLiftOverGenomeList(char *customOrgCgiName, char *db, struct dbDb *dbList, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list * containing a list of the possible genomes to choose from. * Databases in dbList do not have to exist. * param db - a database whose genome will be the default genome. * If NULL, no default selection. * param event e.g. "change" * javascript - Optional (can be NULL) onEvent javascript. */ { return printSomeGenomeListHtmlNamedMaybeCheck(customOrgCgiName, db, dbList, event, javascript, FALSE); } void printSomeGenomeListHtml(char *db, struct dbDb *dbList, char *event, char *javascript) /* Prints the dropdown list using the orgCgiName */ { printSomeGenomeListHtmlNamed(NULL, db, dbList, event, javascript); } void printGenomeListHtml(char *db, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list * containing a list of the possible genomes to choose from. * param db - a database whose genome will be the default genome. * If NULL, no default selection. * param event e.g. "change" * javascript - Optional (can be NULL) onEvent javascript. */ { printSomeGenomeListHtml(db, hGetIndexedDatabases(), event, javascript); } void printBlatGenomeListHtml(char *db, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list * containing a list of the possible genomes to choose from. * param db - a database whose genome will be the default genome. * If NULL, no default selection. * param event e.g. "change" * javascript - Optional (can be NULL) onEvent javascript. */ { printSomeGenomeListHtml(db, hGetBlatIndexedDatabases(), event, javascript); } void printGenomeListForCladeHtml(char *db, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list containing * a list of the possible genomes from selOrganism's clade to choose from. * selOrganism is the default for the select. */ { printSomeGenomeListHtml(db, hGetIndexedDatabasesForClade(db), event, javascript); } void printAllAssemblyListHtmlParm(char *db, struct dbDb *dbList, char *dbCgi, bool allowInactive, char *event, char *javascript) /* Prints to stdout the HTML to render a dropdown list containing the list * of assemblies for the current genome to choose from. By default, * this includes only active assemblies with a database (with the * exception of the default assembly, which will be included even * if it isn't active). * param db - The default assembly (the database name) to choose as selected. * If NULL, no default selection. * param allowInactive - if set, print all assemblies for this genome, * even if they're inactive or have no database */ { char *assemblyList[4096]; char *values[4096]; int numAssemblies = 0; struct dbDb *cur = NULL; char *genome = hGenome(db); char *selAssembly = NULL; if (genome == NULL) #ifdef LOWELAB genome = "Pyrococcus furiosus"; #else genome = "Human"; #endif for (cur = dbList; cur != NULL; cur = cur->next) { /* Only for this genome */ if (!sameWord(genome, cur->genome)) continue; /* Save a pointer to the current assembly */ if (sameWord(db, cur->name)) selAssembly = cur->name; if (allowInactive || ((cur->active || sameWord(cur->name, db)) && (trackHubDatabase(db) || sqlDatabaseExists(cur->name)))) { assemblyList[numAssemblies] = cur->description; values[numAssemblies] = cur->name; numAssemblies++; if (numAssemblies >= ArraySize(assemblyList)) internalErr(); } } cgiMakeDropListFull(dbCgi, assemblyList, values, numAssemblies, selAssembly, event, javascript); } void printSomeAssemblyListHtmlParm(char *db, struct dbDb *dbList, char *dbCgi, char *event, char *javascript) /* Find all the assemblies from the list that are active. * Prints to stdout the HTML to render a dropdown list containing the list * of the possible assemblies to choose from. * param db - The default assembly (the database name) to choose as selected. * If NULL, no default selection. */ { printAllAssemblyListHtmlParm(db, dbList, dbCgi, TRUE, event, javascript); } void printSomeAssemblyListHtml(char *db, struct dbDb *dbList, char *event, char *javascript) /* Find all assemblies from the list that are active, and print * HTML to render dropdown list * param db - default assembly. If NULL, no default selection */ { printSomeAssemblyListHtmlParm(db, dbList, dbCgiName, event, javascript); } void printAssemblyListHtml(char *db, char *event, char *javascript) /* Find all the assemblies that pertain to the selected genome * Prints to stdout the HTML to render a dropdown list containing * a list of the possible assemblies to choose from. * Param db - The assembly (the database name) to choose as selected. * If NULL, no default selection. */ { struct dbDb *dbList = hGetIndexedDatabases(); printSomeAssemblyListHtml(db, dbList, event, javascript); } void printAssemblyListHtmlExtra(char *db, char *event, char *javascript) { /* Find all the assemblies that pertain to the selected genome Prints to stdout the HTML to render a dropdown list containing a list of the possible assemblies to choose from. param curDb - The assembly (the database name) to choose as selected. If NULL, no default selection. */ struct dbDb *dbList = hGetIndexedDatabases(); printSomeAssemblyListHtmlParm(db, dbList, dbCgiName, event, javascript); } void printBlatAssemblyListHtml(char *db) { /* Find all the assemblies that pertain to the selected genome Prints to stdout the HTML to render a dropdown list containing a list of the possible assemblies to choose from. param curDb - The assembly (the database name) to choose as selected. If NULL, no default selection. */ struct dbDb *dbList = hGetBlatIndexedDatabases(); printSomeAssemblyListHtml(db, dbList, NULL, NULL); } char *getCurrentGenomeLabel(char *db) /* Construct a label from dbDb (or dbDb related for an assembly hub) for the currently * selected genome */ { // TODO: what if 'db' is a reference to an assembly hub or genark? struct dbDb *info = hDbDb(db); if (info) return cloneString(info->description); else return cloneString(db); } void printGenomeSearchBar(char *id, char *placeholder, char *classStr, boolean withSearchButton, char *labelText, char *labelClassStr) /* Prints an input text box that can be used to search for any genome. * param withSearchButton - controls if there is a button next to the bar * to manually fire the search * param classStr - if desired, a custom class name or string can be used * otherwise the default styling of 'genomeSearchBarDefault' is applied via HGStyle.css * param labelText - If not empty, put up a <label> for the search bar, use labelClassStr to * style it * param labelClassStr - if not empty and labelText not empty, apply this class to the label * * There is a default class in HGStyle.css that is used * * The caller CGI needs to include jquery-ui.js and utils.js to turn this into a * useable search bar with autocomplete */ { printf("<div class='flexContainer'>\n"); // for styling purposes if (isNotEmpty(labelText)) { printf("<label for='%s' class='%s'>%s</label>", id, isNotEmpty(labelClassStr) ? labelClassStr : "genomeSearchLabelDefault", labelText); } printf("<div class='searchBarAndButton'>\n"); printf("<input id='%s' type='text' ", id); if (isNotEmpty(placeholder)) printf("placeholder='%s' ", placeholder); printf("class='%s' ", isNotEmpty(classStr) ? classStr : "genomeSearchBarDefault"); printf("></input>\n"); printf("<button id='%sToggle' type='button' class='genomeSearchToggle' tabindex='-1'" " aria-label='Show popular assemblies'" " title='Show recent and popular assemblies'>▼</button>\n", id); if (withSearchButton) printf("<input id='%sButton' value='search' type='button'></input>", id); char *searchHelpText = "All genome searches are case-insensitive. Single-word searches default to prefix " "matching if an exact match is not found. " "<ul id='searchTipList' class='noBullets'>" "<li> Force inclusion: Use a + sign before <b>+word</b> to ensure it appears in result.</li>" "<li> Exclude words: Use a - sign before <b>-word</b> to exclude it from the search result.</li>" "<li> Wildcard search: Add an * (asterisk) at end of <b>word*</b> to search for all terms starting with that prefix.</li>" "<li> Phrase search: Enclose 'words in quotes' to search for the exact phrase.</li>" "</ul>"; printInfoIcon(searchHelpText); printf("</div>\n"); // the search button is grouped with the input // Embed popular assemblies JSON for the autocomplete dropdown { char *popularStr = cfgOptionDefault("browser.popularGenomes", "hg38,hg19,mm39,mm10,rn7,danRer11,dm6,ce11,sacCer3"); struct slName *dbNames = slNameListFromComma(popularStr); struct dyString *json = dyStringNew(512); dyStringAppendC(json, '['); boolean first = TRUE; struct slName *dbIter; for (dbIter = dbNames; dbIter != NULL; dbIter = dbIter->next) { struct dbDb *info = hDbDb(dbIter->name); if (info == NULL || !info->active) continue; if (!first) dyStringAppendC(json, ','); first = FALSE; dyStringPrintf(json, "{\"db\":\"%s\",\"label\":\"%s - %s (%s)\",\"commonName\":\"%s\"}", jsonStringEscape(info->name), jsonStringEscape(info->organism), jsonStringEscape(info->description), jsonStringEscape(info->name), jsonStringEscape(info->organism)); } dyStringAppendC(json, ']'); printf("<script type='application/json' id='%sPopularData'>%s</script>\n", id, dyStringContents(json)); dyStringFree(&json); slFreeList(&dbNames); } printf("</div>\n"); } static char *getDbForGenome(char *genome, struct cart *cart) /* Function to find the default database for the given Genome. It looks in the cart first and then, if that database's Genome matches the passed-in Genome, returns it. If the Genome does not match, it returns the default database that does match that Genome. param Genome - The Genome for which to find a database param cart - The cart to use to first search for a suitable database name return - The database matching this Genome type */ { char *retDb = cartUsualString(cart, dbCgiName, NULL); if ((retDb == NULL) || !hDbExists(retDb)) { retDb = hDefaultDb(); } /* If genomes don't match, then get the default db for that genome */ if (differentWord(genome, hGenome(retDb))) { retDb = hDefaultDbForGenome(genome); } return retDb; } void getDbGenomeClade(struct cart *cart, char **retDb, char **retGenome, char **retClade, struct hash *oldVars) /* Examine CGI and cart variables to determine which db, genome, or clade * has been selected, and then adjust as necessary so that all three are * consistent. Detect changes and reset db-specific cart variables. * Save db, genome and clade in the cart so it will be consistent hereafter. * The order of preference here is as follows: * If we got a request that explicitly names the db, that takes * highest priority, and we synch the organism to that db. * If we get a cgi request for a specific organism then we use that * organism to choose the DB. If just clade, go from there. * In the cart only, we use the same order of preference. * If someone requests an Genome we try to give them the same db as * was in their cart, unless the Genome doesn't match. */ { boolean gotClade = hGotClade(); *retDb = cgiOptionalString(dbCgiName); if (*retDb == NULL) // if db is not in URL, but genome is, use it for db *retDb = cgiOptionalString(hgHubGenome); *retGenome = cgiOptionalString(orgCgiName); *retClade = cgiOptionalString(cladeCgiName); /* Was the database passed in as a cgi param? * If so, it takes precedence and determines the genome. */ *retDb = asmAliasFindUnlessGenArk(*retDb); if (*retDb && hDbExists(*retDb)) { *retGenome = hGenome(*retDb); } /* If no db was passed in as a cgi param then was the organism (a.k.a. genome) * passed in as a cgi param? * If so, the we use the proper database for that genome. */ else if (*retGenome && !sameWord(*retGenome, "0")) { *retDb = getDbForGenome(*retGenome, cart); *retGenome = hGenome(*retDb); } else if (*retClade && gotClade) { *retGenome = hDefaultGenomeForClade(*retClade); *retDb = getDbForGenome(*retGenome, cart); } /* If no cgi params passed in then we need to inspect the session */ else { *retDb = cartOptionalString(cart, dbCgiName); *retDb = asmAliasFindUnlessGenArk(*retDb); *retGenome = cartOptionalString(cart, orgCgiName); *retClade = cartOptionalString(cart, cladeCgiName); /* If there was a db found in the session that determines everything. */ if (*retDb && hDbExists(*retDb)) { *retGenome = hGenome(*retDb); } else if (*retGenome && !sameWord(*retGenome, "0")) { *retDb = hDefaultDbForGenome(*retGenome); } else if (*retClade && gotClade) { *retGenome = hDefaultGenomeForClade(*retClade); *retDb = getDbForGenome(*retGenome, cart); } /* If no organism in the session then get the default db and organism. */ else { *retDb = hDefaultDb(); *retGenome = hGenome(*retDb); } } *retDb = cloneString(*retDb); *retGenome = cloneString(*retGenome); *retClade = hClade(*retGenome); if ( (*retClade != NULL) && sameString(*retClade, "none")) cartRemove(cart, "position"); /* Detect change of database and reset db-specific cart variables: */ if (oldVars) { char *oldDb = hashFindVal(oldVars, "db"); char *oldOrg = hashFindVal(oldVars, "org"); char *oldClade = hashFindVal(oldVars, "clade"); if ((!IS_CART_VAR_EMPTY(oldDb) && differentWord(oldDb, *retDb)) || (!IS_CART_VAR_EMPTY(oldOrg) && differentWord(oldOrg, *retGenome)) || (!IS_CART_VAR_EMPTY(oldClade) && differentWord(oldClade, *retClade))) { /* Change position to default -- unless it was passed in via CGI: */ if (cgiOptionalString("position") == NULL) cartSetString(cart, "position", hDefaultPos(*retDb)); // remove virtual chrom cart vars related to position cartRemove(cart, "virtMode"); cartRemove(cart, "virtModeType"); cartRemove(cart, "lastVirtModeExtraState"); cartRemove(cart, "lastVirtModeType"); cartRemove(cart, "nonVirtPosition"); cartRemove(cart, "oldPosition"); /* hgNear search term -- unless it was passed in via CGI: */ if (cgiOptionalString("near_search") == NULL) cartRemove(cart, "near_search"); /* hgBlat results (hgUserPsl track): */ cartRemove(cart, "ss"); /* hgTables correlate: */ cartRemove(cart, "hgta_correlateTrack"); cartRemove(cart, "hgta_correlateTable"); cartRemove(cart, "hgta_correlateGroup"); cartRemove(cart, "hgta_correlateOp"); cartRemove(cart, "hgta_nextCorrelateTrack"); cartRemove(cart, "hgta_nextCorrelateTable"); cartRemove(cart, "hgta_nextCorrelateGroup"); cartRemove(cart, "hgta_nextCorrelateOp"); cartRemove(cart, "hgta_corrWinSize"); cartRemove(cart, "hgta_corrMaxLimitCount"); } } /* Save db, genome (as org) and clade in cart. */ cartSetString(cart, "db", *retDb); cartSetString(cart, "org", *retGenome); if (gotClade) cartSetString(cart, "clade", *retClade); } void getDbAndGenome(struct cart *cart, char **retDb, char **retGenome, struct hash *oldVars) /* Get just the db and genome. */ { char *garbage = NULL; getDbGenomeClade(cart, retDb, retGenome, &garbage, oldVars); freeMem(garbage); } static void webIncludeFileSubst(char *file, struct cart *cart) /* Include an HTML file in a CGI. If cart is non-null, invoke hVarSubstWithCart. * The file path may begin with hDocumentRoot(); if it doesn't, it is * assumed to be relative and hDocumentRoot() will be prepended. */ { char *str = hFileContentsOrWarning(file); if (cart != NULL) { char *db = cartString(cart, "db"); hVarSubstWithCart("webIncludeFileSubst", cart, NULL, db, &str); } puts(str); freeMem(str); } void webIncludeFile(char *file) /* Include an HTML file in a CGI. * The file path may begin with hDocumentRoot(); if it doesn't, it is * assumed to be relative and hDocumentRoot() will be prepended. */ { return webIncludeFileSubst(file, NULL); } void webIncludeHelpFileSubst(char *fileRoot, struct cart *cart, boolean addHorizLine) /* Given a help file root name (e.g. "hgPcrResult" or "cutters"), * print out the contents of the file. If cart is non-NULL, invoke hVarSubstWithCart * before printing. If addHorizLine, print out an <HR> first. */ { if (addHorizLine) htmlHorizontalLine(); char *file = hHelpFile(fileRoot); webIncludeFileSubst(file, cart); } void webIncludeHelpFile(char *fileRoot, boolean addHorizLine) /* Given a help file root name (e.g. "hgPcrResult" or "cutters"), * print out the contents of the file. If addHorizLine, print out an * <HR> first. */ { return webIncludeHelpFileSubst(fileRoot, NULL, addHorizLine); } void webPrintLinkTableStart() /* Print link table start in our colors. */ { printf("<TABLE><TR><TD BGCOLOR='#" HG_COL_BORDER "'>\n"); printf("<TABLE CELLSPACING=1 CELLPADDING=3><TR>\n"); } void webPrintLinkTableEnd() /* Print link table end in our colors. */ { printf("</TR></TABLE>\n"); printf("</TD></TR></TABLE>\n"); } void webPrintLinkOutCellStart() /* Print link cell that goes out of our site. End with * webPrintLinkTableEnd. */ { printf("<TD BGCOLOR='#" HG_COL_LOCAL_TABLE "'>"); } void webPrintWideCellStart(int colSpan, char *bgColorRgb) /* Print link multi-column cell start in our colors. */ { printf("<TD BGCOLOR='#%s'", bgColorRgb); if (colSpan > 1) printf(" COLSPAN=%d", colSpan); printf(">"); } void webPrintLinkCellStart() /* Print link cell start in our colors. */ { webPrintWideCellStart(1, HG_COL_TABLE); } void webPrintLinkCellRightStart() /* Print right-justified cell start in our colors. */ { printf("<TD BGCOLOR='#"HG_COL_TABLE"' ALIGN='right'>"); } void webPrintLinkCellEnd() /* Print link cell end in our colors. */ { printf("</TD>"); } void webPrintLinkCell(char *link) /* Print link cell in our colors, if links is null, print empty cell */ { webPrintLinkCellStart(); if (link != NULL) puts(link); webPrintLinkCellEnd(); } void webPrintIntCell(int val) /* Print right-justified int cell in our colors. */ { webPrintLinkCellRightStart(); printf("%d", val); webPrintLinkCellEnd(); } void webPrintDoubleCell(double val) /* Print right-justified cell in our colors with two digits to right of decimal. */ { webPrintLinkCellRightStart(); printf("%4.2f", val); webPrintLinkCellEnd(); } void webPrintWideLabelCellStart(int colSpan) /* Print start of wrapper around a label in a table. */ { printf("<TD BGCOLOR='#"HG_COL_TABLE_LABEL"'"); if (colSpan > 1) printf(" COLSPAN=%d", colSpan); printf("><span style='color:#FFFFFF;'><B>"); } void webPrintLabelCellStart() /* Print start of wrapper around a label in a table. */ { webPrintWideLabelCellStart(1); } void webPrintLabelCellEnd() /* Print end of wrapper around a label in a table. */ { printf("</B></span></TD>"); } void webPrintWideLabelCell(char *label, int colSpan) /* Print label cell over multiple columns in our colors. */ { webPrintWideLabelCellStart(colSpan); printf("%s", label); webPrintLabelCellEnd(); } void webPrintWideCenteredLabelCell(char *label, int colSpan) /* Print label cell over multiple columns in our colors and centered. */ { printf("<TD BGCOLOR='#" HG_COL_TABLE_LABEL "'"); if (colSpan > 1) printf(" COLSPAN=%d", colSpan); printf("><CENTER><span style='color:#FFFFFF;'><B>%s</B></span></CENTER></TD>", label); } void webPrintLabelCell(char *label) /* Print label cell in our colors. */ { webPrintWideLabelCell(label, 1); } void webPrintLinkTableNewRow() /* start a new row */ { printf("</TR>\n<TR>"); } void finishPartialTable(int rowIx, int itemPos, int maxPerRow, void (*cellStart)()) /* Fill out partially empty last row. */ { if (rowIx != 0 && itemPos < maxPerRow) { int i; for (i=itemPos; i<maxPerRow; ++i) { cellStart(); webPrintLinkCellEnd(); } } } void webFinishPartialLinkOutTable(int rowIx, int itemPos, int maxPerRow) /* Fill out partially empty last row. */ { finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkOutCellStart); } void webFinishPartialLinkTable(int rowIx, int itemPos, int maxPerRow) /* Fill out partially empty last row. */ { finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkCellStart); } static struct dyString* getHtdocsSubdir(char *dirName) /* return dystring with subdirectory under htDocs, tolerant of missing docRoot */ { struct dyString *fullDirName = NULL; char *docRoot = hDocumentRoot(); if (docRoot != NULL) fullDirName = dyStringCreate("%s/%s", docRoot, dirName); else // tolerate missing docRoot (i.e. when running from command line) fullDirName = dyStringCreate("%s", dirName); return fullDirName; } char *webCssLink(char *fileName, boolean mustExist) /* alternative for webTimeStampedLinkToResource for CSS files: returns a string with a time-stamped * link to a CSS file as a html fragment <link .... >. returns empty string if file does not exist. * errAborts if mustExist is True. * */ { // construct the absolute path to the file on disk char *relDir = cfgOptionDefault("browser.styleDir","style"); struct dyString *absDir = getHtdocsSubdir(relDir); struct dyString *absFileName = dyStringCreate("%s/%s", dyStringContents(absDir), fileName); struct dyString *htmlFrag = NULL; if (!fileExists(dyStringContents(absFileName))) { if (mustExist) errAbort("webCssLink: file: %s doesn't exist.\n", dyStringContents(absFileName)); else htmlFrag = dyStringNew(0); } else { // construct a link with the relative path to the file on the web server long mtime = fileModTime(dyStringContents(absFileName)); htmlFrag = dyStringCreate("<link rel='stylesheet' href='../%s/%s?v=%ld' type='text/css'>\n", relDir, fileName, mtime); } dyStringFree(&absFileName); dyStringFree(&absDir); return dyStringCannibalize(&htmlFrag); } char *webTimeStampedLinkToResource(char *fileName, boolean wrapInHtml) // If wrapInHtml // returns versioned link embedded in style or script html (free after use). // else // returns full path of a versioned path to the requested resource file (js, or css). // NOTE: png, jpg and gif should also be supported but are untested. // // In production sites we use a versioned softlink that includes the CGI version. This has the following benefits: // a) flushes user's web browser cache when the user visits a GB site whose version has changed since their last visit; // b) enforces the requirement that static files are the same version as the CGIs (something that often fails to happen in mirrors). // (see notes in redmine #3170). // // In dev trees we use mtime to create a pseudo-version; this forces web browsers to reload css/js file when it changes, // so we don't get odd behavior that can be caused by caching of mis-matched javascript and style files in dev trees. // // In either case, the actual file has to have been previously created by running make in the appropriate directory (kent/src/hg/js // or kent/src/hg/htdocs/style). { char baseName[PATH_LEN]; char extension[FILEEXT_LEN]; splitPath(fileName, NULL, baseName, extension); boolean js = sameString(".js",extension); boolean style = !js && sameString(".css",extension); boolean image = !js && !style && ( sameString(".png",extension) || sameString(".jpg",extension) || sameString(".gif",extension)); if (!js && !style) // && !image) NOTE: This code has not been tested on images but should work. errAbort("webTimeStampedLinkToResource: unknown resource type for %s.\n", fileName); char *httpHost = hHttpHost(); // Build and verify directory char *dirName = ""; if (js) dirName = cfgOptionDefault("browser.javaScriptDir", "js"); else if (style) dirName = cfgOptionDefault("browser.styleDir","style"); else if (image) dirName = cfgOptionDefault("browser.styleImagesDir","style/images"); struct dyString *fullDirName = NULL; char *docRoot = hDocumentRoot(); if (docRoot != NULL) fullDirName = dyStringCreate("%s/%s", docRoot, dirName); else // tolerate missing docRoot (i.e. when running from command line) fullDirName = dyStringCreate("%s", dirName); if (!fileExists(dyStringContents(fullDirName))) errAbort("webTimeStampedLinkToResource: dir: %s doesn't exist. (host: %s)\n", dyStringContents(fullDirName), httpHost); // build and verify real path to file struct dyString *realFileName = dyStringCreate("%s/%s", dyStringContents(fullDirName), fileName); if (!fileExists(dyStringContents(realFileName))) errAbort("webTimeStampedLinkToResource: file: %s doesn't exist.\n", dyStringContents(realFileName)); // build and verify link path including timestamp in the form of dir/baseName + timeStamp or CGI Version + ext long mtime = fileModTime(dyStringContents(realFileName)); struct dyString *linkWithTimestamp; linkWithTimestamp = dyStringCreate("%s/%s%s?v=%ld", dyStringContents(fullDirName), baseName, extension, mtime); // Free up all that extra memory dyStringFree(&realFileName); dyStringFree(&fullDirName); char *linkFull = dyStringCannibalize(&linkWithTimestamp); char *link = linkFull; if (docRoot != NULL) { struct dyString *relativeLink = dyStringCreate("../%s", linkFull + strlen(docRoot) + 1); link = dyStringCannibalize(&relativeLink); freeMem(linkFull); } if (wrapInHtml) // wrapped for christmas { struct dyString *wrapped = dyStringNew(0); if (js) dyStringPrintf(wrapped,"<script type='text/javascript' SRC='%s'></script>\n", link); else if (style) dyStringPrintf(wrapped,"<link rel='stylesheet' href='%s' type='text/css'>\n", link); else // Will be image, since these are the only three choices allowed dyStringPrintf(wrapped,"<IMG src='%s' />\n", link); freeMem(link); link = dyStringCannibalize(&wrapped); } return link; } char *webTimeStampedLinkToResourceOnFirstCall(char *fileName, boolean wrapInHtml) // If this is the first call, will return full path of timestamped link to the requested // resource file (js, or css). Free after use. // else returns NULL. Useful to ensure multiple references to the same resource file are not made // NOTE: png, jpg and gif should also be supported but are untested. { if (!includedResourceFiles) includedResourceFiles = newHash(0); if (hashLookup(includedResourceFiles, fileName)) return NULL; char * link = webTimeStampedLinkToResource(fileName,wrapInHtml); if (link) hashAdd(includedResourceFiles, fileName, NULL); // Don't hash link, because return link; // memory will be freed by caller!!! } boolean webIncludeResourcePrintToFile(FILE * toFile, char *fileName) // Converts fileName to web Resource link and prints the html reference // This only prints and returns TRUE on first call for this resource. // Passing in NULL as the file pointer results in hPrintf call // The reference will be to a link with timestamp. { char *link = webTimeStampedLinkToResourceOnFirstCall(fileName,TRUE); if (link) { if (toFile == NULL) hPrintf("%s",link); else fprintf(toFile,"%s",link); freeMem(link); return TRUE; } return FALSE; } // overrides for default context specific help link. char *contextSpecificHelpLink = NULL; char *contextSpecificHelpLabel = NULL; char *contextSpecificHelpId = NULL; void setContextSpecificHelp(char *link, char *label, char *id) // Override default behavior for the context specific help link { if(link) contextSpecificHelpLink = cloneString(link); if(label) contextSpecificHelpLabel = cloneString(label); if(id) contextSpecificHelpId = cloneString(id); } char *menuBarAddUiVars(char *oldString, char *cgiPrefix, char *uiVars) /* Look for CGI program calls in oldString, and add session vars hgsid to them */ { int len = strlen(oldString); char buf[4096]; /* Create a regular expression and compile it */ regex_t re; regmatch_t match[2]; safef(buf, sizeof(buf), "%s[A-Za-z]+(%c%c?)", cgiPrefix, '\\', '?'); int err = regcomp(&re, buf, REG_EXTENDED); if(err) errAbort("regcomp failed; err: %d", err); /* Search through oldString with regex, and build up new string in dy */ struct dyString *dy = dyStringNew(0); int offset; for(offset = 0; offset < len && !regexec(&re, oldString + offset, ArraySize(match), match, 0); offset += match[0].rm_eo) { dyStringAppendN(dy, oldString + offset, match[0].rm_eo); if(match[1].rm_so == match[1].rm_eo) dyStringAppend(dy, "?"); dyStringAppend(dy, uiVars); if(match[1].rm_so != match[1].rm_eo) dyStringAppend(dy, "&"); } if(offset < len) dyStringAppend(dy, oldString + offset); return dyStringCannibalize(&dy); } void webIncludeLocalJs() /* some mirrors want special JS on their site */ { char *addJs = cfgOption("addJs"); if (addJs) { struct slName *jsList = slNameListFromString(addJs, ','); for(; jsList; jsList = jsList->next) jsIncludeFile(jsList->name, NULL); slNameFreeList(&jsList); } } char *menuBar(struct cart *cart, char *db) // Return HTML for the menu bar (read from a configuration file); // we fixup internal CGI's to add hgsid's and include the appropriate js and css files. // // Note this function is also called by hgTracks which extends the menu bar // with a View menu defined in hgTracks/menu.c { char *docRoot = hDocumentRoot(); char *menuStr, buf[4096], uiVars[4096]; FILE *fd; char *navBarFile = "inc/globalNavBar.inc"; struct stat statBuf; char *scriptName = cgiScriptName(); if (cart) safef(uiVars, sizeof(uiVars), "%s=%s", cartSessionVarName(), cartSessionId(cart)); else uiVars[0] = 0; if(docRoot == NULL) // tolerate missing docRoot (i.e. don't bother with menu when running from command line) return NULL; jsIncludeFile("jquery.js", NULL); jsIncludeFile("jquery.plugins.js", NULL); jsIncludeFile("utils.js", NULL); jsIncludeFile("topLinks.js", NULL); webIncludeResourceFile("nice_menu.css"); webIncludeLocalJs(); // Read in menu bar html safef(buf, sizeof(buf), "%s/%s", docRoot, navBarFile); fd = mustOpen(buf, "r"); fstat(fileno(fd), &statBuf); int len = statBuf.st_size; menuStr = needMem(len + 1); mustRead(fd, menuStr, statBuf.st_size); menuStr[len] = 0; carefulClose(&fd); if (cart) { char *newMenuStr = menuBarAddUiVars(menuStr, "/cgi-bin/hg", uiVars); freez(&menuStr); menuStr = newMenuStr; } if(scriptName) { // Provide hgTables options for some CGIs. char hgTablesOptions[1024] = ""; char *track = (cart == NULL ? NULL : (endsWith(scriptName, "hgGene") ? cartOptionalString(cart, "hgg_type") : cartOptionalString(cart, "g"))); if (track && cart && db && (endsWith(scriptName, "hgc") || endsWith(scriptName, "hgTrackUi") || endsWith(scriptName, "hgGtexTrackSettings") || endsWith(scriptName, "hgGene"))) { struct trackDb *tdb = hTrackDbForTrack(db, track); if (tdb) { struct trackDb *topLevel = trackDbTopLevelSelfOrParent(tdb); char *undupedTrack = dupTrackSkipToSourceName(topLevel->track); safef(hgTablesOptions, sizeof hgTablesOptions, "../cgi-bin/hgTables?hgta_doMainPage=1&hgta_group=%s&hgta_track=%s&hgta_table=%s&", topLevel->grp, undupedTrack, tdb->table); menuStr = replaceChars(menuStr, "../cgi-bin/hgTables?", hgTablesOptions); trackDbFree(&tdb); } } } // Fill in the top-right Login link (placeholder <!-- LOGIN_LINK --> in globalNavBar.inc, inside // the #topRightLinks container). Logged out: a "Login" link straight to hgLogin. Logged in: the // username, which opens an account dialog (handled in topLinks.js using the data-* attributes // below). No login system: removed. Login, logout and the account links all send the visitor // back to the page they are on now. { char *loginLi = ""; if (loginSystemEnabled() || wikiLinkEnabled()) { char *userName = wikiLinkUserName(); struct dyString *dy = dyStringNew(512); // Come back to the page the visitor was reading when they clicked, refs #38192. The // return URL is NULL on the few pages there is no point returning to, and then these // links fall back to their old hgSession target. char *hgsid = cart ? cartSessionId(cart) : ""; char *retEnc = wikiLinkEncodeCurrentPageReturnUrl(hgsid); if (userName == NULL) { // Link straight to the login page (same target hgSession's own Login link uses), // rather than bouncing the user through hgSession first. char *loginUrl = retEnc ? wikiLinkUserLoginUrlReturning(hgsid, retEnc) : wikiLinkUserLoginUrl(hgsid); dyStringPrintf(dy, "<a class='topRightLink' href='%s' id='loginLink' " "title='Log in to save and share sessions'>Login</a>", loginUrl); freez(&loginUrl); } else { char *logoutUrl = retEnc ? wikiLinkUserLogoutUrlReturning(hgsid, retEnc) : wikiLinkUserLogoutUrl(hgsid); char *changePwUrl = retEnc ? wikiLinkChangePasswordUrlReturning(hgsid, retEnc) : wikiLinkChangePasswordUrl(hgsid); char *changeEmailUrl = retEnc ? wikiLinkChangeEmailUrlReturning(hgsid, retEnc) : wikiLinkChangeEmailUrl(hgsid); + char *changeRecovEmailUrl = retEnc + ? wikiLinkChangeRecovEmailUrlReturning(hgsid, retEnc) + : wikiLinkChangeRecovEmailUrl(hgsid); dyStringPrintf(dy, "<a class='topRightLink' href='#' id='loginLink' " "title='Account info and sign out' " "data-username=\"%s\" data-logouturl=\"%s\" data-changepwurl=\"%s\" " - "data-changeemailurl=\"%s\">%s</a>", + "data-changeemailurl=\"%s\" data-changerecovemailurl=\"%s\">%s</a>", userName, logoutUrl, changePwUrl ? changePwUrl : "", - changeEmailUrl ? changeEmailUrl : "", userName); + changeEmailUrl ? changeEmailUrl : "", + changeRecovEmailUrl ? changeRecovEmailUrl : "", userName); } freez(&retEnc); loginLi = dyStringCannibalize(&dy); } menuStr = replaceChars(menuStr, "<!-- LOGIN_LINK -->", loginLi); } // Fill in the top-right "Share a link" link (placeholder <!-- SHARE_LINK -->). // data-sharemode tells topLinks.js what to do: // "session" (hgTracks): save the current view as a session and show a short link; branches on // data-loggedin to either name a session (logged in) or make an anonymous link. // "url" (hgTrackUi): just show the current page URL with the hgsid stripped, for sharing. { char *shareLi = ""; boolean isHgTracks = (scriptName && endsWith(scriptName, "hgTracks")); boolean isHgTrackUi = (scriptName && endsWith(scriptName, "hgTrackUi")); if (isHgTracks || isHgTrackUi) { char *userName = (loginSystemEnabled() || wikiLinkEnabled()) ? wikiLinkUserName() : NULL; char *shareMode = isHgTrackUi ? "url" : "session"; struct dyString *dy = dyStringNew(256); // data-shortlink lets topLinks.js build the exact share URL to preview in the dialog, before // the session is actually created, matching addSessionLink()'s server-side choice. dyStringPrintf(dy, "<a class='topRightLink' href='#' id='shareLink' " "title='Get a link to this view to share with others' " "data-sharemode='%s' data-loggedin='%d' data-shortlink='%d'", shareMode, (userName != NULL), cfgOptionBooleanDefault("hgSession.shortLink", FALSE)); if (userName != NULL) dyStringPrintf(dy, " data-username=\"%s\"", userName); dyStringAppend(dy, ">Share a link</a>"); shareLi = dyStringCannibalize(&dy); } menuStr = replaceChars(menuStr, "<!-- SHARE_LINK -->", shareLi); } if(scriptName) { // Provide optional official mirror servers menu items char *geoMenu = geoMirrorMenu(); char *pattern = "<!-- OPTIONAL_MIRROR_MENU -->"; char *newMenuStr = replaceChars(menuStr, pattern, geoMenu); freez(&menuStr); menuStr = newMenuStr; } if(scriptName) { // Provide view menu for some CGIs. struct dyString *viewItems = dyStringCreate("%s",""); boolean hasViewMenu = TRUE; if (endsWith(scriptName, "hgGenome")) { safef(buf, sizeof(buf), "../cgi-bin/hgGenome?%s&hgGenome_doPsOutput=1", uiVars); dyStringPrintf(viewItems, "<li><a href='%s' id='%s'>%s</a></li>\n", buf, "pdfLink", "PDF"); } else { hasViewMenu = FALSE; } if (hasViewMenu) { struct dyString *viewMenu = dyStringCreate("<li class='menuparent' id='view'><span>View</span>\n<ul style='display: none; visibility: hidden;'>\n"); dyStringAppend(viewMenu, viewItems->string); dyStringAppend(viewMenu, "</ul>\n</li>\n"); menuStr = replaceChars(menuStr, "<!-- OPTIONAL_VIEW_MENU -->", viewMenu->string); dyStringFree(&viewMenu); } else if (!endsWith(scriptName, "hgTracks")) { replaceChars(menuStr, "<!-- OPTIONAL_VIEW_MENU -->", ""); } dyStringFree(&viewItems); } if(scriptName) { // Provide context sensitive help links for some CGIs. char *link = NULL; char *label = NULL; char *id = NULL; if (endsWith(scriptName, "hgBlat")) { link = "../goldenPath/help/hgTracksHelp.html#BLATAlign"; label = "Help on Blat"; id = "hgBlatHelp"; } else if (endsWith(scriptName, "hgHubConnect")) { link = "../goldenPath/help/hgTrackHubHelp.html"; label = "Help on Track Hubs"; id = "hgHubConnectHelp"; } else if (endsWith(scriptName, "hgNear")) { link = "../goldenPath/help/hgNearHelp.html"; label = "Help on Gene Sorter"; id = "hgNearHelp"; } else if (endsWith(scriptName, "hgTables")) { link = "../goldenPath/help/hgTablesHelp.html"; label = "Help on Table Browser"; id = "hgTablesHelp"; } else if (endsWith(scriptName, "hgIntegrator")) { link = "../goldenPath/help/hgIntegratorHelp.html"; label = "Help on Data Integrator"; id = "hgIntegratorHelp"; } else if (endsWith(scriptName, "hgGenome")) { link = "../goldenPath/help/hgGenomeHelp.html"; label = "Help on Genome Graphs"; id = "hgGenomeHelp"; } else if (endsWith(scriptName, "hgSession")) { link = "../goldenPath/help/hgSessionHelp.html"; label = "Help on Sessions"; id = "hgSessionHelp"; } else if (endsWith(scriptName, "hgVisiGene")) { link = "../goldenPath/help/hgTracksHelp.html#VisiGeneHelp"; label = "Help on VisiGene"; id = "hgVisiGeneHelp"; } else if (endsWith(scriptName, "hgCustom")) { link = "../goldenPath/help/customTrack.html"; label = "Help on Custom Tracks"; id = "hgCustomHelp"; } // Don't overwrite any previously set defaults if(!contextSpecificHelpLink && link) contextSpecificHelpLink = link; if(!contextSpecificHelpLabel && label) contextSpecificHelpLabel = label; if(!contextSpecificHelpId && id) contextSpecificHelpId = id; } if(contextSpecificHelpLink) { char buf[1024]; safef(buf, sizeof(buf), "<li><a id='%s' href='%s'>%s</a></li>", contextSpecificHelpId, contextSpecificHelpLink, contextSpecificHelpLabel); menuStr = replaceChars(menuStr, "<!-- CONTEXT_SPECIFIC_HELP -->", buf); } // links to hgTracks need to use the web browser width and set the hgTracks image // size in pixels correctly to match the hgGateway "GO" button jsInline("$(\"#tools1 ul li a\").each( function (a) {\n" " if (this.href && this.href.indexOf(\"hgTracks\") !== -1) {\n" " var obj = this;\n" " obj.onclick = function(e) {\n" " var pix = calculateHgTracksWidth();\n" " e.currentTarget.href += \"&pix=\" + pix;\n" " }\n" " }\n" "});\n"); // if the user has previously searched for assemblies, add them to the "Genomes" menu heading, // above the "other" assemblies link jsInline("addRecentGenomesToMenuBar();\n"); // If the user explicitly navigated to this genome (db= in CGI params), add it to recent genomes if (db && cgiOptionalString("db")) { struct dbDb *dbInfo = hDbDb(db); if (dbInfo) { char *bareDb = jsonStringEscape(trackHubSkipHubName(db)); char *safeOrganism = jsonStringEscape(trackHubSkipHubName(dbInfo->organism)); struct dyString *jsCall = dyStringNew(512); dyStringPrintf(jsCall, "addRecentGenome({db:\"%s\", genome:\"%s\", label:\"%s (%s)\", commonName:\"%s\"", bareDb, bareDb, safeOrganism, bareDb, safeOrganism); if (dbInfo->taxId > 0) dyStringPrintf(jsCall, ", taxId: %d", dbInfo->taxId); // For hub/GenArk assemblies, include hubUrl and category so hgGateway can route correctly struct trackHubGenome *hubGenome = trackHubDatabase(db) ? trackHubGetGenome(db) : NULL; if (hubGenome && hubGenome->trackHub) { char *safeHubUrl = jsonStringEscape(hubGenome->trackHub->url); dyStringPrintf(jsCall, ", hubUrl:\"%s\"", safeHubUrl); if (startsWith("/gbdb", hubGenome->trackHub->url)) dyStringAppend(jsCall, ", category:\"UCSC Curated\""); else { dyStringAppend(jsCall, ", category:\"Assembly Hub\""); char *hubName = trackHubGetHubName(db); if (hubName) dyStringPrintf(jsCall, ", hubName: \"%s\"", jsonStringEscape(hubName)); } } else dyStringAppend(jsCall, ", category:\"UCSC Curated\""); dyStringAppend(jsCall, "});\n"); jsInline(dyStringContents(jsCall)); dyStringFree(&jsCall); } } return menuStr; } void checkForGeoMirrorRedirect(struct cart *cart) // Implement Geo/IP based redirection. { char *thisNodeStr = geoMirrorNode(); if (thisNodeStr) // if geo-mirroring is enabled { char *redirectCookie = findCookieData("redirect"); char *redirect = cgiOptionalString("redirect"); // if we're not already redirected if (redirect == NULL && redirectCookie == NULL) { int thisNode = sqlUnsigned(thisNodeStr); struct sqlConnection *centralConn = hConnectCentral(); char *ipStr = cgiRemoteAddr(); int node = geoMirrorDefaultNode(centralConn, ipStr); // if our node is not the node that's closest. if (thisNode != node) { char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); char query[1056]; sqlSafef(query, sizeof query, "select domain from gbNode%s where node = %d", geoSuffix, node); char *newDomain = sqlQuickString(centralConn, query); char *oldDomain = cgiServerName(); char *port = cgiServerPort(); char *uri = cgiRequestUri(); char *sep = strchr(uri, '?') ? "&" : "?"; int newUriSize = strlen(uri) + 1024; char *newUri = needMem(newUriSize); char *oldUri = needMem(newUriSize); safef(oldUri, newUriSize, "http%s://%s:%s%s%sredirect=manual&source=%s", cgiServerHttpsIsOn() ? "s" : "", oldDomain, port, uri, sep, oldDomain); safef(newUri, newUriSize, "http%s://%s:%s%s%sredirect=manual&source=%s", cgiServerHttpsIsOn() ? "s" : "", newDomain, port, uri, sep, oldDomain); printf("<TR><TD COLSPAN=3 id='redirectTd'>" "<div style=\"margin: 10px 25%%; border-style:solid; border-width:thin; border-color:#97D897;\">" "<h3 style=\"background-color: #97D897; text-align: left; margin-top:0px; margin-bottom:0px;\">" " You might want to navigate to your nearest mirror - %s" "</h3> " "<ul style=\"margin:5px;\">", newDomain); jsOnEventById("click","redirectTd", "document.getElementById('redirectTd').innerHTML='';"); printf("<li>User settings (sessions and custom tracks) <B>will differ</B> between sites." "<idiv style=\"float:right;\"><a href=\"../goldenPath/help/genomeEuro.html#sessions\">Read more.</a></idiv>"); printf("<li>Take me to <a href=\"%s\">%s</a> </li>", newUri, newDomain); printf("<li>Let me stay here <a href=\"%s\">%s</a>", oldUri, oldDomain ); printf("</div></TD></TR>\n"); jsInlineFinish(); cartCheckout(&cart); exit(0); } hDisconnectCentral(¢ralConn); } } }