/* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** Implementation of the Setup page */ #include #include "config.h" #include "setup.h" /* ** Output a single entry for a menu generated using an HTML table. ** If zLink is not NULL or an empty string, then it is the page that ** the menu entry will hyperlink to. If zLink is NULL or "", then ** the menu entry has no hyperlink - it is disabled. */ void setup_menu_entry( const char *zTitle, const char *zLink, const char *zDesc ){ @ if( zLink && zLink[0] ){ @ %h(zTitle) }else{ @ %h(zTitle) } @ %h(zDesc) } /* ** WEBPAGE: /setup */ void setup_page(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } style_header("Server Administration"); @ setup_menu_entry("Users", "setup_ulist", "Grant privileges to individual users."); setup_menu_entry("Access", "setup_access", "Control access settings."); setup_menu_entry("Configuration", "setup_config", "Configure the WWW components of the repository"); setup_menu_entry("Behavior", "setup_behavior", "Configure the SCM behavior of the repository"); setup_menu_entry("Timeline", "setup_timeline", "Timeline display preferences"); setup_menu_entry("Tickets", "tktsetup", "Configure the trouble-ticketing system for this repository"); setup_menu_entry("Skins", "setup_skin", "Select from a menu of prepackaged \"skins\" for the web interface"); setup_menu_entry("CSS", "setup_editcss", "Edit the Cascading Style Sheet used by all pages of this repository"); setup_menu_entry("Header", "setup_header", "Edit HTML text inserted at the top of every page"); setup_menu_entry("Footer", "setup_footer", "Edit HTML text inserted at the bottom of every page"); setup_menu_entry("Logo", "setup_logo", "Change the logo image for the server"); setup_menu_entry("Shunned", "shun", "Show artifacts that are shunned by this repository"); setup_menu_entry("Log", "rcvfromlist", "A record of received artifacts and their sources"); setup_menu_entry("Stats", "stat", "Display repository statistics"); @
style_footer(); } /* ** WEBPAGE: setup_ulist ** ** Show a list of users. Clicking on any user jumps to the edit ** screen for that user. */ void setup_ulist(void){ Stmt s; login_check_credentials(); if( !g.okAdmin ){ login_needed(); return; } style_submenu_element("Add", "Add User", "setup_uedit"); style_header("User List"); @ @
@ Users: @
@ @ @ @ @ @ db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login"); while( db_step(&s)==SQLITE_ROW ){ const char *zCap = db_column_text(&s, 2); if( strstr(zCap, "s") ) zCap = "s"; @ @ @ @ @ @ } @
User ID Capabilities Contact Info
if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){ @ } @ %h(db_column_text(&s,1)) if( g.okAdmin ){ @ } @    %s(zCap)   %s(db_column_text(&s,3))
@
@ Notes: @
    @
  1. The permission flags are as follows:

    @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
    aAdmin: Create and delete users
    cAppend-Tkt: Append to tickets
    dDelete: Delete wiki and tickets
    eEmail: View sensitive data such as EMail addresses
    fNew-Wiki: Create new wiki pages
    gClone: Clone the repository
    hHyperlinks: Show hyperlinks to detailed @ repository history
    iCheck-In: Commit new versions in the repository
    jRead-Wiki: View wiki pages
    kWrite-Wiki: Edit wiki pages
    mAppend-Wiki: Append to wiki pages
    nNew-Tkt: Create new tickets
    oCheck-Out: Check out versions
    pPassword: Change your own password
    rRead-Tkt: View tickets
    sSetup/Super-user: Setup and configure this website
    tTkt-Report: Create new bug summary reports
    uReader: Inherit privileges of @ user reader
    vDeveloper: Inherit privileges of @ user developer
    wWrite-Tkt: Edit tickets
    zZip download: Download a baseline via the @ /zip URL even without checkout @ and history permissions
    @
  2. @ @
  3. @ Every user, logged in or not, inherits the privileges of nobody. @

  4. @ @
  5. @ Any human can login as anonymous since the password is @ clearly displayed on the login page for them to type. The purpose @ of requiring anonymous to log in is to prevent access by spiders. @ Every logged-in user inherits the combined privileges of @ anonymous and @ nobody. @

  6. @ @
  7. @ Users with privilege v inherit the combined privileges of @ developer, anonymous, and nobody. @

  8. @ @
