/* ** Copyright (c) 2010 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code to do formatting of event messages: ** ** Milestones ** Blog posts ** New articles ** Process checkpoints ** Announcements */ #include #include #include "config.h" #include "event.h" /* ** Output a hyperlink to an event given its tagid. */ void hyperlink_to_event_tagid(int tagid){ char *zEventId; char zShort[12]; zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", tagid); sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zEventId); if( g.okHistory ){ @ [%s(zShort)] }else{ @ [%s(zShort)] } free(zEventId); } /* ** WEBPAGE: event ** URL: /event ** PARAMETERS: ** ** name=EVENTID // Identify the event to display EVENTID must be complete ** detail=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. ** aid=ARTIFACTID // Which specific version of the event. Optional. ** ** Display an existing event identified by EVENTID */ void event_page(void){ int rid = 0; /* rid of the event artifact */ char *zUuid; /* UUID corresponding to rid */ const char *zEventId; /* Event identifier */ char *zETime; /* Time of the event */ char *zATime; /* Time the artifact was created */ int specRid; /* rid specified by aid= parameter */ int prevRid, nextRid; /* Previous or next edits of this event */ Manifest *pEvent; /* Parsed event artifact */ Blob fullbody; /* Complete content of the event body */ Blob title; /* Title extracted from the event body */ Blob tail; /* Event body that comes after the title */ Stmt q1; /* Query to search for the event */ int showDetail; /* True to show details */ /* wiki-read privilege is needed in order to read events. */ login_check_credentials(); if( !g.okRdWiki ){ login_needed(); return; } zEventId = P("name"); if( zEventId==0 ){ fossil_redirect_home(); return; } zUuid = (char*)P("aid"); specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; rid = nextRid = prevRid = 0; db_prepare(&q1, "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" " ORDER BY mtime DESC", zEventId ); while( db_step(&q1)==SQLITE_ROW ){ nextRid = rid; rid = db_column_int(&q1, 0); if( specRid==0 || specRid==rid ){ if( db_step(&q1)==SQLITE_ROW ){ prevRid = db_column_int(&q1, 0); } break; } } db_finalize(&q1); if( rid==0 || (specRid!=0 && specRid!=rid) ){ style_header("No Such Event"); @ Cannot locate specified event style_footer(); return; } zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); showDetail = atoi(PD("detail","0")); /* Extract the event content. */ pEvent = manifest_get(rid, CFTYPE_EVENT); if( pEvent==0 ){ fossil_panic("Object #%d is not an event", rid); } blob_init(&fullbody, pEvent->zWiki, -1); if( wiki_find_title(&fullbody, &title, &tail) ){ style_header(blob_str(&title)); }else{ style_header("Event %S", zEventId); tail = fullbody; } if( g.okWrWiki && g.okWrite && nextRid==0 ){ style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", g.zTop, zEventId); } zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); style_submenu_element("Context", "Context", "%s/timeline?c=%T", g.zTop, zETime); if( g.okHistory ){ if( showDetail ){ style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", g.zTop, zEventId, zUuid); if( nextRid ){ char *zNext; zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); style_submenu_element("Next", "Next", "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zNext); free(zNext); } if( prevRid ){ char *zPrev; zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); style_submenu_element("Prev", "Prev", "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zPrev); free(zPrev); } }else{ style_submenu_element("Detail", "Detail", "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zUuid); } } if( showDetail && g.okHistory ){ int i; const char *zClr = 0; Blob comment; zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); @

Event [%S(zUuid)] at @ [%s(zETime)] @ entered by user %h(pEvent->zUser) on @ [%s(zATime)]:

