Check-in [a5e4e1ba96]
Not logged in
Overview

SHA1 Hash:a5e4e1ba961dc03d988e07400fb3c79b9cc0e2db
Date: 2007-11-24 23:59:47
User: drh
Comment:More work on ticketing. This is a non-working incremental check-in.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/subscript.c from [64131ef2ea] to [b76caa035f].

@@ -68,24 +68,25 @@
 
 /*
 ** Configuration constants
 */
 #define SBSCONFIG_NHASH    41         /* Size of the hash table */
-#define SBSCONFIG_NSTACK   10         /* Maximum stack depth */
+#define SBSCONFIG_NSTACK   20         /* Maximum stack depth */
 #define SBSCONFIG_ERRSIZE  100        /* Maximum size of an error message */
 
 /*
 ** Available token types:
 */
 #define SBSTT_WHITESPACE  1    /* ex:   \040   */
 #define SBSTT_NAME        2    /* ex:   /abcde  */
 #define SBSTT_VERB        3    /* ex:   abcde   */
 #define SBSTT_STRING      4    /* ex:   {...}   */
-#define SBSTT_INTEGER     5    /* Integer including option sign */
-#define SBSTT_INCOMPLETE  6    /* Unterminated string token */
-#define SBSTT_UNKNOWN     7    /* Unknown token */
-#define SBSTT_EOF         8    /* End of input */
+#define SBSTT_QUOTED      5    /* ex:   "...\n..." */
+#define SBSTT_INTEGER     6    /* Integer including option sign */
+#define SBSTT_INCOMPLETE  7    /* Unterminated string token */
+#define SBSTT_UNKNOWN     8    /* Unknown token */
+#define SBSTT_EOF         9    /* End of input */
 
 /*
 ** Values are stored in the hash table as instances of the following
 ** structure.
 */