@
style_footer(); } /* ** Return true if zPw is a valid password string. A valid ** password string is: ** ** (1) A zero-length string, or ** (2) a string that contains a character other than '*'. */ static int isValidPwString(const char *zPw){ if( zPw==0 ) return 0; if( zPw[0]==0 ) return 1; while( zPw[0]=='*' ){ zPw++; } return zPw[0]!=0; } /* ** WEBPAGE: /setup_uedit */ void user_edit(void){ const char *zId, *zLogin, *zInfo, *zCap, *zPw; char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; char *oat, *oau, *oav, *oaz; const char *inherit[128]; int doWrite; int uid; int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ /* Must have ADMIN privleges to access this page */ login_check_credentials(); if( !g.okAdmin ){ login_needed(); return; } /* Check to see if an ADMIN user is trying to edit a SETUP account. ** Don't allow that. */ zId = PD("id", "0"); uid = atoi(zId); if( zId && !g.okSetup && uid>0 ){ char *zOldCaps; zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); higherUser = zOldCaps && strchr(zOldCaps,'s'); } if( P("can") ){ cgi_redirect("setup_ulist"); return; } /* If we have all the necessary information, write the new or ** modified user record. After writing the user record, redirect ** to the page that displays a list of users. */ doWrite = cgi_all("login","info","pw") && !higherUser; if( doWrite ){ char zCap[50]; int i = 0; int aa = P("aa")!=0; int ad = P("ad")!=0; int ae = P("ae")!=0; int ai = P("ai")!=0; int aj = P("aj")!=0; int ak = P("ak")!=0; int an = P("an")!=0; int ao = P("ao")!=0; int ap = P("ap")!=0; int ar = P("ar")!=0; int as = g.okSetup && P("as")!=0; int aw = P("aw")!=0; int ac = P("ac")!=0; int af = P("af")!=0; int am = P("am")!=0; int ah = P("ah")!=0; int ag = P("ag")!=0; int at = P("at")!=0; int au = P("au")!=0; int av = P("av")!=0; int az = P("az")!=0; if( aa ){ zCap[i++] = 'a'; } if( ac ){ zCap[i++] = 'c'; } if( ad ){ zCap[i++] = 'd'; } if( ae ){ zCap[i++] = 'e'; } if( af ){ zCap[i++] = 'f'; } if( ah ){ zCap[i++] = 'h'; } if( ag ){ zCap[i++] = 'g'; } if( ai ){ zCap[i++] = 'i'; } if( aj ){ zCap[i++] = 'j'; } if( ak ){ zCap[i++] = 'k'; } if( am ){ zCap[i++] = 'm'; } if( an ){ zCap[i++] = 'n'; } if( ao ){ zCap[i++] = 'o'; } if( ap ){ zCap[i++] = 'p'; } if( ar ){ zCap[i++] = 'r'; } if( as ){ zCap[i++] = 's'; } if( at ){ zCap[i++] = 't'; } if( au ){ zCap[i++] = 'u'; } if( av ){ zCap[i++] = 'v'; } if( aw ){ zCap[i++] = 'w'; } if( az ){ zCap[i++] = 'z'; } zCap[i] = 0; zPw = P("pw"); zLogin = P("login"); if( isValidPwString(zPw) ){ zPw = sha1_shared_secret(zPw, zLogin); }else{ zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); } if( uid>0 && db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid) ){ style_header("User Creation Error"); @ Login "%h(zLogin)" is already used by a different @ user. @ @

[Bummer]

style_footer(); return; } login_verify_csrf_secret(); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap) " "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')", uid, P("login"), P("info"), zPw, zCap ); cgi_redirect("setup_ulist"); return; } /* Load the existing information about the user, if any */ zLogin = ""; zInfo = ""; zCap = ""; zPw = ""; oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; if( uid ){ zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); if( strchr(zCap, 'a') ) oaa = " checked"; if( strchr(zCap, 'c') ) oac = " checked"; if( strchr(zCap, 'd') ) oad = " checked"; if( strchr(zCap, 'e') ) oae = " checked"; if( strchr(zCap, 'f') ) oaf = " checked"; if( strchr(zCap, 'g') ) oag = " checked"; if( strchr(zCap, 'h') ) oah = " checked"; if( strchr(zCap, 'i') ) oai = " checked"; if( strchr(zCap, 'j') ) oaj = " checked"; if( strchr(zCap, 'k') ) oak = " checked"; if( strchr(zCap, 'm') ) oam = " checked"; if( strchr(zCap, 'n') ) oan = " checked"; if( strchr(zCap, 'o') ) oao = " checked"; if( strchr(zCap, 'p') ) oap = " checked"; if( strchr(zCap, 'r') ) oar = " checked"; if( strchr(zCap, 's') ) oas = " checked"; if( strchr(zCap, 't') ) oat = " checked"; if( strchr(zCap, 'u') ) oau = " checked"; if( strchr(zCap, 'v') ) oav = " checked"; if( strchr(zCap, 'w') ) oaw = " checked"; if( strchr(zCap, 'z') ) oaz = " checked"; } /* figure out inherited permissions */ memset(inherit, 0, sizeof(inherit)); if( strcmp(zLogin, "developer") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( strcmp(zLogin, "reader") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( strcmp(zLogin, "anonymous") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( strcmp(zLogin, "nobody") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } /* Begin generating the page */ style_submenu_element("Cancel", "Cancel", "setup_ulist"); if( uid ){ style_header(mprintf("Edit User %h", zLogin)); }else{ style_header("Add A New User"); } @
@
login_insert_csrf_secret(); @ @ @ if( uid ){ @ }else{ @ } @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ if( zPw[0] ){ /* Obscure the password for all users */ @ }else{ /* Show an empty password as an empty input field */ @ } @ if( !higherUser ){ @ @ @ } @
User ID:%d(uid) (new user)
Login:
Contact Info:
Capabilities: #define B(x) inherit[x] if( g.okSetup ){ @ %s(B('s'))Setup
} @ %s(B('a'))Admin
@ %s(B('d'))Delete
@ %s(B('e'))Email
@ %s(B('p'))Password
@ %s(B('i'))Check-In
@ %s(B('o'))Check-Out
@ %s(B('h'))History
@ %s(B('u'))Reader
@ %s(B('v'))Developer
@ %s(B('g'))Clone
@ %s(B('j'))Read Wiki
@ %s(B('f'))New Wiki
@ %s(B('m'))Append Wiki
@ %s(B('k'))Write Wiki
@ %s(B('r'))Read Tkt
@ %s(B('n'))New Tkt
@ %s(B('c'))Append Tkt
@ %s(B('w'))Write Tkt
@ %s(B('t'))Tkt Report
@ %s(B('z'))Download Zip @
Password:
  @