@
for(i=0; inTag; i++){ if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ zClr = pEvent->aTag[i].zValue; } } if( zClr && zClr[0]==0 ) zClr = 0; if( zClr ){ @
}else{ @
} blob_init(&comment, pEvent->zComment, -1); wiki_convert(&comment, 0, WIKI_INLINE); blob_reset(&comment); @
@

} wiki_convert(&tail, 0, 0); style_footer(); manifest_destroy(pEvent); } /* ** WEBPAGE: eventedit ** URL: /eventedit?name=EVENTID ** ** Edit an event. If name is omitted, create a new event. */ void eventedit_page(void){ char *zTag; int rid = 0; Blob event; const char *zEventId; char *zHtmlPageName; int n; const char *z; char *zBody = (char*)P("w"); char *zETime = (char*)P("t"); const char *zComment = P("c"); const char *zTags = P("g"); const char *zClr; if( zBody ){ zBody = mprintf("%s", zBody); } login_check_credentials(); zEventId = P("name"); if( zEventId==0 ){ zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))"); }else{ int nEventId = strlen(zEventId); if( nEventId!=40 || !validate16(zEventId, 40) ){ fossil_redirect_home(); return; } } zTag = mprintf("event-%s", zEventId); rid = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " ORDER BY mtime DESC", zTag ); free(zTag); /* Need both check-in and wiki-write or wiki-create privileges in order ** to edit/create an event. */ if( !g.okWrite || (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){ login_needed(); return; } /* Figure out the color */ if( rid ){ zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); }else{ zClr = ""; } zClr = PD("clr",zClr); if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); /* If editing an existing event, extract the key fields to use as ** a starting point for the edit. */ if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ Manifest *pEvent; pEvent = manifest_get(rid, CFTYPE_EVENT); if( pEvent && pEvent->type==CFTYPE_EVENT ){ if( zBody==0 ) zBody = pEvent->zWiki; if( zETime==0 ){ zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); } if( zComment==0 ) zComment = pEvent->zComment; } if( zTags==0 ){ zTags = db_text(0, "SELECT group_concat(substr(tagname,5),', ')" " FROM tagxref, tag" " WHERE tagxref.rid=%d" " AND tagxref.tagid=tag.tagid" " AND tag.tagname GLOB 'sym-*'", rid ); } } zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ char *zDate; Blob cksum; int nrid; blob_zero(&event); db_begin_transaction(); login_verify_csrf_secret(); blob_appendf(&event, "C %F\n", zComment); zDate = date_in_standard_format("now"); blob_appendf(&event, "D %s\n", zDate); free(zDate); zETime[10] = 'T'; blob_appendf(&event, "E %s %s\n", zETime, zEventId); zETime[10] = ' '; if( rid ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_appendf(&event, "P %s\n", zUuid); free(zUuid); } if( zClr && zClr[0] ){ blob_appendf(&event, "T +bgcolor * %F\n", zClr); } if( zTags && zTags[0] ){ Blob tags, one; int i, j; Stmt q; char *zBlob; /* Load the tags string into a blob */ blob_zero(&tags); blob_append(&tags, zTags, -1); /* Collapse all sequences of whitespace and "," characters into ** a single space character */ zBlob = blob_str(&tags); for(i=j=0; zBlob[i]; i++, j++){ if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){ while( fossil_isspace(zBlob[i+1]) ){ i++; } zBlob[j] = ' '; }else{ zBlob[j] = zBlob[i]; } } blob_resize(&tags, j); /* Parse out each tag and load it into a temporary table for sorting */ db_multi_exec("CREATE TEMP TABLE newtags(x);"); while( blob_token(&tags, &one) ){ db_multi_exec("INSERT INTO newtags VALUES(%B)", &one); } blob_reset(&tags); /* Extract the tags in sorted order and make an entry in the ** artifact for each. */ db_prepare(&q, "SELECT x FROM newtags ORDER BY x"); while( db_step(&q)==SQLITE_ROW ){ blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0)); } db_finalize(&q); } if( g.zLogin ){ blob_appendf(&event, "U %F\n", g.zLogin); } blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody); md5sum_blob(&event, &cksum); blob_appendf(&event, "Z %b\n", &cksum); blob_reset(&cksum); nrid = content_put(&event, 0, 0, 0); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); manifest_crosslink(nrid, &event); blob_reset(&event); content_deltify(rid, nrid, 0); db_end_transaction(0); cgi_redirectf("event?name=%T", zEventId); } if( P("cancel")!=0 ){ cgi_redirectf("event?name=%T", zEventId); return; } if( zBody==0 ){ zBody = mprintf("Event Text"); } zHtmlPageName = mprintf("Edit Event %S", zEventId); style_header(zHtmlPageName); if( P("preview")!=0 ){ Blob title, tail, com; @

Timeline comment preview:

@
@ if( zClr && zClr[0] ){ @
}else{ @
} blob_zero(&com); blob_append(&com, zComment, -1); wiki_convert(&com, 0, WIKI_INLINE); @
@
@

Page content preview:

@

blob_zero(&event); blob_append(&event, zBody, -1); if( wiki_find_title(&event, &title, &tail) ){ @

%h(blob_str(&title))

wiki_convert(&tail, 0, 0); }else{ wiki_convert(&event, 0, 0); } @

blob_reset(&event); } for(n=2, z=zBody; z[0]; z++){ if( z[0]=='\n' ) n++; } if( n<20 ) n = 20; if( n>40 ) n = 40; @
login_insert_csrf_secret(); @ @ @ @ @ @ @ @ @ @ @ @ @
Event Time: @ @
Timeline Comment: @ @
Background Color: render_color_chooser(0, zClr, 0, "clr", "cclr"); @
Tags: @ @
Page Content: @ @
@ @ @ @
@
style_footer(); }