@@ -159,10 +160,23 @@
   if( c=='#' ){
     int i;
     for(i=1; i<n && z[i] && z[i-1]!='\n'; i++){}
     *pTokenType = SBSTT_WHITESPACE;
     return i;
+  }
+  if( c=='"' ){
+    int i;
+    for(i=1; i<n && z[i] && z[i]!='"'; i++){
+       if( z[i]=='\\' && i<n-1 ){ i++; }
+    }
+    if( z[i]=='"' ){
+      *pTokenType = SBSTT_QUOTED;
+      return i+1;
+    }else{
+      *pTokenType = SBSTT_INCOMPLETE;
+      return i;
+    }
   }
   if( c=='{' ){
     int depth = 1;
     int i;
     for(i=1; i<n && z[i]; i++){
@@ -602,29 +616,57 @@
 #define SBSOP_DIV   4
 #define SBSOP_AND   5
 #define SBSOP_OR    6
 #define SBSOP_MIN   7
 #define SBSOP_MAX   8
+#define SBSOP_EQ    9
+#define SBSOP_NE   10
+#define SBSOP_LT   11
+#define SBSOP_GT   12
+#define SBSOP_LE   13
+#define SBSOP_GE   14
 
 /*
 ** Subscript command:      INTEGER INTEGER <binary-op> INTEGER
 */
 static int bopCmd(struct Subscript *p, void *pOp){
   int a, b, c;
   if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
-  a = SbS_StackValueInt(p, 0);
-  b = SbS_StackValueInt(p, 1);
+  a = SbS_StackValueInt(p, 1);
+  b = SbS_StackValueInt(p, 0);
   switch( (int)pOp ){
+    case SBSOP_EQ:   c = a==b;           break;
+    case SBSOP_NE:   c = a!=b;           break;
+    case SBSOP_LT:   c = a<b;            break;
+    case SBSOP_LE:   c = a<=b;           break;
+    case SBSOP_GT:   c = a>b;            break;
+    case SBSOP_GE:   c = a>=b;           break;
     case SBSOP_ADD:  c = a+b;            break;
     case SBSOP_SUB:  c = a-b;            break;
     case SBSOP_MUL:  c = a*b;            break;
     case SBSOP_DIV:  c = b!=0 ? a/b : 0; break;
     case SBSOP_AND:  c = a && b;         break;
     case SBSOP_OR:   c = a || b;         break;
     case SBSOP_MIN:  c = a<b ? a : b;    break;
     case SBSOP_MAX:  c = a<b ? b : a;    break;
   }
+  SbS_Pop(p, 2);
+  SbS_PushInt(p, c);
+  return 0;
+}
+
+/*
+** Subscript command:      STRING STRING streq INTEGER
+*/
+static int streqCmd(struct Subscript *p, void *pOp){
+  int c, nA, nB;
+  const char *A, *B;
+
+  if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
+  A = SbS_StackValue(p, 1, &nA);
+  B = SbS_StackValue(p, 0, &nB);
+  c = nA==nB && memcmp(A,B,nA)==0;
   SbS_Pop(p, 2);
   SbS_PushInt(p, c);
   return 0;
 }
 
@@ -657,10 +699,24 @@
   for(n=1, i=0; i<size; i++){
     if( z[i]=='\n' ) n++;
   }
   SbS_Pop(p, 1);
   SbS_PushInt(p, n);
+  return 0;
+}
+
+/*
+** Subscript command:     STRING length INTEGER
+**
+** Return one more than the number characters in STRING.
+*/
+static int lengthCmd(struct Subscript *p, void *pNotUsed){
+  int size;
+  if( SbS_RequireStack(p, 1, "length") ) return 1;
+  SbS_StackValue(p, 0, &size);
+  SbS_Pop(p, 1);
+  SbS_PushInt(p, size);
   return 0;
 }
 
 /*
 ** Subscript command:     NAME exists INTEGER
@@ -831,10 +887,66 @@
   }
   SbS_Pop(p, 3);
   return 0;
 }
 
+/*
+** Subscript command:     STRING BOOLEAN if
+**
+** Evaluate STRING as a script if BOOLEAN is true.
+*/
+static int ifCmd(struct Subscript *p, void *pNotUsed){
+  int cond;
+  int rc = SBS_OK;
+
+  if( SbS_RequireStack(p, 2, "if") ) return 1;
+  cond = SbS_StackValueInt(p, 0);
+  if( cond ){
+    SbSValue script = p->aStack[p->nStack-2];
+    p->aStack[p->nStack-2].flags = 0;
+    SbS_Pop(p, 2);
+    rc = SbS_Eval(p, script.u.str.z, script.u.str.size);
+    sbs_value_reset(&script);
+  }else{
+    SbS_Pop(p, 2);
+  }
+  return rc;
+}
+
+/*
+** Subscript command:     ... STRING COUNT concat STRING
+**
+** Concatenate COUNT strings into a single string and leave
+** the concatenation on the stack.
+*/
+static int concatCmd(struct Subscript *p, void *pNotUsed){
+  int count;
+  int nByte;
+  char *z;
+  int i, j;
+
+  if( SbS_RequireStack(p, 1, "concat") ) return SBS_ERROR;
+  count = SbS_StackValueInt(p, 0);
+  if( SbS_RequireStack(p, count+1, "concat") ) return SBS_ERROR;
+  SbS_Pop(p, 1);
+  nByte = 1;
+  for(i=p->nStack-count; i<p->nStack; i++){
+    nByte += p->aStack[i].u.str.size;
+  }
+  z = malloc(nByte);
+  if( z==0 ){ fossil_panic("out of memory"); }
+  for(j=0, i=p->nStack-count; i<p->nStack; i++){
+    nByte = p->aStack[i].u.str.size;
+    memcpy(&z[j], p->aStack[i].u.str.z, nByte);
+    j += nByte;
+  }
+  z[j] = 0;
+  SbS_Pop(p, count);
+  SbS_Push(p, z, j, 1);
+  return SBS_OK;
+}
+
 
 /*
 ** A table of built-in commands
 */
 static const struct {
@@ -843,24 +955,31 @@
   void *pArg;
 } aBuiltin[] = {
   { "add",             bopCmd,               (void*)SBSOP_AND    },
   { "and",             bopCmd,               (void*)SBSOP_AND    },
   { "combobox",        comboboxCmd,          0,                  },
+  { "concat",          concatCmd,            0,                  },
   { "div",             bopCmd,               (void*)SBSOP_DIV    },
   { "enable_output",   enableOutputCmd,      0                   },
+  { "eq",              bopCmd,               (void*)SBSOP_EQ     },
   { "exists",          existsCmd,            0,                  },
   { "get",             getCmd,               0,                  },
   { "hascap",          hascapCmd,            0                   },
   { "html",            putsCmd,              (void*)1            },
+  { "if",              ifCmd,                0,                  },
+  { "le",              bopCmd,               (void*)SBSOP_LE     },
+  { "length",          lengthCmd,            0                   },
   { "linecount",       linecntCmd,           0                   },
+  { "lt",              bopCmd,               (void*)SBSOP_LT     },
   { "max",             bopCmd,               (void*)SBSOP_MAX    },
   { "min",             bopCmd,               (void*)SBSOP_MIN    },
   { "mul",             bopCmd,               (void*)SBSOP_MUL    },
   { "not",             notCmd,               0                   },
   { "or",              bopCmd,               (void*)SBSOP_OR     },
   { "puts",            putsCmd,              0                   },
   { "set",             setCmd,               0                   },
+  { "streq",           streqCmd,             0                   },
   { "sub",             bopCmd,               (void*)SBSOP_SUB    },
   { "wiki",            wikiCmd,              0,                  },
 };
 
 
@@ -929,10 +1048,34 @@
         rc = SbS_Push(p, (char*)&zScript[1], n-1, 0);
         break;
       }
       case SBSTT_STRING: {
         rc = SbS_Push(p, (char*)&zScript[1], n-2, 0);
+        break;
+      }
+      case SBSTT_QUOTED: {
+        char *z = mprintf("%.*s", n-2, &zScript[1]);
+        int i, j;
+        for(i=j=0; z[i]; i++, j++){
+          int c = z[i];
+          if( c=='\\' && z[i+1] ){
+            c = z[++i];
+            if( c=='n' ){
+              c = '\n';
+            }else if( c>='0' && c<='7' ){
+              int k;
+              c -= '0';
+              for(k=1; k<3 && z[i+k]>='0' && z[i+k]<='7'; k++){
+                c = c*8 + z[i+k] - '0';
+              }
+              i += k-1;
+            }
+          }
+          z[j] = c;
+        }
+        z[j] = 0;
+        rc = SbS_Push(p, z, j, 1);
         break;
       }
       case SBSTT_VERB: {
         /* First look up the verb in the hash table */
         const SbSValue *pVal = sbs_fetch(&p->symTab, (char*)zScript, n);

Modified src/tkt.c from [c07694441f] to [f8ab2686b7].

@@ -50,11 +50,14 @@
   return strcmp(*(char**)a, *(char**)b);
 }
 
 /*
 ** Obtain a list of all fields of the TICKET table.  Put them
-** in sorted order.
+** in sorted order in azField[].
+**
+** Also allocate space for azValue[] and azAppend[] and initialize
+** all the values there to zero.
 */
 static void getAllTicketFields(void){
   Stmt q;
   if( nField>0 ) return;
   db_prepare(&q, "PRAGMA table_info(ticket)");
@@ -76,18 +79,19 @@
   memset(azAppend, 0, sizeof(azAppend[0])*nField*2);
   azValue = &azAppend[nField];
 }
 
 /*
-** Return true if zField is a field within the TICKET table.
-*/
-static int isTicketField(const char *zField){
+** Return the index into azField[] of the given field name.
+** Return -1 if zField is not in azField[].
+*/
+static int fieldId(const char *zField){
   int i;
   for(i=0; i<nField; i++){
-    if( strcmp(azField[i], zField)==0 ) return 1;
-  }
-  return 0;
+    if( strcmp(azField[i], zField)==0 ) return i;
+  }
+  return -1;
 }
 
 /*
 ** Query the database for all TICKET fields for the specific
 ** ticket whose name is given by the "name" CGI parameter.
@@ -189,14 +193,14 @@
   zSep = "SET";
   for(i=0; i<p->nField; i++){
     const char *zName = p->aField[i].zName;
     if( zName[0]=='+' ){
       zName++;
-      if( !isTicketField(zName) ) continue;
+      if( fieldId(zName)<0 ) continue;
       blob_appendf(&sql,", %s=%s || %Q", zName, zName, p->aField[i].zValue);
     }else{
-      if( !isTicketField(zName) ) continue;
+      if( fieldId(zName)<0 ) continue;
       blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
     }
   }
   blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
                      p->zTicketUuid);
@@ -312,11 +316,11 @@
   SbS_Render(pInterp, zScript);
   style_footer();
 }
 
 /*
-** Subscript command:   LABEL submit_new_ticket
+** Subscript command:   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){
@@ -370,10 +374,108 @@
   }
   SbS_Pop(p, 1);
   return SBS_OK;
 }
 
+
+/*
+** Subscript command:   STRING FIELD append_field
+**
+** FIELD is the name of a database column to which we might want
+** to append text.  STRING is the text to be appended to that
+** column.  The append does not actually occur until the
+** submit_ticket_change verb is run.
+*/
+static int appendRemarkCmd(struct Subscript *p, void *notUsed){
+  int idx;
+  const char *zField, *zValue;
+  int nField, nValue;
+
+  if( SbS_RequireStack(p, 2, "append_field") ) return 1;
+  zField = SbS_StackValue(p, 0, &nField);
+  for(idx=0; idx<nField; idx++){
+    if( strncmp(azField[idx], zField, nField)==0 && azField[idx][nField]==0 ){
+      break;
+    }
+  }
+  if( idx>=nField ){
+    SbS_SetErrorMessage(p, "no such TICKET column: %.*s", nField, zField);
+    return SBS_ERROR;
+  }
+  zValue = SbS_StackValue(p, 1, &nValue);
+  azAppend[idx] = mprintf("%.*s", nValue, zValue);
+  SbS_Pop(p, 2);
+  return SBS_OK;
+}
+
+/*
+** Subscript command:   submit_ticket
+**
+** Construct and submit a new ticket artifact.
+*/
+static int submitTicketCmd(struct Subscript *p, void *pUuid){
+  char *zDate;
+  const char *zUuid;
+  int i;
+  int rid;
+  Blob tktchng, cksum;
+
+  zUuid = (const char *)pUuid;
+  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;
+    if( azAppend[i] ){
+      blob_appendf(&tktchng, "J +%s %z\n", azField[i],
+                   fossilize(azAppend[i], -1));
+    }else{
+      zValue = SbS_Fetch(p, azField[i], -1, &nValue);
+      if( zValue ){
+        while( nValue>0 && isspace(zValue[nValue-1]) ){ nValue--; }
+        if( strncmp(zValue, azValue[i], nValue)
+                || strlen(azValue[i])!=nValue ){
+          blob_appendf(&tktchng, "J %s %z\n",
+             azField[i], fossilize(zValue,nValue));
+        }
+      }
+    }
+  }
+  if( *(char**)pUuid==0 ){
+    zUuid = db_text(0,
+       "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", 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 1
+  @ <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;
+}
+
+
 /*
 ** WEBPAGE: tktnew
 */
 void tktnew_page(void){
   char *zScript;
@@ -387,11 +489,13 @@
   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);
+  SbS_Store(pInterp, "login", g.zLogin, 0);
+  SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
+  SbS_AddVerb(pInterp, "submit_ticket", submitNewCmd, (void*)&zNewUuid);
   if( SbS_Render(pInterp, zScript)==SBS_RETURN && zNewUuid ){
     cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid));
     return;
   }
   @ </form>
