Check-in [fb358ca492]
Not logged in
Overview

SHA1 Hash:fb358ca492904507ebfc504c40b0a6e4f957537a
Date: 2007-11-24 19:33:46
User: drh
Comment:Progress toward getting ticketing working. We can enter a new ticket and display it. Cannot yet edit a ticket.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/cgi.c from [144475b0ed] to [1c05503871].

@@ -725,10 +725,22 @@
       return zValue;
     }
   }
   CGIDEBUG(("no-match [%s]\n", zName));
   return zDefault;
+}
+
+/*
+** Return the name of the i-th CGI parameter.  Return NULL if there
+** are fewer than i registered CGI parmaeters.
+*/
+const char *cgi_parameter_name(int i){
+  if( i>=0 && i<nUsedQP ){
+    return aParamQP[i].zName;
+  }else{
+    return 0;
+  }
 }
 
 /*
 ** Print CGI debugging messages.
 */

Modified src/db.c from [96be69e00f] to [48224de87a].

@@ -319,10 +319,16 @@
 double db_column_double(Stmt *pStmt, int N){
   return sqlite3_column_double(pStmt->pStmt, N);
 }
 const char *db_column_text(Stmt *pStmt, int N){
   return (char*)sqlite3_column_text(pStmt->pStmt, N);
+}
+const char *db_column_name(Stmt *pStmt, int N){
+  return (char*)sqlite3_column_name(pStmt->pStmt, N);
+}
+int db_column_count(Stmt *pStmt){
+  return (char*)sqlite3_column_count(pStmt->pStmt);
 }
 char *db_column_malloc(Stmt *pStmt, int N){
   return mprintf("%s", db_column_text(pStmt, N));
 }
 void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){

Modified src/manifest.c from [716c309625] to [9573b5d563].

@@ -857,12 +857,28 @@
       m.rDate, rid, m.zUser, zComment,
       TAG_BGCOLOR, rid,
       TAG_BGCOLOR, rid,
       TAG_USER, rid,
       TAG_COMMENT, rid
+    );
+    free(zComment);
+  }
+  if( m.type==CFTYPE_TICKET ){
+    char *zTag;
+    char *zComment;
+
+    ticket_insert(&m, 1, 1);
+    zTag = mprintf("tkt-%s", m.zTicketUuid);
+    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
+    free(zTag);
+    zComment = mprintf("Changes to ticket [%.10s]", m.zTicketUuid);
+    db_multi_exec(
+      "INSERT INTO event(type,mtime,objid,user,comment)"
+      "VALUES('t',%.17g,%d,%Q,%Q)",
+      m.rDate, rid, m.zUser, zComment
     );
     free(zComment);
   }
   db_end_transaction(0);
   manifest_clear(&m);
   return 1;
 }

Modified src/rebuild.c from [45d87035e5] to [d7213a8b18].

@@ -57,10 +57,11 @@
     if( zTable==0 ) break;
     db_multi_exec("DROP TABLE %Q", zTable);
     free(zTable);
   }
   db_multi_exec(zRepositorySchema2);