@

Privileges And Capabilities:

@ @ @

Special Logins

@ @ @ style_footer(); } /* ** Generate a checkbox for an attribute. */ static void onoff_attribute( const char *zLabel, /* The text label on the checkbox */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQParm, /* The query parameter */ int dfltVal /* Default value if VAR table entry does not exist */ ){ const char *zQ = P(zQParm); int iVal = db_get_boolean(zVar, dfltVal); if( zQ==0 && P("submit") ){ zQ = "off"; } if( zQ ){ int iQ = strcmp(zQ,"on")==0 || atoi(zQ); if( iQ!=iVal ){ login_verify_csrf_secret(); db_set(zVar, iQ ? "1" : "0", 0); iVal = iQ; } } if( iVal ){ @ %s(zLabel) }else{ @ %s(zLabel) } } /* ** Generate an entry box for an attribute. */ void entry_attribute( const char *zLabel, /* The text label on the entry box */ int width, /* Width of the entry box */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQParm, /* The query parameter */ char *zDflt /* Default value if VAR table entry does not exist */ ){ const char *zVal = db_get(zVar, zDflt); const char *zQ = P(zQParm); if( zQ && strcmp(zQ,zVal)!=0 ){ login_verify_csrf_secret(); db_set(zVar, zQ, 0); zVal = zQ; } @ @ %s(zLabel) } /* ** Generate a text box for an attribute. */ static void textarea_attribute( const char *zLabel, /* The text label on the textarea */ int rows, /* Rows in the textarea */ int cols, /* Columns in the textarea */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQP, /* The query parameter */ const char *zDflt /* Default value if VAR table entry does not exist */ ){ const char *z = db_get(zVar, (char*)zDflt); const char *zQ = P(zQP); if( zQ && strcmp(zQ,z)!=0 ){ login_verify_csrf_secret(); db_set(zVar, zQ, 0); z = zQ; } if( rows>0 && cols>0 ){ @ @ %s(zLabel) } } /* ** WEBPAGE: setup_access */ void setup_access(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } style_header("Access Control Settings"); db_begin_transaction(); @
login_insert_csrf_secret(); @
onoff_attribute("Require password for local access", "localauth", "localauth", 0); @