@@ -399,171 +503,19 @@
 }
 
 
 
 /*
-** Subscript command:   STR1 STR2 USERVAR APPENDVAR FIELD append_remark
-**
-** FIELD is the name of a database column to which we might want
-** to append text.  APPENDVAR is the name of a CGI parameter which
-** (if it exists) contains the text to be appended.  The append
-** operation will only happen if APPENDVAR exists.  USERVAR is
-** a CGI parameter which contains the name that the user wants to
-** to be known by.  STR1 and STR2 are prefixes that are prepended
-** to the text in the APPENDVAR CGI parameter.  STR1 is used if
-** USERVAR is the same as g.zLogin or if USERVAR does not exist.
-** STR2 is used if USERVAR exists and is different than g.zLogin.
-** Within STR1 and STR2, the following substitutions occur:
-**
-**     %LOGIN%    The value of g.zLogin
-**     %USER%     The value of the USERVAR CGI parameter
-**     %DATE%     The current date and time
-**
-** The concatenation STR1 or STR2 with the content of APPENDVAR
-** is written into azApnd[] in the FIELD slot so that it will be
-** picked up and used by the submit_ticket_change command.
+** WEBPAGE: tktedit
 */
-static int appendRemarkCmd(struct Subscript *p, void *notUsed){
-  int i, j, idx;
-  const char *zField, *zAppendVar, *zUserVar, *zStr, *zValue, *zUser;
-  int nField, nAppendVar, nUserVar, nStr, nValue, nUser;
-
-  if( SbS_RequireStack(p, 5, "append_remark") ) return 1;
-  zField = SbS_StackValue(p, 0, &nField);
-  for(idx=0; idx<nField; idx++){
-    if( strncmp(azField[idx], zField, nField)==0 && azField[idx][nField]==0 ){
-      break;
-    }
-  }
-  if( idx>=nField ){
-    SbS_SetErrorMessage(p, "no such TICKET column: %.*s", nField, zField);
-    return SBS_ERROR;
-  }
-  zAppendVar = SbS_StackValue(p, 1, &nAppendVar);
-  zValue = SbS_Fetch(p, zAppendVar, nAppendVar, &nValue);
-  if( zValue ){
-    Blob out;
-    blob_zero(&out);
-    zUserVar = SbS_StackValue(p, 2, &nUserVar);
-    zUser = SbS_Fetch(p, zUserVar, nUserVar, &nUser);
-    if( zUser && (strncmp(zUser, g.zLogin, nUser) || g.zLogin[nUser]!=0) ){
-      zStr = SbS_StackValue(p, 3, &nStr);
-    }else{
-      zStr = SbS_StackValue(p, 4, &nStr);
-    }
-    for(i=j=0; i<nStr; i++){
-      if( zStr[i]!='%' ) continue;
-      if( i>j ){
-        blob_append(&out, &zStr[j], i-j);
-        j = i;
-      }
-      if( strncmp(&zStr[j], "%USER%", 6)==0 ){
-        blob_appendf(&out, "%z", htmlize(zUser, nUser));
-        i += 5;
-        j = i+1;
-      }else if( strncmp(&zStr[j], "%LOGIN%", 7)==0 ){
-        blob_appendf(&out, "%z", htmlize(g.zLogin, -1));
-        i += 6;
-        j = i+1;
-      }else if( strncmp(&zStr[j], "%DATE%", 6)==0 ){
-        blob_appendf(&out, "%z", db_text(0, "SELECT datetime('now')"));
-        i += 5;
-        j = i+1;
-      }
-    }
-    if( i>j ){
-      blob_append(&out, &zStr[j], i-j);
-    }
-    blob_append(&out, zValue, nValue);
-    azAppend[idx] = blob_str(&out);
-  }
-  SbS_Pop(p, 5);
-  return SBS_OK;
-}
-
-/*
-** Subscript command:   LABEL submit_ticket_change
-**
-** If the variable named LABEL exists, then submit a change to
-** the ticket identified by the "name" CGI parameter.
-*/
-static int submitEditCmd(struct Subscript *p, void *pNotify){
-  const char *zLabel;
-  int nLabel, size;
-
-  if( SbS_RequireStack(p, 1, "submit_ticket_change") ) 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;
-      if( azAppend[i] ){
-        blob_appendf(&tktchng, "J +%s %z\n", azField[i],
-                     fossilize(azAppend[i], -1));
-      }else{
-        zValue = SbS_Fetch(p, azField[i], -1, &nValue);
-        if( zValue ){
-          while( nValue>0 && isspace(zValue[nValue-1]) ){ nValue--; }
-          if( strncmp(zValue, azValue[i], nValue)
-                  || strlen(azValue[i])!=nValue ){
-            blob_appendf(&tktchng, "J %s %z\n",
-               azField[i], fossilize(zValue,nValue));
-          }
-        }
-      }
-    }
-    zUuid = db_text(0,
-       "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'",
-       P("name")
-    );
-    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 1
-    @ <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: tktedit
-*/
 void tktedit_page(void){
   char *zScript;
   int nScript;
-  int chnged = 0;
   int nName;
   const char *zName;
   int nRec;
+  char *zUuid = 0;
 
   login_check_credentials();
   if( !g.okApndTkt && !g.okWrTkt ){ login_needed(); return; }
   style_header("Edit Ticket");
   zName = P("name");
@@ -591,14 +543,16 @@
   initializeVariablesFromDb();
   @ <form method="POST" action="%s(g.zBaseURL)/tktedit">
   @ <input type="hidden" name="name" value="%s(zName)">
   zScript = (char*)SbS_Fetch(pInterp, "tktedit_template", -1, &nScript);
   zScript = mprintf("%.*s", nScript, zScript);
-  SbS_AddVerb(pInterp, "append_remark", appendRemarkCmd, 0);
-  SbS_AddVerb(pInterp, "submit_ticket_change", submitEditCmd, (void*)&chnged);
-  if( SbS_Render(pInterp, zScript)==SBS_RETURN && chnged ){
-    cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zName));
+  SbS_Store(pInterp, "login", g.zLogin, 0);
+  SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
+  SbS_AddVerb(pInterp, "append_field", appendRemarkCmd, 0);
+  SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zUuid);
+  if( SbS_Render(pInterp, zScript)==SBS_RETURN && zUuid ){
+    cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zUuid));
     return;
   }
   @ </form>
   style_footer();
 }