+  ticket_create_table(0);
 
   db_multi_exec("INSERT INTO unclustered SELECT rid FROM blob");
   db_multi_exec(
      "DELETE FROM unclustered"
      " WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"

Modified src/subscript.c from [fb8956976f] to [d503240921].

@@ -60,10 +60,12 @@
 
 #if INTERFACE
 typedef struct Subscript Subscript;
 #define SBS_OK      0
 #define SBS_ERROR   1
+#define SBS_RETURN  2
+#define SBS_BREAK   3
 #endif
 
 /*
 ** Configuration constants
 */
@@ -343,17 +345,25 @@
 
 /*
 ** Set the error message for an interpreter.  Verb implementations
 ** use this routine when they encounter an error.
 */
-void SbS_SetErrorMessage(struct Subscript *p, const char *zErr){
-  int nErr = strlen(zErr);
+void SbS_SetErrorMessage(struct Subscript *p, const char *zErr, ...){
+  int nErr;
+  char *zMsg;
+  va_list ap;
+
+  va_start(ap, zErr);
+  zMsg = vmprintf(zErr, ap);
+  va_end(ap);
+  nErr = strlen(zMsg);
   if( nErr>sizeof(p->zErrMsg)-1 ){
     nErr = sizeof(p->zErrMsg)-1;
   }
-  memcpy(p->zErrMsg, zErr, nErr);
+  memcpy(p->zErrMsg, zMsg, nErr);
   p->zErrMsg[nErr] = 0;
+  free(zMsg);
 }
 
 /*
 ** Return a pointer to the current error message for the
 ** interpreter.
@@ -528,15 +538,16 @@
 ** the interpreter.  Make a copy if you need it to persist.
 */
 const char *SbS_Fetch(
   struct Subscript *p,   /* The interpreter we are interrogating */
   const char *zKey,        /* Name of the variable.  Case sensitive */
+  int nKey,                /* Length of the key */
   int *pLength             /* Write the length here */
 ){
   const SbSValue *pVal;
 
-  pVal = sbs_fetch(&p->symTab, zKey, -1);
+  pVal = sbs_fetch(&p->symTab, zKey, nKey);
   if( pVal==0 || (pVal->flags & SBSVAL_STR)==0 ){
     *pLength = 0;
     return 0;
   }else{
     *pLength = pVal->u.str.size;
@@ -709,10 +720,25 @@
   SbS_Pop(p, 1);
   return 0;
 }
 
 /*
+** Send text to the appropriate output:  Either to the console
+** or to the CGI reply buffer.
+*/
+static void sendText(const char *z, int n){
+  if( enableOutput && n ){
+    if( n<0 ) n = strlen(z);
+    if( g.cgiPanic ){
+      cgi_append_content(z, n);
+    }else{
+      fwrite(z, 1, n, stdout);
+    }
+  }
+}
+
+/*
 ** Subscript command:      STRING puts
 ** Subscript command:      STRING html
 **
 ** Output STRING as HTML (html) or unchanged (puts).
 ** Pop it from the stack.
@@ -728,20 +754,83 @@
       zOut = htmlize(z, size);
       size = strlen(zOut);
     }else{
       zOut = (char*)z;
     }
-    if( g.cgiPanic ){
-      cgi_append_content(zOut, size);
-    }else{
-      printf("%.*s", size, zOut);
-    }
+    sendText(zOut, size);
     if( pConvert ){
       free(zOut);
     }
   }
   SbS_Pop(p, 1);
+  return 0;
+}
+
+/*
+** Subscript command:      STRING wiki
+**
+** Render the input string as wiki.
+*/
+static int wikiCmd(struct Subscript *p, void *pConvert){
+  int size;
+  const char *z;
+  if( SbS_RequireStack(p, 1, "wiki") ) return 1;
+  if( enableOutput ){
+    Blob src;
+    z = SbS_StackValue(p, 0, &size);
+    blob_init(&src, z, size);
+    wiki_convert(&src, 0, WIKI_INLINE);
+    blob_reset(&src);
+  }
+  SbS_Pop(p, 1);
+  return 0;
+}
+
+/*
+** Subscript command:   NAME TEXT-LIST NUMLINES combobox
+**
+** Generate an HTML combobox.  NAME is both the name of the
+** CGI parameter and the name of a variable that contains the
+** currently selected value.  TEXT-LIST is a list of possible
+** values for the combobox.  NUMLINES is 1 for a true combobox.
+** If NUMLINES is greater than one then the display is a listbox
+** with the number of lines given.
+*/
+static int comboboxCmd(struct Subscript *p, void *NotUsed){
+  if( SbS_RequireStack(p, 3, "combobox") ) return 1;
+  if( enableOutput ){
+    int height;
+    Blob list, elem;
+    char *zName, *zList;
+    int nName, nList, nValue;
+    const char *zValue;
+    char *z, *zH;
+
+    height = SbS_StackValueInt(p, 0);
+    zList = (char*)SbS_StackValue(p, 1, &nList);
+    blob_init(&list, zList, nList);
+    zName = (char*)SbS_StackValue(p, 2, &nName);
+    zValue = SbS_Fetch(p, zName, nName, &nValue);
+    z = mprintf("<select name=\"%.*h\" size=\"%d\">", nName, zName, height);
+    sendText(z, -1);
+    free(z);
+    while( blob_token(&list, &elem) ){
+      zH = mprintf("%.*h", blob_size(&elem), blob_buffer(&elem));
+      if( zValue && blob_size(&elem)==nValue
+             && memcmp(zValue, blob_buffer(&elem), nValue)==0 ){
+        z = mprintf("<option value=\"%s\" selected>%s</option>", zH, zH);
+      }else{
+        z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
+      }
+      free(zH);
+      sendText(z, -1);
+      free(z);
+    }
+    sendText("</select>", -1);
+    blob_reset(&list);
+  }
+  SbS_Pop(p, 3);
   return 0;
 }
 
 
 /*
@@ -752,10 +841,11 @@
   int (*xCmd)(Subscript*,void*);
   void *pArg;
 } aBuiltin[] = {
   { "add",             bopCmd,               (void*)SBSOP_AND    },
   { "and",             bopCmd,               (void*)SBSOP_AND    },
+  { "combobox",        comboboxCmd,          0,                  },
   { "div",             bopCmd,               (void*)SBSOP_DIV    },
   { "enable_output",   enableOutputCmd,      0                   },
   { "exists",          existsCmd,            0,                  },
   { "get",             getCmd,               0,                  },
   { "hascap",          hascapCmd,            0                   },
@@ -767,10 +857,11 @@
   { "not",             notCmd,               0                   },
   { "or",              bopCmd,               (void*)SBSOP_OR     },
   { "puts",            putsCmd,              0                   },
   { "set",             setCmd,               0                   },
   { "sub",             bopCmd,               (void*)SBSOP_SUB    },
+  { "wiki",            wikiCmd,              0,                  },
 };
 
 
 /*
 ** Compare a zero-terminated string zPattern against
@@ -860,10 +951,13 @@
               upr = i-1;
             }else{
               lwr = i+1;
             }
           }
+          if( upr<lwr ){
+            SbS_SetErrorMessage(p, "unknown verb: %.*s", n, zScript);
+          }
         }else if( pVal->flags & SBSVAL_VERB ){
           rc = pVal->u.verb.xVerb(p, pVal->u.verb.pArg);
         }else if( pVal->flags & SBSVAL_EXEC ){
           rc = SbS_Eval(p, pVal->u.str.z, pVal->u.str.size);
         }else{
@@ -887,17 +981,11 @@
 int SbS_Render(struct Subscript *p, const char *z){
   int i = 0;
   int rc = SBS_OK;
   while( z[i] ){
     if( z[i]=='[' ){
-      if( enableOutput ){
-        if( g.cgiPanic ){
-          cgi_append_content(z, i);
-        }else{
-          fwrite(z, 1, i, stdout);
-        }
-      }
+      sendText(z, i);
       z += i+1;
       for(i=0; z[i] && z[i]!=']'; i++){}
       rc = SbS_Eval(p, z, i);
       if( rc!=SBS_OK ) break;
       if( z[i] ) i++;
@@ -905,16 +993,16 @@
       i = 0;
     }else{
       i++;
     }
   }
-  if( i>0 && enableOutput ){
-    if( g.cgiPanic ){
-      cgi_append_content(z, i);
-    }else{
-      fwrite(z, 1, i, stdout);
-    }
+  if( rc==SBS_ERROR ){
+    sendText("<hr><p><font color=\"red\"><b>ERROR: ", -1);
+    sendText(SbS_GetErrorMessage(p), -1);
+    sendText("</b></font></p>", -1);
+  }else{
+    sendText(z, i);
   }
   return rc;
 }
 
 /*

Modified src/tkt.c from [e32e47c61c] to [7e0e9ce39d].

@@ -22,11 +22,10 @@
 *******************************************************************************
 **
 ** This file contains code used render and control ticket entry
 ** and display pages.
 */
-#if 0
 #include "config.h"
 #include "tkt.h"
 #include <assert.h>
 
 /*
@@ -38,17 +37,17 @@
 static char **azField = 0;
 
 /*
 ** A subscript interpreter used for processing Tickets.
 */
-struct Subscript *pInterp = 0;
+static struct Subscript *pInterp = 0;
 
 /*
 ** Compare two entries in azField for sorting purposes
 */
-static int nameCmpr(void *a, void *b){
-  return strcmp((char*)a, (char*)b);
+static int nameCmpr(const void *a, const void *b){
+  return strcmp(*(char**)a, *(char**)b);
 }
 
 /*
 ** Obtain a list of all fields of the TICKET table.  Put them
 ** in sorted order.
@@ -83,10 +82,74 @@
   }
   return 0;
 }
 
 /*
+** Query the database for all TICKET fields for the specific
+** ticket whose name is given by the "name" CGI parameter.
+** Load the values for all fields into the interpreter.
+*/
+static void initializeVariablesFromDb(void){
+  const char *zName;
+  Stmt q;
+  int i, n;
+
+  zName = PD("name","");
+  db_prepare(&q, "SELECT * FROM ticket WHERE tkt_uuid GLOB '%q*'", zName);
+  if( db_step(&q)==SQLITE_ROW ){
+    n = db_column_count(&q);
+    for(i=0; i<n; i++){
+      const char *zVal = db_column_text(&q, i);
+      if( zVal==0 ) zVal = "";
+      SbS_Store(pInterp, db_column_name(&q,i), zVal, 1);
+    }
+  }else{
+    db_finalize(&q);
+    db_prepare(&q, "PRAGMA table_info(ticket)");
+    while( db_step(&q)==SQLITE_ROW ){
+      const char *zField = db_column_text(&q, 1);
+      SbS_Store(pInterp, zField, "", 0);
+    }
+  }
+  db_finalize(&q);
+}
+
+/*
+** Transfer all CGI parameters to variables in the interpreter.
+*/
+static void initializeVariablesFromCGI(void){
+  int i;
+  const char *z;
+
+  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
+    SbS_Store(pInterp, z, P(z), 0);
+  }
+}
+
+/*
+** Rebuild all tickets named in the _pending_ticket table.
+**
+** This routine is called just prior to commit after new
+** out-of-sequence ticket changes have been added.
+*/
+static int ticket_rebuild_at_commit(void){
+  Stmt q;
+  db_multi_exec(
+    "DELETE FROM ticket WHERE tkt_uuid IN _pending_ticket"
+  );
+  db_prepare(&q, "SELECT uuid FROM _pending_ticket");
+  while( db_step(&q)==SQLITE_ROW ){
+    const char *zUuid = db_column_text(&q, 0);
+    ticket_rebuild_entry(zUuid);
+  }
+  db_multi_exec(
+    "DELETE FROM _pending_ticket"
+  );
+  return 0;
+}
+
+/*
 ** Update an entry of the TICKET table according to the information
 ** in the control file given in p.  Attempt to create the appropriate
 ** TICKET table entry if createFlag is true.  If createFlag is false,
 ** that means we already know the entry exists and so we can save the
 ** work of trying to create it.
@@ -93,58 +156,72 @@
 */
 void ticket_insert(Manifest *p, int createFlag, int checkTime){
   Blob sql;
   Stmt q;
   int i;
-
+  const char *zSep;
+
+  getAllTicketFields();
   if( createFlag ){
-    db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid) "
-                  "VALUES(%Q)", p->zTicketUuid);
+    db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
+                  "VALUES(%Q, 0)", p->zTicketUuid);
   }
   blob_zero(&sql);
   blob_appendf(&sql, "UPDATE ticket SET tkt_mtime=:mtime");
   zSep = "SET";
   for(i=0; i<p->nField; i++){
     const char *zName = p->aField[i].zName;
     if( zName[0]=='+' ){
+      zName++;
       if( !isTicketField(zName) ) continue;
       blob_appendf(&sql,", %s=%s || %Q", zName, zName, p->aField[i].zValue);
     }else{
       if( !isTicketField(zName) ) continue;
       blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
     }
   }
-  blob_appendf(&sql, " WHERE tkt_uuid='%s'", p->zTicketUuid);
+  blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
+                     p->zTicketUuid);
   db_prepare(&q, "%s", blob_str(&sql));
   db_bind_double(&q, ":mtime", p->rDate);
   db_step(&q);
   db_finalize(&q);