When enabled, the password sign-in is required for @ web access coming from 127.0.0.1. When disabled, web access @ from 127.0.0.1 is allows without any login - the user id is selected @ from the ~/.fossil database. Password login is always required @ for incoming web connections on internet addresses other than @ 127.0.0.1.

@
onoff_attribute("Show javascript button to fill in CAPTCHA", "auto-captcha", "autocaptcha", 0); @

When enabled, a button appears on the login screen for user @ "anonymous" that will automatically fill in the CAPTCHA password. @ This is less secure that forcing the user to do it manually, but is @ probably secure enough and it is certainly more convenient for @ anonymous users.

@
entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); @

The number of hours for which a login is valid. This must be a @ positive number. The default is 8760 hours which is approximately equal @ to a year.

@
entry_attribute("Download packet limit", 10, "max-download", "mxdwn", "5000000"); @

Fossil tries to limit out-bound sync, clone, and pull packets @ to this many bytes, uncompressed. If the client requires more data @ than this, then the client will issue multiple HTTP requests. @ Values below 1 million are not recommended. 5 million is a @ reasonable number.

@
@

@
db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_timeline */ void setup_timeline(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } style_header("Timeline Display Preferences"); db_begin_transaction(); @
login_insert_csrf_secret(); @
onoff_attribute("Allow block-markup in timeline", "timeline-block-markup", "tbm", 0); @

In timeline displays, check-in comments can be displayed with or @ without block markup (paragraphs, tables, etc.)

@
onoff_attribute("Use Universal Coordinated Time (UTC)", "timeline-utc", "utc", 1); @

Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or @ Zulu) instead of in local time.

@
onoff_attribute("Show version differences by default", "show-version-diffs", "vdiff", 0); @

On the version-information pages linked from the timeline can either @ show complete diffs of all file changes, or can just list the names of @ the files that have changed. Users can get to either page by @ clicking. This setting selects the default.

@
entry_attribute("Max timeline comment length", 6, "timeline-max-comment", "tmc", "0"); @

The maximum length of a comment to be displayed in a timeline. @ "0" there is no length limit.

@
@

@
db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_behavior */ void setup_behavior(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } style_header("Fossil SCM Behavior"); db_begin_transaction(); @
login_insert_csrf_secret(); @
onoff_attribute("Automatically synchronize with repository", "autosync", "autosync", 1); @

Automatically keeps your work in sync with a centralized server.

@
onoff_attribute("Show javascript button to fill in CAPTCHA", "auto-captcha", "autocaptcha", 0); @

When enabled, a button appears on the login screen for user @ "anonymous" that will automatically fill in the CAPTCHA password. @ This is less secure that forcing the user to do it manually, but is @ probably secure enough and it is certainly more convenient for @ anonymous users.

@
onoff_attribute("Sign all commits with GPG", "clearsign", "clearsign", 1); @

When enabled (the default), fossil will attempt to @ sign all commits with GPG. When disabled, commits will @ be unsigned.

@
onoff_attribute("Require local authentication", "localauth", "localauth", 0); @

If enabled, require that HTTP connections from @ 127.0.0.1 be authenticated by password. If @ false, all HTTP requests from localhost have @ unrestricted access to the repository.

@
onoff_attribute("Modification times used to detect changes", "mtime-changes", "mtime-changes", 0); @

Use file modification times (mtimes) to detect when files have been modified.

@
entry_attribute("Diff Command", 16, "diff-command", "diff-command", "diff"); @

External command used to generate a textual diff

@
entry_attribute("Gdiff Command", 16, "gdiff-command", "gdiff-command", "gdiff"); @

External command to run when performing a graphical diff. If undefined, text diff will be used.

@
entry_attribute("Editor", 16, "editor", "editor", ""); @

Text editor command used for check-in comments.

@
entry_attribute("HTTP port", 16, "http-port", "http-port", "8080"); @

The TCP/IP port number to use by the "server" and "ui" commands. Default: 8080

@
entry_attribute("PGP Command", 32, "pgp-command", "pgp-command", "gpg --clearsign -o "); @

Command used to clear-sign manifests at check-in.The default is "gpg --clearsign -o ".

@
entry_attribute("Proxy", 32, "proxy", "proxy", "off"); @

URL of the HTTP proxy.

@
entry_attribute("Web browser", 32, "web-browser", "web-browser", ""); @

Default web browser for "fossil ui".

@
@

@
db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_config */ void setup_config(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } style_header("WWW Configuration"); db_begin_transaction(); @
login_insert_csrf_secret(); @
entry_attribute("Project Name", 60, "project-name", "pn", ""); @

