Index: src/configure.c ================================================================== --- src/configure.c +++ src/configure.c @@ -83,13 +83,13 @@ */ static struct { const char *zName; /* Name of the configuration parameter */ int groupMask; /* Which config groups is it part of */ } aConfig[] = { - { "css", CONFIGSET_CSS }, - { "header", CONFIGSET_SKIN }, - { "footer", CONFIGSET_SKIN }, + { "css", CONFIGSET_CSS }, /* Keep all CSS and SKIN in */ + { "header", CONFIGSET_SKIN }, /* in sync with db_skin_name() */ + { "footer", CONFIGSET_SKIN }, /* in db.c */ { "logo-mimetype", CONFIGSET_SKIN }, { "logo-image", CONFIGSET_SKIN }, { "background-mimetype", CONFIGSET_SKIN }, { "background-image", CONFIGSET_SKIN }, { "index-page", CONFIGSET_SKIN }, Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -1963,10 +1963,64 @@ } /* Prefer the versioned setting */ return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting; } + +/* +** Translate a local CONFIG parameter name based on the g.zSkin setting +** and return a pointer to the new name. +** +** The returned string is only valid until the next call to this routine. +** +** If g.zSkin==0 (which is the overwhelmingly common case) then just +** return zName immediately, without change. But if g.zSkin is set and +** if zName is one of the CONFIG parameters that affect the screen +** appearance, then return a copy of zName with "("+g.zSkin+")" appended. +** Space to hold the copy is managed locally and is freed on the next +** call to this routine. +*/ +const char *db_skin_name(const char *zName){ + static const char *azSkinVars[] = { /* These are the names of CONFIG */ + "adunit", /* variables that affect appearance. */ + "adunit-omit-if-admin", /* Keep this list in sorted order, */ + "adunit-omit-if-user", /* and in sync with the list of */ + "background-image", /* CONFIGSET_SKIN and CONFIGSET_CSS */ + "background-mimetype", /* names in configure.c */ + "css", + "footer", + "header", + "index-page", + "logo-image", + "logo-mimetype", + "timeline-block-markup", + "timeline-max-comment", + "timeline-plaintext", + }; + int i, c, lwr, upr; + static char *zToFree = 0; + if( g.zSkin==0 ) return zName; /* The common case */ + if( zToFree ){ + fossil_free(zToFree); + zToFree = 0; + } + lwr = 0; + upr = sizeof(azSkinVars)/sizeof(azSkinVars[0])-1; + while( lwr<=upr ){ + i = (lwr+upr)/2; + c = fossil_strcmp(azSkinVars[i], zName); + if( c<0 ){ + lwr = i+1; + }else if( c>0 ){ + upr = i-1; + }else{ + zToFree = mprintf("%s(%s)", zName, g.zSkin); + return zToFree; + } + } + return zName; +} /* ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the ** repository and local databases. */ @@ -1980,11 +2034,12 @@ ctrlSetting = &(ctrlSettings[i]); break; } } if( g.repositoryOpen ){ - z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName); + z = db_text(0, "SELECT value FROM config WHERE name=%Q", + db_skin_name(zName)); } if( z==0 && g.zConfigDbName ){ db_swap_connections(); z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName); db_swap_connections(); @@ -2000,11 +2055,12 @@ return z; } char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){ char *z = 0; if( g.repositoryOpen ){ - z = db_text(0, "SELECT mtime FROM config WHERE name=%Q", zName); + z = db_text(0, "SELECT mtime FROM config WHERE name=%Q", + db_skin_name(zName)); } if( z==0 ){ z = zDefault; }else if( zFormat!=0 ){ z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z); @@ -2018,11 +2074,11 @@ db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)", zName, zValue); db_swap_connections(); }else{ db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())", - zName, zValue); + db_skin_name(zName), zValue); } if( globalFlag && g.repositoryOpen ){ db_multi_exec("DELETE FROM config WHERE name=%Q", zName); } db_end_transaction(0); @@ -2032,11 +2088,11 @@ if( globalFlag ){ db_swap_connections(); db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName); db_swap_connections(); }else{ - db_multi_exec("DELETE FROM config WHERE name=%Q", zName); + db_multi_exec("DELETE FROM config WHERE name=%Q", db_skin_name(zName)); } if( globalFlag && g.repositoryOpen ){ db_multi_exec("DELETE FROM config WHERE name=%Q", zName); } db_end_transaction(0); @@ -2053,11 +2109,12 @@ int db_get_int(const char *zName, int dflt){ int v = dflt; int rc; if( g.repositoryOpen ){ Stmt q; - db_prepare(&q, "SELECT value FROM config WHERE name=%Q", zName); + db_prepare(&q, "SELECT value FROM config WHERE name=%Q", + db_skin_name(zName)); rc = db_step(&q); if( rc==SQLITE_ROW ){ v = db_column_int(&q, 0); } db_finalize(&q); @@ -2077,11 +2134,11 @@ db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)", zName, value); db_swap_connections(); }else{ db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%d,now())", - zName, value); + db_skin_name(zName), value); } if( globalFlag && g.repositoryOpen ){ db_multi_exec("DELETE FROM config WHERE name=%Q", zName); } } Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -149,10 +149,11 @@ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fSshTrace; /* Trace the SSH setup traffic */ int fSshClient; /* HTTP client flags for SSH client */ char *zSshCmd; /* SSH command string */ int fNoSync; /* Do not do an autosync ever. --nosync */ + const char *zSkin; /* Alternative webpage skin name */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ char *zHttpsURL; /* zBaseURL translated to https: */ char *zTop; /* Parent directory of zPath */ @@ -1856,10 +1857,21 @@ ** to function as a primitive web-server delivering arbitrary content. */ pFileGlob = glob_create(blob_str(&value)); blob_reset(&value); continue; + } + if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ + /* skin: NAME + ** + ** Use an alternative "skin" for this instance. NAME is the name + ** of the alternative skin to use. See comments on db_skin_name() + ** for addition information. + */ + g.zSkin = mprintf("%s", blob_str(&value)); + blob_reset(&value); + continue; } if( blob_eq(&key, "setenv:") && blob_token(&line, &value) && blob_token(&line, &value2) ){ /* setenv: NAME VALUE ** @@ -1991,10 +2003,11 @@ ** --nossl signal that no SSL connections are available ** --notfound URL use URL as "HTTP 404, object not found" page. ** --files GLOB comma-separate glob patterns for static file to serve ** --baseurl URL base URL (useful with reverse proxies) ** --scgi Interpret input as SCGI rather than HTTP +** --skin NAME Use an alternative labeled NAME ** ** See also: cgi, server, winsrv */ void cmd_http(void){ const char *zIpAddr = 0; @@ -2017,10 +2030,11 @@ zFileGlob = find_option("files",0,1); } zNotFound = find_option("notfound", 0, 1); g.useLocalauth = find_option("localauth", 0, 0)!=0; g.sslNotAvailable = find_option("nossl", 0, 0)!=0; + g.zSkin = find_option("skin", 0, 1); useSCGI = find_option("scgi", 0, 0)!=0; zAltBase = find_option("baseurl", 0, 1); if( zAltBase ) set_base_url(zAltBase); if( find_option("https",0,0)!=0 ){ zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ @@ -2210,10 +2224,11 @@ g.useLocalauth = find_option("localauth", 0, 0)!=0; Th_InitTraceLog(); zPort = find_option("port", "P", 1); zNotFound = find_option("notfound", 0, 1); zAltBase = find_option("baseurl", 0, 1); + g.zSkin = find_option("skin",0,1); if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; if( zAltBase ){ set_base_url(zAltBase); } if( find_option("localhost", 0, 0)!=0 ){ Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -1800,51 +1800,49 @@ Blob img; Stmt ins; blob_init(&img, aLogoImg, szLogoImg); db_prepare(&ins, "REPLACE INTO config(name,value,mtime)" - " VALUES('logo-image',:bytes,now())" + " VALUES(%Q,:bytes,now())", + db_skin_name("logo-image") ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( - "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())", - zLogoMime + "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())", + db_skin_name("logo-mimetype"), zLogoMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clrlogo")!=0 ){ - db_multi_exec( - "DELETE FROM config WHERE name IN " - "('logo-image','logo-mimetype')" - ); + db_unset("logo-image", 0); + db_unset("logo-mimetype", 0); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){ Blob img; Stmt ins; blob_init(&img, aBgImg, szBgImg); db_prepare(&ins, "REPLACE INTO config(name,value,mtime)" - " VALUES('background-image',:bytes,now())" + " VALUES(%Q,:bytes,now())", + db_skin_name("background-image") ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( "REPLACE INTO config(name,value,mtime)" - " VALUES('background-mimetype',%Q,now())", - zBgMime + " VALUES(%Q,%Q,now())", + db_skin_name("background-mimetype"),zBgMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clrbg")!=0 ){ - db_multi_exec( - "DELETE FROM config WHERE name IN " - "('background-image','background-mimetype')" - ); + db_unset("background-image", 0); + db_unset("background-mimetype", 0); db_end_transaction(0); cgi_redirect("setup_logo"); } style_header("Edit Project Logo And Background"); @

The current project logo has a MIME-Type of %h(zLogoMime) Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -58,11 +58,15 @@ ** If ifExists is true, and the named skin does not exist, return NULL. */ static char *skinVarName(const char *zSkinName, int ifExists){ char *z; if( zSkinName==0 || zSkinName[0]==0 ) return 0; - z = mprintf("skin:%s", zSkinName); + if( g.zSkin ){ + z = mprintf("skin(%s):%s", g.zSkin, zSkinName); + }else{ + z = mprintf("skin:%s", zSkinName); + } if( ifExists && !db_exists("SELECT 1 FROM config WHERE name=%Q", z) ){ free(z); z = 0; } return z; @@ -96,11 +100,11 @@ fossil_free(zLabel); } } blob_appendf(&val, "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n", - azType[i], z + db_skin_name(azType[i]), z ); } return blob_str(&val); } @@ -166,17 +170,19 @@ seen = 1; break; } } if( !seen ){ - seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'" - " AND value=%Q", zCurrent); + seen = db_exists("SELECT 1 FROM config" + " WHERE name GLOB %Q" + " AND value=%Q", + skinVarName("*",0), zCurrent); if( !seen ){ db_multi_exec( - "INSERT INTO config(name,value,mtime) VALUES(" - " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," - " %Q,now())", zCurrent + "INSERT INTO config(name,value,mtime) " + " VALUES(strftime(%Q),%Q,now())", + skinVarName("Backup On %Y-%m-%d %H:%M:%S",0), zCurrent ); } } seen = 0; for(i=0; i } db_prepare(&q, "SELECT substr(name, 6), value FROM config" - " WHERE name GLOB 'skin:*'" - " ORDER BY name" + " WHERE name GLOB %Q" + " ORDER BY name", + skinVarName("*",0) ); while( db_step(&q)==SQLITE_ROW ){ const char *zN = db_column_text(&q, 0); const char *zV = db_column_text(&q, 1); i++; + if( g.zSkin ) zN += strlen(g.zSkin)+2; @ %d(i).%h(zN)   if( fossil_strcmp(zV, zCurrent)==0 ){ @ (Currently In Use) }else{ @

Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -1229,10 +1229,13 @@ @ g.userUid = %d(g.userUid)
@ g.zLogin = %h(g.zLogin)
@ g.isHuman = %d(g.isHuman)
@ capabilities = %s(zCap)
@ g.zRepositoryName = %h(g.zRepositoryName)
+ if( g.zSkin ){ + @ g.zSkin = %h(g.zSkin)
+ } @ load_average() = %f(load_average())
@
P("HTTP_USER_AGENT"); cgi_print_all(showAll); if( showAll && blob_size(&g.httpHeader)>0 ){