+  if( checkTime && db_changes()==0 ){
+    static int isInit = 0;
+    if( !isInit ){
+      db_multi_exec("CREATE TEMP TABLE _pending_ticket(uuid TEXT UNIQUE)");
+      db_commit_hook(ticket_rebuild_at_commit, 1);
+      isInit = 1;
+    }
+    db_multi_exec("INSERT OR IGNORE INTO _pending_ticket"
+                  "VALUES(%Q)", p->zTicketUuid);
+  }
   blob_reset(&sql);
 }
 
 /*
 ** Rebuild an entire entry in the TICKET table
 */
 void ticket_rebuild_entry(const char *zTktUuid){
   char *zTag = mprintf("tkt-%s", zTktUuid);
   int tagid = tag_findid(zTag, 1);
-  Stmt *q;
+  Stmt q;
   Manifest manifest;
   Blob content;
-  int clearFlag = 1;
+  int createFlag = 1;
 
   db_multi_exec(
      "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
   );
   db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
   while( db_step(&q)==SQLITE_ROW ){
     int rid = db_column_int(&q, 0);
-    content_get(rid, &entry);
-    manifest_parse(&manifest, &entry);
-    ticket_insert(&manifest, clearFlag);
+    content_get(rid, &content);
+    manifest_parse(&manifest, &content);
+    ticket_insert(&manifest, createFlag, 0);
     manifest_clear(&manifest);
-    clearFlag = 0;
+    createFlag = 0;
   }
   db_finalize(&q);
 }
 
 /*
@@ -152,30 +229,43 @@
 */
 void ticket_init(void){
   char *zConfig;
   if( pInterp ) return;
   pInterp = SbS_Create();
-  zConfig = db_text(zDefaultTicketConfig,
+  zConfig = db_text((char*)zDefaultTicketConfig,
              "SELECT value FROM config WHERE name='ticket-configuration'");
-  SbS_Eval(pInterp, zConfig);
+  SbS_Eval(pInterp, zConfig, -1);
 }
 
 /*
-** Rebuild the entire TICKET table.
+** Recreate the ticket table.
 */
-void ticket_rebuild(void){
+void ticket_create_table(int separateConnection){
   char *zSql;
   int nSql;
+
   db_multi_exec("DROP TABLE IF EXISTS ticket;");
   ticket_init();
-  zSql = SbS_Fetch(pInterp, "ticket_sql", &nSql);
+  zSql = (char*)SbS_Fetch(pInterp, "ticket_sql", -1, &nSql);
   if( zSql==0 ){
-    fossil_error("no ticket_sql defined by ticket configuration");
+    fossil_panic("no ticket_sql defined by ticket configuration");
+  }
+  if( separateConnection ){
+    zSql = mprintf("%.*s", nSql, zSql);
+    db_init_database(g.zRepositoryName, zSql, 0);
+    free(zSql);
+  }else{
+    db_multi_exec("%.*s", nSql, zSql);
   }
-  zSql = mprintf("%.*s", nSql, zSql);
-  db_init_database(g.zRepositoryName, zSql, 0);
-  free(zSql);
+}
+
+/*
+** Repopulate the ticket table
+*/
+void ticket_rebuild(void){
+  Stmt q;
+  db_begin_transaction();
   db_prepare(&q,"SELECT tagname FROM tag WHERE tagname GLOB 'tkt-*'");
   while( db_step(&q)==SQLITE_ROW ){
     const char *zName = db_column_text(&q, 0);
     int len;
     zName += 4;
@@ -182,7 +272,110 @@
     len = strlen(zName);
     if( len<20 || !validate16(zName, len) ) continue;
     ticket_rebuild_entry(zName);
   }
   db_finalize(&q);
+  db_end_transaction(0);
+}
+
+/*
+** WEBPAGE: tktview
+*/
+void tktview_page(void){
+  char *zScript;
+  int nScript;
+  login_check_credentials();
+  if( !g.okRdTkt ){ login_needed(); return; }
+  style_header("View Ticket");
+  ticket_init();
+  initializeVariablesFromDb();
+  zScript = (char*)SbS_Fetch(pInterp, "tktview_template", -1, &nScript);
+  zScript = mprintf("%.*s", nScript, zScript);
+  SbS_Render(pInterp, zScript);
+  style_footer();
 }
+
+/*
+** Subscript command:   LABEL submit_new_ticket
+**
+** If the variable named LABEL exists, then submit a new ticket
+** based on the values of other defined variables.
+*/
+static int submitNewCmd(struct Subscript *p, void *pNotify){
+  const char *zLabel;
+  int nLabel, size;
+  if( SbS_RequireStack(p, 1, "submit_new_ticket") ) return 1;
+  zLabel = SbS_StackValue(p, 0, &nLabel);
+  if( SbS_Fetch(p, zLabel, nLabel, &size)!=0 ){
+    char *zDate, *zUuid;
+    int i;
+    int rid;
+    Blob tktchng, cksum;
+
+    (*(int*)pNotify) = 1;
+    blob_zero(&tktchng);
+    zDate = db_text(0, "SELECT datetime('now')");
+    zDate[10] = 'T';
+    blob_appendf(&tktchng, "D %s\n", zDate);
+    free(zDate);
+    for(i=0; i<nField; i++){
+      const char *zValue;
+      int nValue;
+      zValue = SbS_Fetch(p, azField[i], -1, &nValue);
+      if( zValue ){
+        while( nValue>0 && isspace(zValue[nValue-1]) ){ nValue--; }
+        blob_appendf(&tktchng, "J %s %z\n",
+           azField[i], fossilize(zValue,nValue));
+      }
+    }
+    zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))");
+    blob_appendf(&tktchng, "K %s\n", zUuid);
+    (*(char**)pNotify) = zUuid;
+    blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : "");
+    md5sum_blob(&tktchng, &cksum);
+    blob_appendf(&tktchng, "Z %b\n", &cksum);
+
+#if 0
+    @ <hr><pre>
+    @ %h(blob_str(&tktchng))
+    @ </pre><hr>
+    blob_zero(&tktchng)
+    SbS_Pop(p, 1);
+    return SBS_OK;
 #endif
+
+    rid = content_put(&tktchng, 0, 0);
+    if( rid==0 ){
+      fossil_panic("trouble committing ticket: %s", g.zErrMsg);
+    }
+    manifest_crosslink(rid, &tktchng);
+    return SBS_RETURN;
+  }
+  SbS_Pop(p, 1);
+  return SBS_OK;
+}
+
+/*
+** WEBPAGE: tktnew
+*/
+void tktnew_page(void){
+  char *zScript;
+  int nScript;
+  char *zNewUuid = 0;
+
+  login_check_credentials();
+  if( !g.okNewTkt ){ login_needed(); return; }
+  style_header("New Ticket");
+  ticket_init();
+  getAllTicketFields();
+  initializeVariablesFromCGI();
+  @ <form method="POST" action="%s(g.zBaseURL)/tktnew">
+  zScript = (char*)SbS_Fetch(pInterp, "tktnew_template", -1, &nScript);
+  zScript = mprintf("%.*s", nScript, zScript);
+  SbS_AddVerb(pInterp, "submit_new_ticket", submitNewCmd, (void*)&zNewUuid);
+  if( SbS_Render(pInterp, zScript)==SBS_RETURN && zNewUuid ){
+    cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid));
+    return;
+  }
+  @ </form>
+  style_footer();
+}

