/* ** 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 */ /* ** A subscript interpreter used for processing Tickets. */ static struct Subscript *pInterp = 0; /* ** 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; if( pInterp ) return; pInterp = SbS_Create(); zConfig = db_text((char*)zDefaultTicketConfig, "SELECT value FROM config WHERE name='ticket-configuration'"); SbS_Eval(pInterp, 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*)SbS_Fetch(pInterp, "ticket_sql", -1, &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 */ 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: 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 ){ 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; 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); SbS_Pop(p, 1); return SBS_OK; } 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 ** WEBPAGE: debug_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(); @
zScript = (char*)SbS_Fetch(pInterp, "tktnew_template", -1, &nScript); zScript = mprintf("%.*s", nScript, zScript); SbS_Store(pInterp, "login", g.zLogin, 0); SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2); SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid); if( SbS_Render(pInterp, zScript)==SBS_RETURN && zNewUuid ){ cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid)); return; } @
style_footer(); } /* ** WEBPAGE: tktedit ** WEBPAGE: debug_tktedit */ 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*)SbS_Fetch(pInterp, "tktedit_template", -1, &nScript); zScript = mprintf("%.*s", nScript, zScript); 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*)&zName); if( SbS_Render(pInterp, zScript)==SBS_RETURN && zName ){ cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zName)); return; } @
style_footer(); }