/* ** 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 version 2 as published by the Free Software Foundation. ** ** 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/ ** ******************************************************************************* ** ** This file contains code used render and control ticket entry ** and display pages. */ #include "config.h" #include "tkt.h" #include /* ** The list of database user-defined fields in the TICKET table. ** The real table also contains some addition fields for internal ** used. The internal-use fields begin with "tkt_". */ static int nField = 0; static char **azField = 0; /* Names of database fields */ static char **azValue = 0; /* Original values */ static char **azAppend = 0; /* Value to be appended */ /* ** Compare two entries in azField for sorting purposes */ 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 in azField[]. ** ** Also allocate space for azValue[] and azAppend[] and initialize ** all the values there to zero. */ static void getAllTicketFields(void){ Stmt q; int i; if( nField>0 ) return; db_prepare(&q, "PRAGMA table_info(ticket)"); while( db_step(&q)==SQLITE_ROW ){ const char *zField = db_column_text(&q, 1); if( strncmp(zField,"tkt_",4)==0 ) continue; if( nField%10==0 ){ azField = realloc(azField, sizeof(azField)*3*(nField+10) ); if( azField==0 ){ fossil_fatal("out of memory"); } } azField[nField] = mprintf("%s", zField); nField++; } db_finalize(&q); qsort(azField, nField, sizeof(azField[0]), nameCmpr); azAppend = &azField[nField]; memset(azAppend, 0, sizeof(azAppend[0])*nField); azValue = &azAppend[nField]; for(i=0; izTicketUuid); } blob_zero(&sql); blob_appendf(&sql, "UPDATE ticket SET tkt_mtime=:mtime"); zSep = "SET"; for(i=0; inField; i++){ const char *zName = p->aField[i].zName; if( zName[0]=='+' ){ zName++; if( fieldId(zName)<0 ) continue; blob_appendf(&sql,", %s=%s || %Q", zName, zName, p->aField[i].zValue); }else{ 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); 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; Manifest manifest; Blob content; 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, &content); manifest_parse(&manifest, &content); ticket_insert(&manifest, createFlag, 0); manifest_clear(&manifest); createFlag = 0; } db_finalize(&q); } /* ** Create the subscript interpreter and load the ticket configuration. */ void ticket_init(void){ char *zConfig; Th_FossilInit(); zConfig = db_text((char*)zDefaultTicketConfig, "SELECT value FROM config WHERE name='ticket-configuration'"); Th_Eval(g.interp, 0, (const uchar*)zConfig, -1); } /* ** Recreate the ticket table. */ void ticket_create_table(int separateConnection){ char *zSql; int nSql; db_multi_exec("DROP TABLE IF EXISTS ticket;"); ticket_init(); zSql = (char*)Th_Fetch("ticket_sql", &nSql); if( zSql==0 ){ 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); } } /* ** 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; len = strlen(zName); if( len<20 || !validate16(zName, len) ) continue; ticket_rebuild_entry(zName); } db_finalize(&q); db_end_transaction(0); } /* ** WEBPAGE: tktview ** URL: tktview?name=UUID ** ** View a ticket. */ void tktview_page(void){ char *zScript; int nScript; login_check_credentials(); if( !g.okRdTkt ){ login_needed(); return; } if( g.okWrTkt ){ style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", g.zTop, PD("name","")); } style_header("View Ticket"); ticket_init(); initializeVariablesFromDb(); zScript = (char*)Th_Fetch("tktview_template", &nScript); zScript = mprintf("%.*s", nScript, zScript); Th_Render(zScript); style_footer(); } /* ** TH command: append_field FIELD STRING ** ** 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 command is run. */ static int appendRemarkCmd( Th_Interp *interp, void *p, int argc, const unsigned char **argv, int *argl ){ int idx; if( argc!=3 ){ return Th_WrongNumArgs(interp, "append_field FIELD STRING"); } for(idx=0; idx=nField ){ Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); return TH_ERROR; } azAppend[idx] = mprintf("%.*s", argl[2], argv[2]); return TH_OK; } /* ** Subscript command: submit_ticket ** ** Construct and submit a new ticket artifact. */ static int submitTicketCmd( Th_Interp *interp, void *pUuid, int argc, const unsigned char **argv, int *argl ){ 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; i0 && 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 ){ 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( strncmp(g.zPath,"debug_",6)==0 ){ @
    @ %h(blob_str(&tktchng))
    @ 

blob_zero(&tktchng); return TH_OK; } rid = content_put(&tktchng, 0, 0); if( rid==0 ){ fossil_panic("trouble committing ticket: %s", g.zErrMsg); } manifest_crosslink(rid, &tktchng); return TH_RETURN; } /* ** 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){ char *zScript; int nScript; char *zNewUuid = 0; login_check_credentials(); if( !g.okNewTkt ){ login_needed(); return; } style_header("New Ticket"); ticket_init(); getAllTicketFields(); initializeVariablesFromDb(); initializeVariablesFromCGI(); @
zScript = (char*)Th_Fetch("tktnew_template", &nScript); zScript = mprintf("%.*s", nScript, zScript); Th_Store("login", g.zLogin); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid, 0); if( Th_Render(zScript)==TH_RETURN && zNewUuid ){ cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid)); return; } @
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){ char *zScript; int nScript; int nName; const char *zName; int nRec; login_check_credentials(); if( !g.okApndTkt && !g.okWrTkt ){ login_needed(); return; } style_header("Edit Ticket"); zName = P("name"); if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE || !validate16(zName,nName) ){ @ Not a valid ticket id: \"%h(zName)\" style_footer(); return; } nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'", zName); if( nRec==0 ){ @ No such ticket: \"%h(zName)\" style_footer(); return; } if( nRec>1 ){ @ %d(nRec) tickets begin with: \"%h(zName)\" style_footer(); return; } ticket_init(); getAllTicketFields(); initializeVariablesFromCGI(); initializeVariablesFromDb(); @
@ zScript = (char*)Th_Fetch("tktedit_template", &nScript); zScript = mprintf("%.*s", nScript, zScript); Th_Store("login", 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( Th_Render(zScript)==TH_RETURN && zName ){ cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zName)); return; } @
style_footer(); } /* ** Check the ticket configuration in zConfig to see if it appears to ** be well-formed. If everything is OK, return NULL. If something is ** amiss, then return a pointer to a string (obtained from malloc) that ** describes the problem. */ char *ticket_config_check(const char *zConfig){ char *zErr = 0; const char *z; int n; int i; int rc; sqlite3 *db; static const char *azRequired[] = { "tktnew_template", "tktview_template", "tktedit_template", }; Th_FossilInit(); rc = Th_Eval(g.interp, 0, (const uchar*)zConfig, strlen(zConfig)); if( rc!=TH_OK ){ zErr = (char*)Th_TakeResult(g.interp, 0); return zErr; } for(i=0; i