Deleted src/tktconf.c version [d769ab0345]

Modified src/tktconfig.c from [a3312ee193] to [9a0ee09a71].

@@ -103,44 +103,43 @@
 @ # used as CGI parameter names, so to avoid problems it is best that
 @ # the names be all lower-case alphabetic characters.  The names must
 @ # be unique and must not begin with "tkt_".
 @ #
 @ {
-@    type
-@    status
-@    subsystem
-@    priority
-@    severity
-@    contact
-@    title
-@    comment
-@ } /ticket_fields
-@ {
 @    CREATE TABLE ticket(
 @      -- Do not change any column that begins with tkt_
 @      tkt_id INTEGER PRIMARY KEY,
-@      tkt_uuid TEXT UNIQUE,
-@      tkt_mtime DATE,
-@      tkt_valid BOOLEAN,
+@      tkt_uuid TEXT,
+@      tkt_mtime DATE,
 @      -- Add as many field as required below this line
 @      type TEXT,
 @      status TEXT,
 @      subsystem TEXT,
 @      priority TEXT,
 @      severity TEXT,
+@      foundin TEXT,
+@      contact TEXT,
 @      title TEXT,
-@      comment TEXT
+@      comment TEXT,
+@      -- Do not alter this UNIQUE clause:
+@      UNIQUE(tkt_uuid, tkt_mtime)
 @    );
 @    -- Add indices as desired
 @ } /ticket_sql set
 @
 @ ############################################################################
 @ # You can define additional variables here.  These variables will be
 @ # accessible to the page templates when they run.
 @ #
