6d28e748f8aee8c252386600fc93d7692aa6929d braney Wed Sep 2 10:39:13 2026 -0700 hgTracks: don't escape the Track Search title twice, refs #38172 The Track Search page built its blue bar title with htmlEncode() on the organism and the freeze name, then handed it to webStartWrapperDetailedNoArgs. That call prints the title through htmlTextOut, which already escapes & < > and the double quote, so the ampersand starting each entity htmlEncode() wrote was escaped a second time and the page printed the entity instead of the character. Every assembly whose dbDb description carries a '/' showed it: hg38 read "Search for Tracks in the Human Dec. 2013 (GRCh38/hg38) Assembly". The title lands in element text between two divs rather than in an attribute, so htmlTextOut covers it on its own and the encode here is not needed. Checked against the assembly hub case that prompted the encode in the first place: a hub whose genomes.txt organism and description hold markup still comes back as text in the blue bar. Found by Max in the v503 Preview II code review. diff --git src/hg/hgTracks/searchTracks.c src/hg/hgTracks/searchTracks.c index 04f75100073..a9ec02e061e 100644 --- src/hg/hgTracks/searchTracks.c +++ src/hg/hgTracks/searchTracks.c @@ -1,1241 +1,1245 @@ /* Track search code used by hgTracks CGI */ /* Copyright (C) 2012 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include #include "common.h" #include "search.h" #include "hCommon.h" #include "memalloc.h" #include "obscure.h" #include "dystring.h" #include "hash.h" #include "cheapcgi.h" #include "hPrint.h" #include "htmshell.h" #include "cart.h" #include "hgTracks.h" #include "web.h" #include "jksql.h" #include "hdb.h" #include "mdb.h" #include "fileUi.h" #include "trix.h" #include "jsHelper.h" #include "imageV2.h" #include "hgConfig.h" #include "trackHub.h" #include "hubConnect.h" #include "hubPublic.h" #include "hubSearchText.h" #include "errCatch.h" #define TRACK_SEARCH_FORM "trackSearch" #define SEARCH_RESULTS_FORM "searchResults" #define TRACK_SEARCH_CURRENT_TAB "tsCurTab" #define TRACK_SEARCH_SIMPLE "tsSimple" #define TRACK_SEARCH_ON_NAME "tsName" #define TRACK_SEARCH_ON_TYPE "tsType" #define TRACK_SEARCH_ON_GROUP "tsGroup" #define TRACK_SEARCH_ON_DESCR "tsDescr" #define TRACK_SEARCH_SORT "tsSort" #define TRACK_SEARCH_ON_HUBS "tsIncludePublicHubs" // the list of found tracks struct slRef *tracks = NULL; // for advanced search only: // associates hub id's to hub urls that have search results, // used to get huburls onto hgTrackUi links struct hash *hubIdsToUrls = NULL; static int gCmpGroup(const void *va, const void *vb) /* Compare groups based on label. */ { const struct group *a = *((struct group **)va); const struct group *b = *((struct group **)vb); return strcmp(a->label, b->label); } // Would like to do a radio button choice ofsorts enum sortBy { sbRelevance=0, sbAbc =1, sbHierarchy=2, }; static int gCmpTrackHierarchy(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); if ( tdbIsFolder(a->tdb) && !tdbIsFolder(b->tdb)) return -1; else if (!tdbIsFolder(a->tdb) && tdbIsFolder(b->tdb)) return 1; if ( tdbIsContainer(a->tdb) && !tdbIsContainer(b->tdb)) return -1; else if (!tdbIsContainer(a->tdb) && tdbIsContainer(b->tdb)) return 1; if (!tdbIsContainerChild(a->tdb) && tdbIsContainerChild(b->tdb)) return -1; else if ( tdbIsContainerChild(a->tdb) && !tdbIsContainerChild(b->tdb)) return 1; return strcasecmp(a->longLabel, b->longLabel); } static int gCmpTrack(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); return strcasecmp(a->longLabel, b->longLabel); } static void findTracksSort(struct slRef **pTrack, enum sortBy sortBy) { if (sortBy == sbHierarchy) slSort(pTrack, gCmpTrackHierarchy); else if (sortBy == sbAbc) slSort(pTrack, gCmpTrack); else slReverse(pTrack); } static int getFormatTypes(char ***pLabels, char ***pTypes) { char *crudeTypes[] = { ANYLABEL, "bam", "psl", "chain", "netAlign", "maf", "bed", "bigBed", "ctgPos", "expRatio", "genePred", "broadPeak", "narrowPeak", "rmsk", "bedGraph", "bigWig", "wig", "wigMaf" }; // Non-standard: // type altGraphX // type axt // type bed5FloatScore // type bed5FloatScoreWithFdr // type chromGraph // type clonePos // type coloredExon // type encodeFiveC // type factorSource // type ld2 // type logo // type maf // type sample // type wigMafProt 0.0 1.0 char *nicerTypes[] = { ANYLABEL, "Alignment binary (bam) - binary SAM", "Alignment Blast (psl) - Blast output", "Alignment Chains (chain) - Pairwise alignment", "Alignment Nets (netAlign) - Net alignments", "Alignments (maf) - multiple alignment format", "bed - browser extensible data", "bigBed - self index, often remote bed format", "ctgPos - Contigs", "expRatio - Expression ratios", "Genes (genePred) - Gene prediction and annotation", "Peaks Broad (broadPeak) - ENCODE large region peak format", "Peaks Narrow (narrowPeak) - ENCODE small region peak format", "Repeats (rmsk) - Repeat masking", "Signal (bedGraph) - graphically represented bed data", "Signal (bigWig) - self index, often remote wiggle format", "Signal (wig) - wiggle format", "Signal (wigMaf) - multiple alignment wiggle" }; int ix = 0, count = sizeof(crudeTypes)/sizeof(char *); char **labels; char **values; AllocArray(labels, count); AllocArray(values, count); for (ix=0;ixnext) { if (tdbIsSuperTrackChild(tdb) && hashFindVal(superTrackHash, tdb->parent->track) == NULL) { hashAdd(superTrackHash, tdb->parent->track, tdb->parent); } } struct hashEl *hel, *helList = hashElListHash(superTrackHash); for (hel = helList; hel != NULL; hel = hel->next) slAddHead(&ret, (struct trackDb *)hel->val); return ret; } static void hubTdbListToTrackList(struct trackDb *tdbList, struct track **trackList, struct slName *trackNames) /* Recursively convert a (struct trackDb *) to a (struct track *) */ { struct trackDb *tmp, *next; for (tmp = tdbList; tmp != NULL; tmp = next) { next = tmp->next; if (slNameInList(trackNames, tmp->track)) { struct track *t = trackFromTrackDb(tmp); slAddHead(trackList, t); } if (tmp->subtracks) hubTdbListToTrackList(tmp->subtracks, trackList, trackNames); } } static void hubTdbListAddSupers(struct trackDb *tdbList, struct track **trackList, struct slName *trackNames) /* a track we are looking for might be a super track and thus not in tdbList, look for it here */ { struct trackDb *tmp, *superTrackDbs = getSuperTrackTdbs(tdbList); for (tmp = superTrackDbs; tmp != NULL; tmp = tmp->next) { if (slNameInList(trackNames, tmp->track)) { struct track *tg = trackFromTrackDb(tmp); slAddHead(trackList, tg); } } } struct hubSearchTracks /* A helper struct for collapsing a (struct hubSearchText *) into just the parts * we need for looking up the track hits */ { struct hubSearchTracks *next; char *hubUrl; // the url to this hub which is used as a key into the search hash int hubId; struct hubConnectStatus *hub; // the hubStatus result struct slName *searchedTracks; // the track names the search terms matched against }; struct paraFetchData /* A helper struct for managing connecting to many hubs in parallel and adding the * relevant tracks to the global (struct slRef *)tracks struct. */ { struct paraFetchData *next; char *hubName; // the name of the hub for measureTiming results struct hubSearchTracks *hst; // the tracks we are adding to the search results struct track *tlist; // the resulting tracks to add to the global trackList long searchTime; // how many milliseconds did it take to search this hub boolean done; }; // helper variables for connecting to hubs in parallel pthread_mutex_t pfdMutex = PTHREAD_MUTEX_INITIALIZER; struct paraFetchData *pfdListInitial = NULL; struct paraFetchData *pfdList = NULL; struct paraFetchData *pfdRunning = NULL; struct paraFetchData *pfdDone = NULL; void *addUnconnectedHubSearchResults(void *x) /* Add a not yet connected hub to the search results */ { struct paraFetchData *pfd = NULL; boolean allDone = FALSE; while(1) { pthread_mutex_lock(&pfdMutex); // the wait function will set pfdList = NULL, so don't start up any more // stuff if that happens: if (!pfdList) allDone = TRUE; else { pfd = slPopHead(&pfdList); slAddHead(&pfdRunning, pfd); } pthread_mutex_unlock(&pfdMutex); if (allDone) return NULL; struct hubSearchTracks *hst = pfd->hst; struct track *tracksToAdd = NULL; long startTime = clock1000(); struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { pfd->done = FALSE; boolean foundFirstGenome = FALSE; struct hash *trackDbNameHash = newHash(5); struct trackDb *tdbList = hubAddTracks(hst->hub, database, &foundFirstGenome, trackDbNameHash, NULL); if (measureTiming) measureTime("After connecting to hub %s: '%d': ", hst->hubUrl, hst->hubId); // get composite and subtracks into trackList hubTdbListToTrackList(tdbList, &tracksToAdd, hst->searchedTracks); hubTdbListAddSupers(tdbList, &tracksToAdd, hst->searchedTracks); pfd->done = TRUE; pfd->tlist = tracksToAdd; pfd->searchTime = clock1000() - startTime;; } errCatchEnd(errCatch); pthread_mutex_lock(&pfdMutex); slRemoveEl(&pfdRunning, pfd); slAddHead(&pfdDone, pfd); if (measureTiming) measureTime("Finished finding tracks for hub '%s': ", pfd->hubName); pthread_mutex_unlock(&pfdMutex); } //always return NULL for pthread_create() return NULL; } static void hubSearchHashToPfdList(char *descSearch, struct hash *searchResultsHash, struct hash *hubLookup, struct sqlConnection *conn) /* getHubSearchResults() returned a hash of search hits to various hubs, convert that * into something we can work on in parallel */ { struct hubSearchTracks *ret = NULL; struct hashCookie cookie = hashFirst(searchResultsHash); struct hash *hubUrlsToTrackList = hashNew(0); struct hashEl *hel = NULL; struct dyString *trackName = dyStringNew(0); struct slName *connectedHubs = hubConnectHubsInCart(cart); while ((hel = hashNext(&cookie)) != NULL) { struct hubSearchText *hst = (struct hubSearchText *)hel->val; struct hubEntry *hubInfo = (struct hubEntry *) hashFindVal(hubLookup, hst->hubUrl); if (isNotEmpty(hubInfo->errorMessage)) continue; // if we were already connected to this hub, then it's search hits // were already taken care of by the regular search code char hubId[256]; safef(hubId, sizeof(hubId), "%d", hubInfo->id); if (slNameInList(connectedHubs, hubId)) continue; struct hubConnectStatus *hub = hubConnectStatusForId(conn, hubInfo->id); // the hubSearchText contains multiple entries per lookup in order to form // the hgHubConnect UI. We only need one type of hit here: struct hubSearchTracks *found = hashFindVal(hubUrlsToTrackList, hst->hubUrl); for (; hst != NULL; hst = hst->next) { // don't add results for matches to the hub descriptionUrl, only to track names/descs if (isNotEmpty(hst->track) && hst->textLength != hubSearchTextMeta) { // hst->textLength=hubSearchTextLong denotes hits to track description if (isNotEmpty(descSearch) && hst->textLength != hubSearchTextLong) continue; if (!found) { AllocVar(found); found->hubUrl = hst->hubUrl; found->searchedTracks = NULL; found->hub = hub; found->hubId = hubInfo->id; slAddHead(&ret, found); hashAdd(hubUrlsToTrackList, hst->hubUrl, found); hashAdd(hubIdsToUrls, hubId, hst->hubUrl); } dyStringPrintf(trackName, "%s%d_%s", hubTrackPrefix, hubInfo->id, hst->track); slNameStore(&found->searchedTracks, cloneString(trackName->string)); dyStringClear(trackName); } } } struct hubSearchTracks *t; for (t = ret; t != NULL; t = t->next) { struct paraFetchData *pfd; AllocVar(pfd); pfd->hubName = t->hubUrl; pfd->hst = t; slAddHead(&pfdList, pfd); slAddHead(&pfdListInitial, CloneVar(pfd)); } if (measureTiming) measureTime("Finished converting hubSearchText to hubSearchTracks and pfd"); } void waitForSearchResults(int numThreads, pthread_t *threadList) /* Run each thread and kill the ones that take too long */ { // only wait 5 seconds, if something is in the cache we can show it // otherwise just ignore, nobody should wait more than 5 seconds // for a simple track search. Although note that just getting to // this point can take quite a while depending on hgcentral // connections and obtaining trackDb. int maxTime = 5 * 1000; int waitTime = 0; int lockStatus = 0; struct paraFetchData *pfd; while(1) { sleep1000(50); waitTime += 50; boolean allDone = TRUE; // we don't want to block in the event one of the child threads is // taking forever lockStatus = pthread_mutex_trylock(&pfdMutex); if (pfdList || pfdRunning) allDone = FALSE; if (allDone) { if (lockStatus == 0) // we aquired the lock pthread_mutex_unlock(&pfdMutex); break; } if (waitTime >= maxTime) { if (lockStatus == 0) // we aquired the lock pthread_mutex_unlock(&pfdMutex); break; } if (lockStatus == 0) // release the lock if we got it pthread_mutex_unlock(&pfdMutex); } // now that we've waited the maximum time we need to kill // any running threads and add the results of any threads // that ran successfully lockStatus = pthread_mutex_trylock(&pfdMutex); struct paraFetchData *neverRan = pfdList; if (lockStatus == 0) { // prevent still running threads from continuing pfdList = NULL; if (measureTiming) fprintf(stdout, "Successfully acquired lock, adding any successful thread data\n
"); for (pfd = pfdDone; pfd != NULL; pfd = pfd->next) { struct track *t; for (t = pfd->tlist; t != NULL; t = t->next) refAdd(&tracks, t); if (measureTiming) measureTime("'%s' search times", pfd->hubName); } for (pfd = neverRan; pfd != NULL; pfd = pfd->next) if (measureTiming) measureTime("'%s' search times: never ran", pfd->hubName); } else { // Should we warn or something that results are still waiting? As of now // just silently return instead, and note that no unconnected hub data // will show up (we get connected hub results for free because of // trackDbCaching) if (measureTiming) measureTime("Timed out searching hubs"); } if (lockStatus == 0) pthread_mutex_unlock(&pfdMutex); } void addHubSearchResults(struct slName *nameList, char *descSearch) /* add public hubs to the track list */ { struct sqlConnection *conn = hConnectCentral(); char *hubSearchTableName = hubSearchTextTableName(); char *publicTable = cfgOptionEnvDefault("HGDB_HUB_PUBLIC_TABLE", hubPublicTableConfVariable, defaultHubPublicTableName); char *statusTable = cfgOptionEnvDefault("HGDB_HUB_STATUS_TABLE", hubStatusTableConfVariable, defaultHubStatusTableName); struct dyString *extra = dyStringNew(0); if (nameList) { struct slName *tmp = NULL; for (tmp = nameList; tmp != NULL; tmp = tmp->next) { sqlDyStringPrintf(extra, "label like '%%%s%%'", tmp->name); if (tmp->next) sqlDyStringPrintf(extra, " and "); } } if (sqlTableExists(conn, hubSearchTableName)) { struct hash *searchResultsHash = hashNew(0); struct hash *pHash = hashNew(0); struct slName *hubsToPrint = NULL; addPublicHubsToHubStatus(cart, conn, publicTable, statusTable); struct hash *hubLookup = buildPublicLookupHash(conn, publicTable, statusTable, &pHash); char *db = cloneString(trackHubSkipHubName(database)); tolowers(db); getHubSearchResults(conn, hubSearchTableName, descSearch, isNotEmpty(descSearch), db, hubLookup, &searchResultsHash, &hubsToPrint, dyStringCannibalize(&extra)); hubSearchHashToPfdList(descSearch, searchResultsHash, hubLookup, conn); if (measureTiming) measureTime("after querying hubSearchText table and ready to start threads"); int ptMax = atoi(cfgOptionDefault("parallelFetch.threads", "20")); int pfdListCount = 0, pt; if (ptMax > 0) { pfdListCount = slCount(pfdList); pthread_t *threads = NULL; ptMax = min(ptMax, pfdListCount); if (ptMax > 0) { AllocArray(threads, ptMax); for (pt = 0; pt < ptMax; pt++) { int rc = pthread_create(&threads[pt], NULL, addUnconnectedHubSearchResults, NULL); if (rc ) errAbort("Unexpected error in pthread_create"); pthread_detach(threads[pt]); // this thread will just happily keep working until waitForSearchResults() finishes, // moving it's completed work onto pfdDone, so we can safely detach } } waitForSearchResults(ptMax, threads); } } if (measureTiming) measureTime("Total time spent searching hubs"); } static void simpleSearchForTracks(char *simpleEntry) // Performs the simple search and returns the found tracks. { // Prepare for trix search if (!isEmpty(simpleEntry)) { int trixWordCount = 0; char *tmp = cloneString(simpleEntry); char *val = nextWord(&tmp); struct slName *el, *trixList = NULL; while (val != NULL) { slNameAddTail(&trixList, val); trixWordCount++; val = nextWord(&tmp); } if (trixWordCount > 0 && !isHubTrack(database)) { // Unfortunately trixSearch can't handle the slName list int i; char **trixWords = needMem(sizeof(char *) * trixWordCount); for (i = 0, el = trixList; el != NULL; i++, el = el->next) trixWords[i] = strLower(el->name); // Now open the trix file char trixFile[HDB_MAX_PATH_STRING]; getSearchTrixFile(database, trixFile, sizeof(trixFile)); struct trix *trix = trixOpen(trixFile); struct trixSearchResult *tsList = trixSearch(trix, trixWordCount, trixWords, tsmExpand); for ( ; tsList != NULL; tsList = tsList->next) { struct track *track = (struct track *) hashFindVal(trackHash, tsList->itemId); if (track != NULL) // It is expected that this is NULL { // (e.g. when trix references trackDb tracks which have no tables) refAdd(&tracks, track); } } //trixClose(trix); // don't bother (this is a CGI that is about to end) } } } static void advancedSearchForTracks(struct sqlConnection *conn,struct group *groupList, char *nameSearch, char *typeSearch, char *descSearch, char *groupSearch, struct slPair *mdbPairs, boolean includeHubResults) // Performs the advanced search and returns the found tracks. { int tracksFound = 0; int numMetadataNonEmpty = 0; struct slPair *pair = mdbPairs; for (; pair!= NULL;pair=pair->next) { if (!isEmpty((char *)(pair->val))) numMetadataNonEmpty++; } if (!isEmpty(groupSearch) && sameString(groupSearch,ANYLABEL)) groupSearch = NULL; if (!isEmpty(typeSearch) && sameString(typeSearch,ANYLABEL)) typeSearch = NULL; if (isEmpty(nameSearch) && isEmpty(typeSearch) && isEmpty(descSearch) && isEmpty(groupSearch) && numMetadataNonEmpty == 0) return; // First do the metaDb searches, which can be done quickly for all tracks with db queries. struct hash *matchingTracks = NULL; if (numMetadataNonEmpty) { struct mdbObj *mdbObj, *mdbObjs = mdbObjRepeatedSearch(conn,mdbPairs,TRUE,FALSE); if (mdbObjs) { for (mdbObj = mdbObjs; mdbObj != NULL; mdbObj = mdbObj->next) { if (matchingTracks == NULL) matchingTracks = newHash(0); hashAddInt(matchingTracks, mdbObj->obj, 1); } mdbObjsFree(&mdbObjs); } if (matchingTracks == NULL) return; } // Set the word lists up once struct slName *nameList = NULL; if (!isEmpty(nameSearch)) nameList = slNameListOfUniqueWords(cloneString(nameSearch),TRUE); // TRUE means respect quotes struct slName *descList = NULL; if (!isEmpty(descSearch)) descList = slNameListOfUniqueWords(cloneString(descSearch),TRUE); struct group *group; for (group = groupList; group != NULL; group = group->next) { if (isEmpty(groupSearch) || sameString(group->name, groupSearch)) { if (group->trackList == NULL) continue; struct trackRef *tr; for (tr = group->trackList; tr != NULL; tr = tr->next) { struct track *track = tr->track; char *trackType = cloneFirstWord(track->tdb->type); // will be spilled if ((matchingTracks == NULL || hashLookup(matchingTracks, track->track) != NULL) && ( isEmpty(typeSearch) || (sameWord(typeSearch, trackType) && !tdbIsComposite(track->tdb))) && (isEmpty(nameSearch) || searchNameMatches(track->tdb, nameList)) && (isEmpty(descSearch) || searchDescriptionMatches(track->tdb, descList))) { if (track != NULL) { tracksFound++; refAdd(&tracks, track); } else warn("found group track is NULL."); } if (track->subtracks != NULL) { struct track *subTrack; for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) { trackType = cloneFirstWord(subTrack->tdb->type); // will be spilled if ( (matchingTracks == NULL || hashLookup(matchingTracks, subTrack->track) != NULL) && (isEmpty(typeSearch) || sameWord(typeSearch, trackType)) && (isEmpty(nameSearch) || searchNameMatches(subTrack->tdb, nameList)) && (isEmpty(descSearch) // subtracks inherit description || searchDescriptionMatches(subTrack->tdb, descList) || (tdbIsCompositeChild(subTrack->tdb) && subTrack->parent && searchDescriptionMatches(subTrack->parent->tdb, descList)))) { if (track != NULL) { tracksFound++; refAdd(&tracks, subTrack); } else warn("found subtrack is NULL."); } } } } } } if (measureTiming) measureTime("searched native tracks: "); if (includeHubResults) addHubSearchResults(nameList,descSearch); } #define MAX_FOUND_TRACKS 100 static void findTracksPageLinks(int tracksFound, int startFrom, int instance) { char id[256]; if (tracksFound <= MAX_FOUND_TRACKS) return; // Opener int willStartAt = 0; int curPage = (startFrom/MAX_FOUND_TRACKS) + 1; int endAt = startFrom+MAX_FOUND_TRACKS; if (endAt > tracksFound) endAt = tracksFound; hPrintf("Listing %d - %d of %d tracks   ", startFrom+1,endAt,tracksFound); // << and < if (startFrom >= MAX_FOUND_TRACKS) { safef(id, sizeof id, "ftpl%d-first", instance); hPrintf("« ", TRACK_SEARCH,TRACK_SEARCH_PAGER,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",0);", TRACK_SEARCH_PAGER); safef(id, sizeof id, "ftpl%d-prev", instance); willStartAt = startFrom - MAX_FOUND_TRACKS; hPrintf("  ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);", TRACK_SEARCH_PAGER,willStartAt); } // page number links int lastPage = (tracksFound/MAX_FOUND_TRACKS); if ((tracksFound % MAX_FOUND_TRACKS) > 0) lastPage++; int thisPage = curPage - 3; // Window of 3 pages above and below if (thisPage < 1) thisPage = 1; for (;thisPage <= lastPage && thisPage <= curPage + 3; thisPage++) { safef(id, sizeof id, "ftpl%d-%d", instance, thisPage); if (thisPage != curPage) { willStartAt = ((thisPage - 1) * MAX_FOUND_TRACKS); endAt = willStartAt+ MAX_FOUND_TRACKS; if (endAt > tracksFound) endAt = tracksFound; hPrintf(" %d ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id,thisPage,willStartAt+1,endAt,thisPage); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); } else hPrintf(" %d ",COLOR_DARKGREY,thisPage); } // > and >> if ((startFrom + MAX_FOUND_TRACKS) < tracksFound) { safef(id, sizeof id, "ftpl%d-next", instance); willStartAt = startFrom + MAX_FOUND_TRACKS; hPrintf("  ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); safef(id, sizeof id, "ftpl%d-last", instance); willStartAt = tracksFound - (tracksFound % MAX_FOUND_TRACKS); if (willStartAt == tracksFound) willStartAt -= MAX_FOUND_TRACKS; hPrintf(" »\n", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); } } static void displayFoundTracks(struct cart *cart, struct slRef *tracks, int tracksFound, enum sortBy sortBy) // Routine for displaying found tracks { char id[256]; char javascript[1024]; hPrintf(""); // This div allows the clear button to empty it } void doSearchTracks(struct group *groupList) { webIncludeResourceFile("ui.dropdownchecklist.css"); jsIncludeFile("ui.dropdownchecklist.js",NULL); // This line is needed to get the multi-selects initialized jsIncludeFile("ddcl.js",NULL); struct group *group; char *groups[128]; char *labels[128]; int numGroups = 1; groups[0] = ANYLABEL; labels[0] = ANYLABEL; char *nameSearch = cartOptionalString(cart, TRACK_SEARCH_ON_NAME); char *typeSearch = cartUsualString( cart, TRACK_SEARCH_ON_TYPE,ANYLABEL); char *simpleEntry = cartOptionalString(cart, TRACK_SEARCH_SIMPLE); char *descSearch = cartOptionalString(cart, TRACK_SEARCH_ON_DESCR); char *groupSearch = cartUsualString( cart, TRACK_SEARCH_ON_GROUP,ANYLABEL); boolean doSearch = sameString(cartOptionalString(cart, TRACK_SEARCH), "Search") || cartUsualInt(cart, TRACK_SEARCH_PAGER, -1) >= 0; boolean includeHubResults = cartUsualBoolean(cart, TRACK_SEARCH_ON_HUBS, TRUE); struct sqlConnection *conn = NULL; boolean metaDbExists = FALSE; if (!isHubTrack(database)) { conn = hAllocConn(database); metaDbExists = sqlTableExists(conn, "metaDb"); } int tracksFound = 0; int cols; char buf[512]; char *currentTab = cartUsualString(cart, TRACK_SEARCH_CURRENT_TAB, "simpleTab"); enum searchTab selectedTab = (sameString(currentTab, "advancedTab") ? advancedTab : simpleTab); // NOTE: could support quotes in simple tab by detecting quotes and choosing // to use doesNameMatch() || doesDescriptionMatch() if (selectedTab == simpleTab && !isEmpty(simpleEntry)) stripChar(simpleEntry, '"'); trackList = getTrackList(&groupList, -2); // global makeGlobalTrackHash(trackList); // NOTE: This is necessary when container cfg by '*' results in vis changes // This will handle composite/view override when subtrack specific vis exists, // AND superTrack reshaping. // Subtrack settings must be removed when composite/view settings are updated parentChildCartCleanup(trackList,cart,oldVars); slSort(&groupList, gCmpGroup); struct hash *superHash = hashNew(8); for (group = groupList; group != NULL; group = group->next) { groupTrackListAddSuper(cart, group, superHash, trackHash); if (group->trackList != NULL) { groups[numGroups] = cloneString(group->name); // a hub group's label is built from the hub's shortLabel and its groups.txt label labels[numGroups] = htmlEncode(group->label); numGroups++; if (numGroups >= ArraySize(groups)) internalErr(); } } hashFree(&superHash); -// on an assembly hub the organism and the freeze name both come from the hub +// On an assembly hub the organism and the freeze name both come from the hub, but they are +// not escaped here: webStartWrapperDetailedNoArgs puts the title through htmlTextOut, which +// escapes & < > and the double quote for us. Escaping it a second time printed the entity +// instead of the character, so every assembly with a '/' in its description read +// "(GRCh38/hg38)" in the blue bar. safef(buf, sizeof(buf),"Search for Tracks in the %s %s Assembly", - htmlEncode(organism), htmlEncode(hFreezeFromDb(database))); + organism, hFreezeFromDb(database)); webStartWrapperDetailedNoArgs(cart, database, "", buf, FALSE, FALSE, FALSE, FALSE); hPrintf("
"); hPrintf("
\n\n", hgTracksName(),TRACK_SEARCH_FORM,TRACK_SEARCH_FORM); cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo safef(buf, sizeof(buf), "%lu", clock1()); cgiMakeHiddenVar("hgt_", buf); // timestamps page to avoid browser cache hPrintf("\n", database); hPrintf("\n", TRACK_SEARCH_CURRENT_TAB, currentTab); hPrintf("\n",TRACK_SEARCH_DEL_ROW); hPrintf("\n",TRACK_SEARCH_ADD_ROW); hPrintf("\n",TRACK_SEARCH_PAGER); hPrintf("\n"); hPrintf("
\n"); hPrintf("
"); // Restricts to max-width:1000px; cgiDown(0.8); if (measureTiming) measureTime("Rendered tabs"); if (doSearch) { // Now search long startTime = clock1000(); if (hubIdsToUrls == NULL) hubIdsToUrls = hashNew(0); if (selectedTab==simpleTab && !isEmpty(simpleEntry)) simpleSearchForTracks(simpleEntry); else if (selectedTab==advancedTab) advancedSearchForTracks(conn,groupList,nameSearch,typeSearch,descSearch, groupSearch,mdbSelects, includeHubResults); if (measureTiming) fprintf(stdout, "Searched for tracks: %lu
", clock1000()-startTime); // Sort and Print results if (selectedTab!=filesTab) { enum sortBy sortBy = cartUsualInt(cart,TRACK_SEARCH_SORT,sbRelevance); tracksFound = slCount(tracks); if (tracksFound > 1) findTracksSort(&tracks,sortBy); displayFoundTracks(cart,tracks,tracksFound,sortBy); if (measureTiming) measureTime("Displayed found tracks"); } slPairFreeList(&mdbSelects); } hFreeConn(&conn); webNewSection("About Track Search"); if (metaDbExists) hPrintf("

Search for terms in track names, descriptions, groups, and ENCODE " "metadata. If multiple terms are entered, only tracks with all terms " "will be part of the results."); else hPrintf("

Search for terms in track descriptions, groups, and names. " "If multiple terms are entered, only tracks with all terms " "will be part of the results."); hPrintf("
more help

\n"); webEndSectionTables(); }