Modified src/tktconfig.c from [dabb2f23c9] to [745ff35aaa].

@@ -169,11 +169,14 @@
 @ # rendered.
 @ #
 @ {
 @   <!-- load database field names not found in CGI with an empty string -->
 @   <!-- start a form -->
-@   [{Open} /status set /submit submit_new_ticket]
+@   [{
+@      {Open} /status set
+@       submit_new_ticket
+@   } /submit exists if]
 @   <table cellpadding="5">
 @   <tr>
 @   <td colspan="2">
 @   Enter a one-line summary of the problem:<br>
 @   <input type="text" name="title" size="60" value="[{} /title get html]">
@@ -242,20 +245,34 @@
 @   <!-- end of form -->
 @ } /tktnew_template set
 @
 @ ##########################################################################
 @ # The template for the "edit ticket" page
+@ #
+@ # Then generated text is inserted inside a form which feeds back to itself.
+@ # All CGI parameters are loaded into variables.  All database files are
+@ # loaded into variables if they have not previously been loaded by
+@ # CGI parameters.
 @ {
-@   <!-- database field names not found as CGI parameters are loaded
-@        from the database automatically -->
-@   <!-- start a form -->
-@   [{
-@     <hr><i>%LOGIN% added on %DATE%:</i><br>
-@    } {
-@     <hr><i>%LOGIN% claiming to be %USER% added on %DATE%:</i><br>
-@    } /username /cmappnd /comment append_remark
-@   /submit submit_ticket_change]
+@   [
+@     login /username get /username set
+@     {
+@       {
+@         username login eq /samename set
+@  "samename=" html samename html "<br>" puts
+@         {
+@            "\n<hr><i>" login " added on " date ":</i></br>\n" cmappnd 6 concat
+@            /comment append_field
+@         } samename if
+@         {
+@            "\n<hr><i>" login " claiming to be " username " added on " date
+@            "</i><br>\n" cmappnd 8 concat /comment append_field
+@         } samename not if
+@       } 0 {} /cmappnd get length lt if
+@       submit_ticket
+@     } /submit exists if
+@   ]
 @   <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>
@@ -283,34 +300,40 @@
 @   [1 enable_output]
 @   <tr><td align="right">Version&nbsp;Found&nbsp;In:</td><td>
 @   <input type="text" name="foundin" size="50" value="[foundin html]">
 @   </td></tr>
 @   <tr><td colspan="2">
-@   [0 /eall 0 get /eall set]
-@   [/aonlybtn exists not /eall set]
-@   [/eallbtn exists /eall set]
-@   [/w hascap eall and /eall set]
+@
+@   [
+@      0 /eall get /eall set           # eall means "edit all".  default==no
+@      /aonlybtn exists not /eall set  # Edit all if no aonlybtn CGI param
+@      /eallbtn exists /eall set       # Edit all if eallbtn CGI param
+@      /w hascap eall and /eall set    # WrTkt permission needed to edit all
+@   ]
+@
 @   [eall enable_output]
 @     Description And Comments:<br>
 @     <textarea name="comment" cols="80"
 @      rows="[{} /comment get linecount 15 max 10 min html]"
 @      wrap="virtual" class="wikiedit">[comment html]</textarea><br>
 @     <input type="hidden" name="eall" value="1">
 @     <input type="submit" name="aonlybtn" value="Append Remark">
+@
 @   [eall not enable_output]
-@     Append Remark:<br>
+@     Append Remark from
+@     <input type="text" name="username" value="[username html]" size="30">:<br>
 @     <textarea name="cmappnd" cols="80" rows="15"
 @      wrap="virtual" class="wikiedit">[{} /cmappnd get html]</textarea><br>
 @     [/w hascap eall not and enable_output]
 @     <input type="submit" name="eallbtn" value="Edit All">
+@
 @   [1 enable_output]
 @   </td></tr>
 @   <tr><td align="right"></td><td>
 @   <input type="submit" name="submit" value="Submit Changes">
 @   </td></tr>
 @   </table>
-@   <!-- end-form inserted automatically -->
 @ } /tktedit_template set
 @
 @ ##########################################################################
 @ # The template for the "view ticket" page
 @ {