-@ {Code Build_Problem Documentation Feature_Request Incident} /type_choices set
-@ {High Medium Low} /priority_choices set
+@ {
+@    Code_Defect
+@    Build_Problem
+@    Documentation
+@    Feature_Request
+@    Incident
+@ } /type_choices set
+@ {Immediate High Medium Low Zero} /priority_choices set
 @ {Critical Severe Important Minor Cosmetic} /severity_choices set
 @ {
 @   Open
 @   Fixed
 @   Rejected
@@ -159,10 +158,11 @@
 @   Deferred
 @   Fixed
 @   Tested
 @   Closed
 @ } /status_choices set
+@ {} /subsystem_choices set
 @
 @ ##########################################################################
 @ # The "tktnew_template" variable is set to text which is a template for
 @ # the HTML of the "New Ticket" page.  Within this template, text contained
 @ # within [...] is subscript.  That subscript runs when the page is
@@ -172,55 +172,63 @@
 @   <!-- load database field names not found in CGI with an empty string -->
 @   <!-- start a form -->
 @   [{Open} /status set /submit submit_new_ticket]
 @   <table cellpadding="5">
 @   <tr>
-@   <td cellpadding="2">
+@   <td colspan="2">
 @   Enter a one-line summary of the problem:<br>
-@   <input type="text" name="title" size="60" value="[title html]">
+@   <input type="text" name="title" size="60" value="[{} /title get html]">
 @   </td>
 @   </tr>
 @
 @   <tr>
 @   <td align="right">Type:
