/* ** Main sub-menu for configuring the ticketing system. ** WEBPAGE: tktsetup */ void tktsetup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(0); return; } style_header("Ticket Setup"); cgi_printf("<table border=\"0\" cellspacing=\"20\">\n"); setup_menu_entry("Table", "tktsetup_tab", "Specify the schema of the \"ticket\" table in the database."); setup_menu_entry("Timeline", "tktsetup_timeline", "How to display ticket status in the timeline"); setup_menu_entry("Common", "tktsetup_com", "Common TH1 code run before all ticket processing."); setup_menu_entry("Change", "tktsetup_change", "The TH1 code run after a ticket is edited or created."); setup_menu_entry("New Ticket Page", "tktsetup_newpage", "HTML with embedded TH1 code for the \"new ticket\" webpage."); setup_menu_entry("View Ticket Page", "tktsetup_viewpage", "HTML with embedded TH1 code for the \"view ticket\" webpage."); setup_menu_entry("Edit Ticket Page", "tktsetup_editpage", "HTML with embedded TH1 code for the \"edit ticket\" webpage."); setup_menu_entry("Report List Page", "tktsetup_reportlist", "HTML with embedded TH1 code for the \"report list\" webpage."); setup_menu_entry("Report Template", "tktsetup_rpttplt", "The default ticket report format."); setup_menu_entry("Key Template", "tktsetup_keytplt", "The default color key for reports."); cgi_printf("</table>\n"); style_footer(); }
/* ** WEBPAGE: ambiguous ** URL: /ambiguous?name=UUID&src=WEBPAGE ** ** The UUID given by the name parameter is ambiguous. Display a page ** that shows all possible choices and let the user select between them. */ void ambiguous_page(void){ Stmt q; const char *zName = P("name"); const char *zSrc = P("src"); char *z; if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){ fossil_redirect_home(); } style_header("Ambiguous Artifact ID"); cgi_printf("<p>The artifact id <b>%h</b> is ambiguous and might\n" "mean any of the following:\n" "<ol>\n",(zName)); z = mprintf("%s", zName); canonical16(z, strlen(z)); db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); int rid = db_column_int(&q, 1); cgi_printf("<li><p><a href=\"%s/%T/%S\">\n" "%S</a> -\n",(g.zTop),(zSrc),(zUuid),(zUuid)); object_description(rid, 0, 0); cgi_printf("</p></li>\n"); } cgi_printf("</ol>\n"); style_footer(); }
/* ** WEBPAGE: /taglist */ void taglist_page(void){ Stmt q; login_check_credentials(); if( !g.perm.Read ){ login_needed(); } login_anonymous_available(); style_header("Tags"); style_submenu_element("Timeline", "Timeline", "tagtimeline"); cgi_printf("<h2>Non-propagating tags:</h2>\n"); db_prepare(&q, "SELECT substr(tagname,5)" " FROM tag" " WHERE EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=tag.tagid" " AND tagtype=1)" " AND tagname GLOB 'sym-*'" " ORDER BY tagname" ); cgi_printf("<ul>\n"); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); if( g.perm.Hyperlink ){ cgi_printf("<li>%z\n" "%h</a></li>\n",(xhref("class='taglink'","%R/timeline?t=%T",zName)),(zName)); }else{ cgi_printf("<li><span class=\"tagDsp\">%h</span></li>\n",(zName)); } } cgi_printf("</ul>\n"); db_finalize(&q); style_footer(); }
/* ** The aList[] array records the current nesting of <ul> and <ol>. ** aList[0] records the stack depth. (Max depth of 10). aList[1] ** is +1 if the outer layer is <ul> and -1 if the outer layer is <ol> ** aList[2] holds similar information for the second layer, and so forth. ** ** The iTarget parameter specifies the desired depth of the stack and ** whether the inner most level is <ul> or <ol> The absolute value of ** iTarget is the desired depth. iTarget is negative for <ol> on the ** inner layer and positive for <ul> on the inner layer. ** ** The routine outputs HTML to adjust the list nesting to the desired ** level. */ static void adjust_list_nesting(int *aList, int iTarget){ int iDepth = iTarget; if( iDepth<0 ) iDepth = 0x7fffffff & -iDepth; if( aList[0]==iDepth && iDepth>0 && aList[iDepth]*iTarget<0 ){ iDepth--; } while( aList[0]>iDepth ){ if( aList[aList[0]--]>0 ){ cgi_printf("</ul>\n"); }else{ cgi_printf("</ol>\n"); } } while( aList[0]<iDepth-1 ){ cgi_printf("<ul>\n"); aList[0]++; aList[aList[0]] = +1; } iDepth = iTarget; if( iDepth<0 ) iDepth = 0x7fffffff & -iDepth; if( aList[0]==iDepth-1 ){ if( iTarget<0 ){ cgi_printf("<ol>\n"); aList[iDepth] = -1; }else{ cgi_printf("<ul>\n"); aList[iDepth] = +1; } aList[0]++; } }
/* ** WEBPAGE: /tagtimeline */ void tagtimeline_page(void){ Stmt q; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } style_header("Tagged Check-ins"); style_submenu_element("List", "List", "taglist"); login_anonymous_available(); cgi_printf("<h2>Check-ins with non-propagating tags:</h2>\n"); db_prepare(&q, "%s AND blob.rid IN (SELECT rid FROM tagxref" " WHERE tagtype=1 AND srcid>0" " AND tagid IN (SELECT tagid FROM tag " " WHERE tagname GLOB 'sym-*'))" " ORDER BY event.mtime DESC", timeline_query_for_www() ); www_print_timeline(&q, 0, 0, 0, 0); db_finalize(&q); cgi_printf("<br />\n" "<script type=\"text/JavaScript\">\n" "function xin(id){\n" "}\n" "function xout(id){\n" "}\n" "</script>\n"); style_footer(); }
/* ** Common implementation for the ticket setup editor pages. */ static void tktsetup_generic( const char *zTitle, /* Page title */ const char *zDbField, /* Configuration field being edited */ const char *zDfltValue, /* Default text value */ const char *zDesc, /* Description of this field */ char *(*xText)(const char*), /* Validity test or NULL */ void (*xRebuild)(void), /* Run after successful update */ int height /* Height of the edit box */ ){ const char *z; int isSubmit; login_check_credentials(); if( !g.perm.Setup ){ login_needed(0); return; } if( PB("setup") ){ cgi_redirect("tktsetup"); } isSubmit = P("submit")!=0; z = P("x"); if( z==0 ){ z = db_get(zDbField, (char*)zDfltValue); } style_header("Edit %s", zTitle); if( P("clear")!=0 ){ login_verify_csrf_secret(); db_unset(zDbField, 0); if( xRebuild ) xRebuild(); cgi_redirect("tktsetup"); }else if( isSubmit ){ char *zErr = 0; login_verify_csrf_secret(); if( xText && (zErr = xText(z))!=0 ){ cgi_printf("<p class=\"tktsetupError\">ERROR: %h</p>\n",(zErr)); }else{ db_set(zDbField, z, 0); if( xRebuild ) xRebuild(); cgi_redirect("tktsetup"); } } cgi_printf("<form action=\"%s/%s\" method=\"post\"><div>\n",(g.zTop),(g.zPath)); login_insert_csrf_secret(); cgi_printf("<p>%s</p>\n" "<textarea name=\"x\" rows=\"%d\" cols=\"80\">%h</textarea>\n" "<blockquote><p>\n" "<input type=\"submit\" name=\"submit\" value=\"Apply Changes\" />\n" "<input type=\"submit\" name=\"clear\" value=\"Revert To Default\" />\n" "<input type=\"submit\" name=\"setup\" value=\"Cancel\" />\n" "</p></blockquote>\n" "</div></form>\n" "<hr />\n" "<h2>Default %s</h2>\n" "<blockquote><pre>\n" "%h\n" "</pre></blockquote>\n",(zDesc),(height),(z),(zTitle),(zDfltValue)); style_footer(); }
/* ** WEBPAGE: tktedit ** WEBPAGE: debug_tktedit ** ** Edit a ticket. The ticket is identified by the name CGI parameter. ** /tktedit is the official page. The /debug_tktedit page does the same ** thing except that it does not save the ticket change record when you ** press submit - it instead prints the ticket change record at the top ** of the page. The /debug_tktedit page is intended to be used when ** debugging ticket configurations. */ void tktedit_page(void){ const char *zScript; int nName; const char *zName; int nRec; login_check_credentials(); if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; } zName = P("name"); if( P("cancel") ){ cgi_redirectf("tktview?name=%T", zName); } style_header("Edit Ticket"); if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE || !validate16(zName,nName) ){ cgi_printf("<span class=\"tktError\">Not a valid ticket id: \\\"%h\\\"</span>\n",(zName)); style_footer(); return; } nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'", zName); if( nRec==0 ){ cgi_printf("<span class=\"tktError\">No such ticket: \\\"%h\\\"</span>\n",(zName)); style_footer(); return; } if( nRec>1 ){ cgi_printf("<span class=\"tktError\">%d tickets begin with:\n" "\\\"%h\\\"</span>\n",(nRec),(zName)); style_footer(); return; } if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); ticket_init(); getAllTicketFields(); initializeVariablesFromCGI(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); cgi_printf("<input type=\"hidden\" name=\"name\" value=\"%s\" />\n",(zName)); login_insert_csrf_secret(); zScript = ticket_editpage_code(); Th_Store("login", g.zLogin ? g.zLogin : "******"); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName)); return; } captcha_generate(0); cgi_printf("</form>\n"); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); style_footer(); }
/* ** Output nText characters zText as HTML. Do not allow markup other ** than the markup for which isAllowed() returns true. */ static void output_restricted_html(const char *zText, int nText){ int i, j, k; cgi_printf("<div>"); for(i=0; i<nText; i++){ if( zText[i]!='<' ) continue; k = 1 + (zText[i+1]=='/'); for(j=k; isalnum(zText[i+j]); j++){} if( isAllowed(&zText[i+k], j-k) ) continue; cgi_printf("%.*s<", i, zText); zText += i+1; nText -= i+1; i = -1; } cgi_printf("%.*s</div>", i, zText); }
/* ** For trouble-shooting purposes, render a dump of the aField[] table to ** the webpage currently under construction. */ static void showAllFields(void){ int i; cgi_printf("<font color=\"blue\">\n" "<p>Database fields:</p><ul>\n"); for(i=0; i<nField; i++){ cgi_printf("<li>aField[%d].zName = \"%h\";\n" "originally = \"%h\";\n" "currently = \"%h\"\";\n",(i),(aField[i].zName),(aField[i].zValue),(PD(aField[i].zName,""))); if( aField[i].zAppend ){ cgi_printf("zAppend = \"%h\";\n",(aField[i].zAppend)); } cgi_printf("mUsed = %d;\n",(aField[i].mUsed)); } cgi_printf("</ul></font>\n"); }
/* ** Output N characters of text from zText. */ static void put_htmlized_text(const char **pzText, int N){ if( N>0 ){ char *z = htmlize(*pzText, N); cgi_printf("%s", z); free(z); *pzText += N; } }
/* ** The pTkt object is a ticket change artifact. Output a detailed ** description of this object. */ void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){ int i; int wikiFlags = WIKI_NOBADLINKS; const char *zBlock = "<blockquote>"; const char *zEnd = "</blockquote>"; if( P("plaintext")!=0 ){ wikiFlags |= WIKI_LINKSONLY; zBlock = "<blockquote><pre class='verbatim'>"; zEnd = "</pre></blockquote>"; } if( zListType==0 ) zListType = "1"; cgi_printf("<ol type=\"%s\">\n",(zListType)); for(i=0; i<pTkt->nField; i++){ Blob val; const char *z; z = pTkt->aField[i].zName; blob_set(&val, pTkt->aField[i].zValue); if( z[0]=='+' ){ cgi_printf("<li>Appended to %h:%s\n",(&z[1]),(zBlock)); wiki_convert(&val, 0, wikiFlags); cgi_printf("%s</li>\n",(zEnd)); }else if( blob_size(&val)>50 || contains_newline(&val) ){ cgi_printf("<li>Change %h to:%s\n",(z),(zBlock)); wiki_convert(&val, 0, wikiFlags); cgi_printf("%s</li>\n",(zEnd)); }else{ cgi_printf("<li>Change %h to \"%h\"</li>\n",(z),(blob_str(&val))); } blob_reset(&val); } cgi_printf("</ol>\n"); }
/* ** WEBPAGE: tktnew ** WEBPAGE: debug_tktnew ** ** Enter a new ticket. The tktnew_template script in the ticket ** configuration is used. The /tktnew page is the official ticket ** entry page. The /debug_tktnew page is used for debugging the ** tktnew_template in the ticket configuration. /debug_tktnew works ** just like /tktnew except that it does not really save the new ticket ** when you press submit - it just prints the ticket artifact at the ** top of the screen. */ void tktnew_page(void){ const char *zScript; char *zNewUuid = 0; login_check_credentials(); if( !g.perm.NewTkt ){ login_needed(); return; } if( P("cancel") ){ cgi_redirect("home"); } style_header("New Ticket"); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); ticket_init(); initializeVariablesFromCGI(); getAllTicketFields(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); login_insert_csrf_secret(); if( P("date_override") && g.perm.Setup ){ cgi_printf("<input type=\"hidden\" name=\"date_override\" value=\"%h\">\n",(P("date_override"))); } zScript = ticket_newpage_code(); Th_Store("login", g.zLogin ? g.zLogin : "******"); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid, 0); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zNewUuid)); return; } captcha_generate(0); cgi_printf("</form>\n"); if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); style_footer(); }
/* ** WEBPAGE: tktsetup_timeline */ void tktsetup_timeline_page(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(0); return; } if( P("setup") ){ cgi_redirect("tktsetup"); } style_header("Ticket Display On Timelines"); db_begin_transaction(); cgi_printf("<form action=\"%s/tktsetup_timeline\" method=\"post\"><div>\n",(g.zTop)); login_insert_csrf_secret(); cgi_printf("<hr />\n"); entry_attribute("Ticket Title", 40, "ticket-title-expr", "t", "title", 0); cgi_printf("<p>An SQL expression in a query against the TICKET table that will\n" "return the title of the ticket for display purposes.</p>\n"); cgi_printf("<hr />\n"); entry_attribute("Ticket Status", 40, "ticket-status-column", "s", "status", 0); cgi_printf("<p>The name of the column in the TICKET table that contains the ticket\n" "status in human-readable form. Case sensitive.</p>\n"); cgi_printf("<hr />\n"); entry_attribute("Ticket Closed", 40, "ticket-closed-expr", "c", "status='Closed'", 0); cgi_printf("<p>An SQL expression that evaluates to true in a TICKET table query if\n" "the ticket is closed.</p>\n"); cgi_printf("<hr />\n" "<p>\n" "<input type=\"submit\" name=\"submit\" value=\"Apply Changes\" />\n" "<input type=\"submit\" name=\"setup\" value=\"Cancel\" />\n" "</p>\n" "</div></form>\n"); db_end_transaction(0); style_footer(); }
/* ** Draw the header. */ void style_header(const char *zTitleFormat, ...){ va_list ap; char *zTitle; const char *zHeader = db_get("header", (char*)zDefaultHeader); login_check_credentials(); va_start(ap, zTitleFormat); zTitle = vmprintf(zTitleFormat, ap); va_end(ap); cgi_destination(CGI_HEADER); cgi_printf("%s","<!DOCTYPE html>"); if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1); /* Generate the header up through the main menu */ Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); Th_Store("title", zTitle); Th_Store("baseurl", g.zBaseURL); Th_Store("home", g.zTop); Th_Store("index_page", db_get("index-page","/home")); Th_Store("current_page", g.zPath); Th_Store("release_version", RELEASE_VERSION); Th_Store("manifest_version", MANIFEST_VERSION); Th_Store("manifest_date", MANIFEST_DATE); Th_Store("compiler_name", COMPILER_NAME); if( g.zLogin ){ Th_Store("login", g.zLogin); } if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1); Th_Render(zHeader); if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1); Th_Unstore("title"); /* Avoid collisions with ticket field names */ cgi_destination(CGI_BODY); g.cgiOutput = 1; headerHasBeenGenerated = 1; sideboxUsed = 0; }
/* ** Output Wiki text while inserting the proper HTML control codes. ** The following formatting conventions are implemented: ** ** * Characters with special meaning to HTML are escaped. ** ** * Blank lines results in a paragraph break. ** ** * Paragraphs where the first line is indented by two or more ** spaces are shown verbatim. None of the following rules apply ** to verbatim text. ** ** * Lines beginning with "*: " begin a bullet in a bullet list. ** ** * Lines beginning with "1: " begin an item in an enumerated list. ** ** * Paragraphs beginning with "_: " are indented. ** ** * Multiple colons can be used in *:, 1:, and _: for multiple ** levels of indentation. ** ** * Text within _..._ is italic and text in *...* is bold. ** Text with in **...** or ***...*** bold with a larger font. ** ** * Wiki pages names (Words in initial caps) are enclosed in an ** appropriate hyperlink. ** ** * Words that begin with "http:", "https:", "ftp:", or "mailto:" ** are enclosed in an appropriate hyperlink. ** ** * Text of the form "#NNN" where NNN is a valid ticket number ** is converted into a hyperlink to the corresponding ticket. ** ** * Text of the form "[NNN]" where NNN is a valid check-in number ** becomes a hyperlink to the checkin. ** ** * {quote: XYZ} renders XYZ with all special meanings for XYZ escaped. ** ** * {link: URL TEXT} renders TEXT with a link to URL. URL can be ** relative. ** ** * {linebreak} renders a linebreak. ** ** * {image: URL ALT} renders an in-line image from URL. URL can be ** relative or it can be the name of an attachment to zPageId. ** {leftimage: URL ALT} and {rightimage: URL ALT} create wrap-around ** images at the left or right margin. ** ** * {clear} skips down the page far enough to clear any wrap-around ** images. ** ** * Text between <html>...</html> is interpreted as HTML. A restricted ** subset of tags are supported - things like forms and javascript are ** intentionally excluded. The initial <html> must occur at the ** beginning of a paragraph. */ void output_wiki( const char *zText, /* The text to be formatted */ const char *zLinkSuffix, /* Suffix added to hyperlinks to Wiki */ const char *zPageId /* Name of current page */ ){ int i, j, k; int aList[20]; /* See adjust_list_nesting for details */ int inPRE = 0; int inB = 0; int inI = 0; int v; int wordStart = 1; /* At the start of a word */ int lineStart = 1; /* At the start of a line */ int paraStart = 1; /* At the start of a paragraph */ const char *zEndB; /* Text used to end a run of bold */ char **azAttach; /* Attachments to zPageId */ static int once = 1; static int nTicket, nCommit; if( once ){ nTicket = atoi(db_short_query("SELECT max(tn) FROM ticket")); nCommit = atoi(db_short_query("SELECT max(cn) FROM chng")); once = 0; } i = 0; aList[0] = 0; azAttach = 0; zEndB = ""; while( zText[i] ){ char *z; int n; Markup sMarkup; int c = zText[i]; /* Text between <html>...</html> is interpreted as HTML. */ if( c=='<' && (n = is_html(&zText[i]))>0 ){ put_htmlized_text(&zText, i); zText += 6; output_restricted_html(zText, n-13); zText += n - 6; i = 0; continue; } /* Markup may consist of special strings contained in curly braces. ** Examples: "{linebreak}" or "{quote: *:}" */ if( c=='{' && is_markup(&zText[i], &sMarkup) ){ /* ** Markup of the form "{linebreak}" forces a line break. */ if( sMarkup.lenType==9 && strncmp(sMarkup.zType,"linebreak",9)==0 ){ put_htmlized_text(&zText, i); zText += sMarkup.lenTotal; i = 0; cgi_printf("<br>\n"); wordStart = lineStart = paraStart = 0; continue; } /* ** Markup of the form "{clear}" moves down past any left or right ** aligned images. */ if( sMarkup.lenType==5 && strncmp(sMarkup.zType,"clear",5)==0 ){ put_htmlized_text(&zText, i); zText += sMarkup.lenTotal; i = 0; cgi_printf("<br clear=\"both\">\n"); wordStart = lineStart = paraStart = 0; continue; } /* ** Markup of the form "{quote: ABC}" writes out the text ABC exactly ** as it appears. This can be used to escape special meanings ** associated with ABC. */ if( sMarkup.lenType==5 && strncmp(sMarkup.zType,"quote",5)==0 ){ int n; put_htmlized_text(&zText, i); if( sMarkup.zKey==sMarkup.zArgs ){ n = sMarkup.lenKey; }else{ n = &sMarkup.zArgs[sMarkup.lenArgs] - sMarkup.zKey; } put_htmlized_text(&sMarkup.zKey, n); zText += sMarkup.lenTotal; i = 0; wordStart = lineStart = paraStart = 0; continue; } /* ** Markup of the form "{link: TO TEXT}" creates a hyperlink to TO. ** The hyperlink appears on the screen as TEXT. TO can be a any URL, ** including a relative URL such as "chngview?cn=123". */ if( sMarkup.lenType==4 && strncmp(sMarkup.zType,"link",4)==0 ){ put_htmlized_text(&zText, i); cgi_printf("<a href=\"%.*s\">", sMarkup.lenKey, sMarkup.zKey); put_htmlized_text(&sMarkup.zArgs, sMarkup.lenArgs); cgi_printf("</a>"); zText += sMarkup.lenTotal; i = 0; wordStart = lineStart = paraStart = 0; continue; } /* ** Markup of the form "{image: URL ALT}" creates an in-line image to ** URL with ALT as the alternate text. URL can be relative (for example ** the URL of an attachment. ** ** If the URL is the name of an attachment, then automatically ** convert it to the correct URL for that attachment. */ if( (sMarkup.lenType==5 && strncmp(sMarkup.zType,"image",5)==0) || (sMarkup.lenType==9 && strncmp(sMarkup.zType,"leftimage",9)==0) || (sMarkup.lenType==10 && strncmp(sMarkup.zType,"rightimage",10)==0) ){ char *zUrl = 0; const char *zAlign; char *zAlt = htmlize(sMarkup.zArgs, sMarkup.lenArgs); if( azAttach==0 && zPageId!=0 ){ azAttach = (char **) db_query("SELECT fname, atn FROM attachment " "WHERE tn='%q'", zPageId); } if( azAttach ){ int ix; for(ix=0; azAttach[ix]; ix+=2){ if( strncmp(azAttach[ix],sMarkup.zKey,sMarkup.lenKey)==0 ){ free(zUrl); zUrl = mprintf("attach_get/%s/%h", azAttach[ix+1], azAttach[ix]); break; } } } if( zUrl==0 ){ zUrl = htmlize(sMarkup.zKey, sMarkup.lenKey); } put_htmlized_text(&zText, i); switch( sMarkup.zType[0] ){ case 'l': case 'L': zAlign = " align=\"left\""; break; case 'r': case 'R': zAlign = " align=\"right\""; break; default: zAlign = ""; break; } cgi_printf("<img src=\"%s\" alt=\"%s\"%s>", zUrl, zAlt, zAlign); free(zUrl); free(zAlt); zText += sMarkup.lenTotal; i = 0; wordStart = lineStart = paraStart = 0; continue; } } if( paraStart ){ put_htmlized_text(&zText, i); /* Blank lines at the beginning of a paragraph are ignored. */ if( isspace(c) && (j = is_blank_line(&zText[i]))>0 ){ zText += j; continue; } /* If the first line of a paragraph begins with a tab or with two ** or more spaces, then that paragraph is printed verbatim. */ if( c=='\t' || (c==' ' && (zText[i+1]==' ' || zText[i+1]=='\t')) ){ if( !inPRE ){ if( inB ){ cgi_printf(zEndB); inB=0; } if( inI ){ cgi_printf("</i>"); inI=0; } adjust_list_nesting(aList, 0); cgi_printf("<pre>\n"); inPRE = 1; } } } /* end if( paraStart ) */ if( lineStart ){ /* Blank lines in the middle of text cause a paragraph break */ if( isspace(c) && (j = is_blank_line(&zText[i]))>0 ){ put_htmlized_text(&zText, i); zText += j; if( inB ){ cgi_printf(zEndB); inB=0; } if( inI ){ cgi_printf("</i>"); inI=0; } if( inPRE ){ cgi_printf("</pre>\n"); inPRE = 0; } is_list_elem(zText, &k); if( abs(k)<aList[0] ) adjust_list_nesting(aList, k); if( zText[0]!=0 ){ cgi_printf("\n<p>"); } wordStart = lineStart = paraStart = 1; i = 0; continue; } } /* end if( lineStart ) */ if( lineStart && !inPRE ){ /* If we are not in verbatim text and a line begins with "*:", then ** generate a bullet. Or if the line begins with "NNN:" where NNN ** is a number, generate an enumeration item. */ if( (j = is_list_elem(&zText[i], &k))>0 ){ put_htmlized_text(&zText, i); adjust_list_nesting(aList, k); if( zText[0]!='_' ) cgi_printf("<li>"); zText += j; i = 0; wordStart = 1; lineStart = paraStart = 0; continue; } /* Four or more "-" characters on at the beginning of a line that ** contains no other text results in a horizontal rule. */ if( (c=='-' || c=='=') && (j = is_horizontal_rule(&zText[i]))>0 ){ put_htmlized_text(&zText, i); adjust_list_nesting(aList, 0); cgi_printf("<hr>\n"); zText += j; if( *zText ) zText++; i = 0; lineStart = wordStart = 1; paraStart = 1; continue; } } /* end if( lineStart && !inPre ) */ if( wordStart && !inPRE ){ /* A wiki name at the beginning of a word which is not in verbatim ** text generates a hyperlink to that wiki page. ** ** Special case: If the name is in CamelCase but ends with a "_", then ** suppress the "_" and do not generate the hyperlink. This allows ** CamelCase words that are not wiki page names to appear in text. */ if( g.okRdWiki && isupper(c) && (j = is_wiki_name(&zText[i]))>0 ){ put_htmlized_text(&zText, i); cgi_printf("<a href=\"wiki?p=%.*s%s\">%.*s</a>", j, zText, zLinkSuffix, j, zText); zText += j; i = 0; wordStart = lineStart = paraStart = 0; continue; } /* A "_" at the beginning of a word puts us into an italic font. */ if( c=='_' && !inB && !inI && font_terminator(&zText[i+1],c,1) ){ put_htmlized_text(&zText, i); i = 0; zText++; cgi_printf("<i>"); inI = 1; continue; } /* A "*" at the beginning of a word puts us into a bold font. */ if( c=='*' && !inB && !inI && (j = count_stars(&zText[i]))>=1 && j<=3 && font_terminator(&zText[i+j],c,j) ){ const char *zBeginB = ""; put_htmlized_text(&zText, i); i = 0; zText += j; switch( j ){ case 1: zBeginB = "<b>"; zEndB = "</b>"; break; case 2: zBeginB = "<big><b>"; zEndB = "</b></big>"; break; case 3: zBeginB = "<big><big><b>"; zEndB = "</b></big></big>"; break; } cgi_printf(zBeginB); inB = j; continue; } /* Words that begin with "http:" or "https:" or "ftp:" or "mailto:" ** become hyperlinks. */ if( (c=='h' || c=='f' || c=='m') && (j=is_url(&zText[i]))>0 ){ put_htmlized_text(&zText, i); z = htmlize(zText, j); if( is_image(z, strlen(z)) ){ cgi_printf("<img src=\"%s\" alt=\"%s\">", z, z); }else{ cgi_printf("<a href=\"%s\">%s</a>", z, z); } free(z); zText += j; i = 0; wordStart = lineStart = paraStart = 0; continue; } /* If the user has read permission on tickets and a word is of the ** form "#NNN" where NNN is a sequence of digits, then generate a ** hyperlink to ticket number NNN. */ if( c=='#' && g.okRead && (j = ndigit(&zText[i+1]))>0 && is_eow(&zText[i+1+j],0) && (v = atoi(&zText[i+1]))>0 && v<=nTicket ){ put_htmlized_text(&zText, i); cgi_printf("<a href=\"tktview?tn=%d\">#%d</a>", v, v); zText += j; if( *zText ) zText++; i = 0; wordStart = lineStart = paraStart = 0; continue; } /* If the user has checkout permissions and a word is of the form ** "[NNN]" where NNN is a checkin number, then generate a hyperlink ** to check-in NNN. */ if( c=='[' && g.okCheckout && (j = ndigit(&zText[i+1]))>0 && is_eow(&zText[i+j+2],0) && (v = atoi(&zText[i+1]))>0 && v<=nCommit && zText[i+j+1]==']' ){ put_htmlized_text(&zText, i); cgi_printf("<a href=\"chngview?cn=%d\">[%d]</a>", v, v); zText += j+1; if( *zText ) zText++; i = 0; wordStart = lineStart = paraStart = 0; continue; } } /* end if( wordStart && !inPre ) */ /* A "*" or a "_" at the end of a word takes us out of bold or ** italic mode. */ if( inB && c=='*' && !isspace(zText[i-1]) && zText[i-1]!='*' && (j = count_stars(&zText[i]))==inB && is_eow(&zText[i+j],0) ){ inB = 0; put_htmlized_text(&zText, i); i = 0; zText += j; cgi_printf(zEndB); continue; } if( inI && c=='_' && !isspace(zText[i-1]) && is_eow(&zText[i+1],0) ){ put_htmlized_text(&zText, i); i = 0; zText++; inI = 0; cgi_printf("</i>"); continue; } if( wordStart ){ wordStart = isspace(c) || c=='(' || c=='"'; }else{ wordStart = isspace(c); } lineStart = c=='\n'; paraStart = 0; i++; } if( zText[0] ) cgi_printf("%h", zText); if( inB ) cgi_printf("%s\n",zEndB); if( inI ) cgi_printf("</i>\n"); adjust_list_nesting(aList, 0); if( inPRE ) cgi_printf("</pre>\n"); }
/* ** Subscript command: submit_ticket ** ** Construct and submit a new ticket artifact. The fields of the artifact ** are the names of the columns in the TICKET table. The content is ** taken from TH variables. If the content is unchanged, the field is ** omitted from the artifact. Fields whose names begin with "private_" ** are concealed using the db_conceal() function. */ static int submitTicketCmd( Th_Interp *interp, void *pUuid, int argc, const char **argv, int *argl ){ char *zDate; const char *zUuid; int i; int nJ = 0; Blob tktchng, cksum; login_verify_csrf_secret(); if( !captcha_is_correct() ){ cgi_printf("<p class=\"generalError\">Error: Incorrect security code.</p>\n"); return TH_OK; } zUuid = (const char *)pUuid; blob_zero(&tktchng); zDate = date_in_standard_format("now"); blob_appendf(&tktchng, "D %s\n", zDate); free(zDate); for(i=0; i<nField; i++){ if( aField[i].zAppend ){ blob_appendf(&tktchng, "J +%s %z\n", aField[i].zName, fossilize(aField[i].zAppend, -1)); ++nJ; } } for(i=0; i<nField; i++){ const char *zValue; int nValue; if( aField[i].zAppend ) continue; zValue = Th_Fetch(aField[i].zName, &nValue); if( zValue ){ while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) || memcmp(zValue, aField[i].zValue, nValue)!=0 || strlen(aField[i].zValue)!=nValue ){ if( memcmp(aField[i].zName, "private_", 8)==0 ){ zValue = db_conceal(zValue, nValue); blob_appendf(&tktchng, "J %s %s\n", aField[i].zName, zValue); }else{ blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue); } nJ++; } } } if( *(char**)pUuid ){ zUuid = db_text(0, "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%q*'", P("name") ); }else{ zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); } *(const char**)pUuid = zUuid; blob_appendf(&tktchng, "K %s\n", zUuid); blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : ""); md5sum_blob(&tktchng, &cksum); blob_appendf(&tktchng, "Z %b\n", &cksum); if( nJ==0 ){ blob_reset(&tktchng); return TH_OK; } if( g.zPath[0]=='d' ){ /* If called from /debug_tktnew or /debug_tktedit... */ cgi_printf("<font color=\"blue\">\n" "<p>Ticket artifact that would have been submitted:</p>\n" "<blockquote><pre>%h</pre></blockquote>\n" "<hr /></font>\n",(blob_str(&tktchng))); return TH_OK; }else{ if( g.thTrace ){ Th_Trace("submit_ticket {\n<blockquote><pre>\n%h\n</pre></blockquote>\n" "}<br />\n", blob_str(&tktchng)); } ticket_put(&tktchng, zUuid, (g.perm.ModTkt==0 && db_get_boolean("modreq-tkt",0)==1)); } return ticket_change(); }
/* ** WEBPAGE: leaves ** ** Find leaves of all branches. */ void leaves_page(void){ Blob sql; Stmt q; int showAll = P("all")!=0; int showClosed = P("closed")!=0; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } if( !showAll ){ style_submenu_element("All", "All", "leaves?all"); } if( !showClosed ){ style_submenu_element("Closed", "Closed", "leaves?closed"); } if( showClosed || showAll ){ style_submenu_element("Open", "Open", "leaves"); } style_header("Leaves"); login_anonymous_available(); style_sidebox_begin("Nomenclature:", "33%"); cgi_printf("<ol>\n" "<li> A <div class=\"sideboxDescribed\">leaf</div>\n" "is a check-in with no descendants in the same branch.</li>\n" "<li> An <div class=\"sideboxDescribed\">open leaf</div>\n" "is a leaf that does not have a \"closed\" tag\n" "and is thus assumed to still be in use.</li>\n" "<li> A <div class=\"sideboxDescribed\">closed leaf</div>\n" "has a \"closed\" tag and is thus assumed to\n" "be historical and no longer in active use.</li>\n" "</ol>\n"); style_sidebox_end(); if( showAll ){ cgi_printf("<h1>All leaves, both open and closed:</h1>\n"); }else if( showClosed ){ cgi_printf("<h1>Closed leaves:</h1>\n"); }else{ cgi_printf("<h1>Open leaves:</h1>\n"); } blob_zero(&sql); blob_append(&sql, timeline_query_for_www(), -1); blob_appendf(&sql, " AND blob.rid IN leaf"); if( showClosed ){ blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); }else if( !showAll ){ blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); blob_reset(&sql); www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0); db_finalize(&q); cgi_printf("<br />\n" "<script type=\"text/JavaScript\">\n" "function xin(id){\n" "}\n" "function xout(id){\n" "}\n" "</script>\n"); style_footer(); }
/* ** WEBPAGE: tkttimeline ** URL: /tkttimeline?name=TICKETUUID&y=TYPE ** ** Show the change history for a single ticket in timeline format. */ void tkttimeline_page(void){ Stmt q; char *zTitle; char *zSQL; const char *zUuid; char *zFullUuid; int tagid; char zGlobPattern[50]; const char *zType; login_check_credentials(); if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zType = PD("y","a"); if( zType[0]!='c' ){ style_submenu_element("Check-ins", "Check-ins", "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); }else{ style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%T", g.zTop, zUuid); } style_submenu_element("History", "History", "%s/tkthistory/%s", g.zTop, zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); if( zType[0]=='c' ){ zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid); }else{ zTitle = mprintf("Timeline Of Ticket %h", zUuid); } style_header(zTitle); free(zTitle); sqlite3_snprintf(6, zGlobPattern, "%s", zUuid); canonical16(zGlobPattern, strlen(zGlobPattern)); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ cgi_printf("No such ticket: %h\n",(zUuid)); style_footer(); return; } zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d", tagid); if( zType[0]=='c' ){ zSQL = mprintf( "%s AND event.objid IN " " (SELECT srcid FROM backlink WHERE target GLOB '%.4s*' " "AND '%s' GLOB (target||'*')) " "ORDER BY mtime DESC", timeline_query_for_www(), zFullUuid, zFullUuid ); }else{ zSQL = mprintf( "%s AND event.objid IN " " (SELECT rid FROM tagxref WHERE tagid=%d" " UNION SELECT srcid FROM backlink" " WHERE target GLOB '%.4s*'" " AND '%s' GLOB (target||'*')" " UNION SELECT attachid FROM attachment" " WHERE target=%Q) " "ORDER BY mtime DESC", timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid ); } db_prepare(&q, zSQL); free(zSQL); www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, 0); db_finalize(&q); style_footer(); }
/* ** WEBPAGE: tkthistory ** URL: /tkthistory?name=TICKETUUID ** ** Show the complete change history for a single ticket */ void tkthistory_page(void){ Stmt q; char *zTitle; const char *zUuid; int tagid; int nChng = 0; login_check_credentials(); if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zTitle = mprintf("History Of Ticket %h", zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins", "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid); if( P("plaintext")!=0 ){ style_submenu_element("Formatted", "Formatted", "%R/tkthistory/%S", zUuid); }else{ style_submenu_element("Plaintext", "Plaintext", "%R/tkthistory/%S?plaintext", zUuid); } style_header(zTitle); free(zTitle); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ cgi_printf("No such ticket: %h\n",(zUuid)); style_footer(); return; } db_prepare(&q, "SELECT datetime(mtime%s), objid, uuid, NULL, NULL, NULL" " FROM event, blob" " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" " AND blob.rid=event.objid" " UNION " "SELECT datetime(mtime%s), attachid, uuid, src, filename, user" " FROM attachment, blob" " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" " AND blob.rid=attachid" " ORDER BY 1", timeline_utc(), tagid, timeline_utc(), tagid ); while( db_step(&q)==SQLITE_ROW ){ Manifest *pTicket; char zShort[12]; const char *zDate = db_column_text(&q, 0); int rid = db_column_int(&q, 1); const char *zChngUuid = db_column_text(&q, 2); const char *zFile = db_column_text(&q, 4); memcpy(zShort, zChngUuid, 10); zShort[10] = 0; if( nChng==0 ){ cgi_printf("<ol>\n"); } nChng++; if( zFile!=0 ){ const char *zSrc = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 5); if( zSrc==0 || zSrc[0]==0 ){ cgi_printf("\n" "<li><p>Delete attachment \"%h\"\n",(zFile)); }else{ cgi_printf("\n" "<li><p>Add attachment\n" "\"%z%s</a>\"\n",(href("%R/artifact/%S",zSrc)),(zFile)); } cgi_printf("[%z%s</a>]\n" "(rid %d) by\n",(href("%R/artifact/%T",zChngUuid)),(zShort),(rid)); hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, ".</p>"); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET, 0); if( pTicket ){ cgi_printf("\n" "<li><p>Ticket change\n" "[%z%s</a>]\n" "(rid %d) by\n",(href("%R/artifact/%T",zChngUuid)),(zShort),(rid)); hyperlink_to_user(pTicket->zUser,zDate," on"); hyperlink_to_date(zDate, ":"); cgi_printf("</p>\n"); ticket_output_change_artifact(pTicket, "a"); } manifest_destroy(pTicket); } } db_finalize(&q); if( nChng ){ cgi_printf("</ol>\n"); } style_footer(); }
/* ** Main sub-menu for configuring the transfer system. ** WEBPAGE: xfersetup */ void xfersetup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(0); return; } style_header("Transfer Setup"); cgi_printf("<table border=\"0\" cellspacing=\"20\">\n"); setup_menu_entry("Common", "xfersetup_com", "Common TH1 code run before all transfer request processing."); setup_menu_entry("Push", "xfersetup_push", "Specific TH1 code to run after \"push\" transfer requests."); setup_menu_entry("Commit", "xfersetup_commit", "Specific TH1 code to run after processing a commit."); setup_menu_entry("Ticket", "xfersetup_ticket", "Specific TH1 code to run after processing a ticket change."); cgi_printf("</table>\n"); url_parse(0, 0); if( g.url.protocol ){ unsigned syncFlags; const char *zButton; char *zWarning; if( db_get_boolean("dont-push", 0) ){ syncFlags = SYNC_PULL; zButton = "Pull"; zWarning = 0; }else{ syncFlags = SYNC_PUSH | SYNC_PULL; zButton = "Synchronize"; zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.", g.url.canonical); } if( P("sync") ){ user_select(); url_enable_proxy(0); client_sync(syncFlags, 0, 0); } cgi_printf("<p>Press the %h button below to synchronize with the\n" "\"%h\" repository now. This may be useful when\n" "testing the various transfer scripts.</p>\n" "<p>You can use the \"http -async\" command in your scripts, but\n" "make sure the \"th1-uri-regexp\" setting is set first.</p>\n",(zButton),(g.url.canonical)); if( zWarning ){ cgi_printf("\n" "<big><b>%h</b></big>\n",(zWarning)); free(zWarning); } cgi_printf("\n" "<blockquote>\n" "<form method=\"post\" action=\"%s/%s\"><div>\n",(g.zTop),(g.zPath)); login_insert_csrf_secret(); cgi_printf("<input type=\"submit\" name=\"sync\" value=\"%h\" />\n" "</div></form>\n" "</blockquote>\n" "\n",(zButton)); } style_footer(); }
/* ** WEBPAGE: sitemap ** ** Show an incomplete list of web pages offered by the Fossil web engine. */ void sitemap_page(void){ login_check_credentials(); style_header("Site Map"); style_adunit_config(ADUNIT_RIGHT_OK); cgi_printf("<p>\n" "The following links are just a few of the many web-pages available for\n" "this Fossil repository:\n" "</p>\n" "\n" "<ul>\n" "<li>%zHome Page</a>\n" " <ul>\n" " <li>%zSearch Project Documentation</a></li>\n" " </ul></li>\n" "<li>%zFile Browser</a></li>\n" " <ul>\n" " <li>%zTree-view,\n" " Trunk Check-in</a></li>\n" " <li>%zFlat-view</a></li>\n" " <li>%zFile ages for Trunk</a></li>\n" "</ul>\n" "<li>%zProject Timeline</a></li>\n" "<ul>\n" " <li>%zFirst 10 \n" " check-ins</a></li>\n" " <li>%zAll check-ins with file name\n" " changes</a></li>\n" " <li>%zActivity Reports</a></li>\n" "</ul>\n" "<li>%zBranches</a></li>\n" "<ul>\n" " <li>%zLeaf Check-ins</a></li>\n" " <li>%zList of Tags</a></li>\n" "</ul>\n" "</li>\n" "<li>%zWiki</a>\n" " <ul>\n" " <li>%zWiki Search</a></li>\n" " <li>%zList of Wiki Pages</a></li>\n" " <li>%zRecent activity</a></li>\n" " <li>%zWiki Formatting Rules</a></li>\n" " <li>%zMarkdown Formatting Rules</a></li>\n" " <li>%zSandbox</a></li>\n" " <li>%zList of Attachments</a></li>\n" " </ul>\n" "</li>\n" "<li>%zTickets</a>\n" " <ul>\n" " <li>%zTicket Search</a></li>\n" " <li>%zRecent activity</a></li>\n" " <li>%zList of Attachments</a></li>\n" " </ul>\n" "</li>\n" "<li>%zFull-Text Search</a></li>\n" "<li>%zLogin/Logout/Change Password</a></li>\n" "<li>%zRepository Status</a>\n" " <ul>\n" " <li>%zCollisions on SHA1 hash\n" " prefixes</a></li>\n" " <li>%zList of URLs used to access\n" " this repository</a></li>\n" " <li>%zList of Artifacts</a></li>\n" " </ul></li>\n" "<li>On-line Documentation\n" " <ul>\n" " <li>%zList of All Commands and Web Pages</a></li>\n" " <li>%zAll \"help\" text on a single page</a></li>\n" " <li>%zFilename suffix to mimetype map</a></li>\n" " </ul></li>\n" "<li>%zAdministration Pages</a>\n" " <ul>\n" " <li>%zPending Moderation Requests</a></li>\n" " <li>%zAdmin log</a></li>\n" " <li>%zStatus of the web-page cache</a></li>\n" " </ul></li>\n" "<li>Test Pages\n" " <ul>\n" " <li>%zCGI Environment Test</a></li>\n" " <li>%zList of \"Timewarp\" Check-ins</a></li>\n" " <li>%zList of file renames</a></li>\n" " <li>%zPage to experiment with the automatic\n" " colors assigned to branch names</a>\n" " </ul></li>\n" "</ul></li>\n",(href("%R/home")),(href("%R/docsrc")),(href("%R/tree")),(href("%R/tree?type=tree&ci=trunk")),(href("%R/tree?type=flat")),(href("%R/fileage?name=trunk")),(href("%R/timeline?n=200")),(href("%R/timeline?a=1970-01-01&y=ci&n=10")),(href("%R/timeline?n=all&namechng")),(href("%R/reports")),(href("%R/brlist")),(href("%R/leaves")),(href("%R/taglist")),(href("%R/wikihelp")),(href("%R/wikisrch")),(href("%R/wcontent")),(href("%R/timeline?y=w")),(href("%R/wiki_rules")),(href("%R/md_rules")),(href("%R/wiki?name=Sandbox")),(href("%R/attachlist")),(href("%R/reportlist")),(href("%R/tktsrch")),(href("%R/timeline?y=t")),(href("%R/attachlist")),(href("%R/search")),(href("%R/login")),(href("%R/stat")),(href("%R/hash-collisions")),(href("%R/urllist")),(href("%R/bloblist")),(href("%R/help")),(href("%R/test-all-help")),(href("%R/mimetype_list")),(href("%R/setup")),(href("%R/modreq")),(href("%R/admin_log")),(href("%R/cachestat")),(href("%R/test_env")),(href("%R/test_timewarps")),(href("%R/test-rename-list")),(href("%R/hash-color-test"))); style_footer(); }