Give your project a name so visitors know what this site is about. @ The project name will also be used as the RSS feed title.

@
textarea_attribute("Project Description", 5, 60, "project-description", "pd", ""); @

Describe your project. This will be used in page headers for search @ engines as well as a short RSS description.

@
entry_attribute("Index Page", 60, "index-page", "idxpg", "/home"); @

Enter the pathname of the page to display when the "Home" menu @ option is selected and when no pathname is @ specified in the URL. For example, if you visit the url:

@ @
%h(g.zBaseURL)
@ @

And you have specified an index page of "/home" the above will @ automatically redirect to:

@ @
%h(g.zBaseURL)/home
@ @

The default "/home" page displays a Wiki page with the same name @ as the Project Name specified above. Some sites prefer to redirect @ to a documentation page (ex: "/doc/tip/index.wiki") or to "/timeline".

@
onoff_attribute("Use HTML as wiki markup language", "wiki-use-html", "wiki-use-html", 0); @

Use HTML as the wiki markup language. Wiki links will still be parsed but @ all other wiki formatting will be ignored. This option is helpful if you have @ chosen to use a rich HTML editor for wiki markup such as TinyMCE.

@

CAUTION: when @ enabling, all HTML tags and attributes are accepted in the wiki. @ No sanitization is done. This means that it is very possible for malicious @ users to inject dangerous HTML, CSS and JavaScript code into your wiki.

@

This should only be enabled when wiki editing is limited @ to trusted users. It should not be used on a publically @ editable wiki.

@
@

@
db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_editcss */ void setup_editcss(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='css'"); cgi_replace_parameter("css", zDefaultCSS); }else{ textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS); } style_header("Edit CSS"); @
login_insert_csrf_secret(); @ Edit the CSS below:
textarea_attribute("", 40, 80, "css", "css", zDefaultCSS); @
@ @ @
@
@ The default CSS is shown below for reference. Other examples @ of CSS files can be seen on the skins page. @ See also the header and @ footer editing screens. @
  @ %h(zDefaultCSS)
  @ 
style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_header */ void setup_header(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='header'"); cgi_replace_parameter("header", zDefaultHeader); }else{ textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader); } style_header("Edit Page Header"); @
login_insert_csrf_secret(); @

Edit HTML text with embedded TH1 (a TCL dialect) that will be used to @ generate the beginning of every page through start of the main @ menu.

textarea_attribute("", 40, 80, "header", "header", zDefaultHeader); @
@ @ @
@
@ The default header is shown below for reference. Other examples @ of headers can be seen on the skins page. @ See also the CSS and @ footer editing screeens. @
  @ %h(zDefaultHeader)
  @ 
style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_footer */ void setup_footer(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='footer'"); cgi_replace_parameter("footer", zDefaultFooter); }else{ textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter); } style_header("Edit Page Footer"); @
login_insert_csrf_secret(); @

Edit HTML text with embedded TH1 (a TCL dialect) that will be used to @ generate the end of every page.

textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter); @
@ @ @
@
@ The default footer is shown below for reference. Other examples @ of footers can be seen on the skins page. @ See also the CSS and @ header editing screens. @
  @ %h(zDefaultFooter)
  @ 
style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_logo */ void setup_logo(void){ const char *zMime = "image/gif"; const char *aImg = P("im"); int szImg = atoi(PD("im:bytes","0")); if( szImg>0 ){ zMime = PD("im:mimetype","image/gif"); } login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){ Blob img; Stmt ins; blob_init(&img, aImg, szImg); db_prepare(&ins, "REPLACE INTO config(name, value)" " VALUES('logo-image',:bytes)" ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( "REPLACE INTO config(name, value) VALUES('logo-mimetype',%Q)", zMime ); }else if( P("clr")!=0 ){ db_multi_exec( "DELETE FROM config WHERE name GLOB 'logo-*'" ); } style_header("Edit Project Logo"); @

The current project logo has a MIME-Type of %h(zMime) and looks @ like this:

@
logo
@ @

The logo is accessible to all users at this URL: @ %s(g.zBaseURL)/logo. @ The logo may or may not appear on each @ page depending on the CSS and @ header setup.

@ @
@

To set a new logo image, select a file to use as the logo using @ the entry box below and then press the "Change Logo" button.

login_insert_csrf_secret(); @ Logo Image file: @
@ @ @
style_footer(); db_end_transaction(0); }