-@   [/type typechoices 20 combobox]
+@   [/type type_choices 1 combobox]
 @   </td>
 @   <td>What type of ticket is this?</td>
 @   </tr>
 @
 @   <tr>
 @   <td align="right">Version:
-@   <input type="text" name="foundin" size="20" value="[foundin html]">
+@   <input type="text" name="foundin" size="20" value="[{} /foundin get html]">
 @   </td>
 @   <td>In what version or build number do you observer the problem?</td>
 @   </tr>
 @
 @   <tr>
 @   <td align="right">Severity:
-@   [/severity {High Medium Low} 8 combobox]
+@   [/severity severity_choices 1 combobox]
 @   </td>
 @   <td>How debilitating is the problem?  How badly does the problem
 @   effect the operation of the product?</td>
+@   </tr>
+@
+@   <tr>
+@   <td align="right">EMail:
+@   [/severity severity_choices 1 combobox]
+@   </td>
+@   <td>Not publically visible. Used by developers to contact you with
+@   questions.</td>
 @   </tr>
 @
 @   <tr>
 @   <td colspan="2">
 @   Enter a detailed description of the problem.
 @   For code defects, be sure to provide details on exactly how
 @   the problem can be reproduced.  Provide as much detail as
 @   possible.
 @   <br>
-@   <textarea name="comment" cols="80" rows="[comment linecount 50 max 10 min]"
-@    wrap="virtual" class="wikiedit">[comment html]</textarea><br>
-@   <br>
+@   <textarea name="comment" cols="80"
+@    rows="[{} /comment linecount 50 max 10 min]"
+@    wrap="virtual" class="wikiedit">[{} /comment get html]</textarea><br>
 @   <input type="submit" name="preview" value="Preview">
 @   </tr>
 @
-@   [0 /preview get enable_output]
+@   [/preview exists enable_output]
 @   <tr><td colspan="2">
 @   Description Preview:<br><hr>
-@   [/comment html]
+@   [{} /comment get wiki]
 @   <hr>
 @   </td></tr>
 @   [1 enable_output]
 @
 @   <tr>
@@ -246,26 +254,26 @@
 @   <table cellpadding="5">
 @   <tr><td align="right">Title:</td><td>
 @   <input type="text" name="title" value="[title html] size=60">
 @   </td></tr>
 @   <tr><td align="right">Status:</td><td>
-@   [/status status_choices 20 combobox]
+@   [/status status_choices 1 combobox]
 @   </td></tr>
 @   <tr><td align="right">Type:</td><td>
-@   [/type type_choices 20 combobox]
+@   [/type type_choices 1 combobox]
 @   </td></tr>
 @   <tr><td align="right">Severity:</td><td>
-@   [/severity {High Medium Low} 10 combobox]
+@   [/severity severity_choices 1 combobox]
 @   </td></tr>
 @   <tr><td align="right">Priority:</td><td>
-@   [/priority {High Medium Low} 10 combobox]
+@   [/priority priority_choices 1 combobox]
 @   </td></tr>
 @   <tr><td align="right">Resolution:</td><td>
-@   [/resolution resolution_choices 20 combobox]
+@   [/resolution resolution_choices 1 combobox]
 @   </td></tr>
 @   <tr><td align="right">Subsystem:</td><td>
-@   [/subsystem subsystem_choices 30 combobox]
+@   [/subsystem subsystem_choices 1 combobox]
 @   </td></tr>
 @   [/e hascap enable_output]
 @     <tr><td align="right">Contact:</td><td>
 @     <input type="text" name="contact" size="40" value="[contact html]">
 @     </td></tr>
@@ -303,40 +311,40 @@
 @ # The template for the "view ticket" page
 @ {
 @   <!-- load database fields automatically loaded into variables -->
 @   <table cellpadding="5">
 @   <tr><td align="right">Title:</td><td>
-@   [/title html]
+@   [title html]
 @   </td></tr>
 @   <tr><td align="right">Status:</td><td>
-@   [/status html]
+@   [status html]
 @   </td></tr>
 @   <tr><td align="right">Type:</td><td>
-@   [/type html]
+@   [type html]
 @   </td></tr>
 @   <tr><td align="right">Severity:</td><td>
-@   [/severity html]
+@   [severity html]
 @   </td></tr>
 @   <tr><td align="right">Priority:</td><td>
-@   [/priority html]
+@   [priority html]
 @   </td></tr>
 @   <tr><td align="right">Resolution:</td><td>
-@   [/priority html]
+@   [priority html]
 @   </td></tr>
 @   <tr><td align="right">Subsystem:</td><td>
-@   [/subsystem html]
+@   [subsystem html]
 @   </td></tr>
 @   [{e} hascap enable_output]
 @     <tr><td align="right">Contact:</td><td>
-@     [/contact html]
+@     [contact html]
 @     </td></tr>
 @   [1 enable_output]
 @   <tr><td align="right">Version&nbsp;Found&nbsp;In:</td><td>
-@   [/foundin html]
+@   [foundin html]
 @   </td></tr>
 @   <tr><td colspan="2">
 @   Description And Comments:<br>
-@   [/comment html]
+@   [comment wiki]
 @   </td></tr>
 @   </table>
 @ } /tktview_template set
 ;