summaryrefslogtreecommitdiff
path: root/src/event.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/event.c')
-rw-r--r--src/event.c620
1 files changed, 620 insertions, 0 deletions
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..75ffb6a
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,620 @@
+/*
+** 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:
+**
+** Technical Notes
+** Milestones
+** Blog posts
+** New articles
+** Process checkpoints
+** Announcements
+**
+** Do not confuse "event" artifacts with the "event" table in the
+** repository database. An "event" artifact is a technical-note: a
+** wiki- or blog-like essay that appears on the timeline. The "event"
+** table records all entries on the timeline, including tech-notes.
+**
+** (2015-02-14): Changing the name to "tech-note" most everywhere.
+*/
+#include "config.h"
+#include <assert.h>
+#include <ctype.h>
+#include "event.h"
+
+/*
+** Output a hyperlink to an technote given its tagid.
+*/
+void hyperlink_to_event_tagid(int tagid){
+ char *zId;
+ zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
+ tagid);
+ @ [%z(href("%R/technote/%s",zId))%S(zId)</a>]
+ free(zId);
+}
+
+/*
+** WEBPAGE: technote
+** WEBPAGE: event
+**
+** Display a technical note (formerly called an "event").
+**
+** PARAMETERS:
+**
+** name=ID Identify the technical note to display. ID must be
+** complete.
+** aid=ARTIFACTID Which specific version of the tech-note. Optional.
+** v=BOOLEAN Show details if TRUE. Default is FALSE. Optional.
+**
+** Display an existing tech-note identified by its ID, optionally at a
+** specific version, and optionally with additional details.
+*/
+void event_page(void){
+ int rid = 0; /* rid of the event artifact */
+ char *zUuid; /* artifact hash corresponding to rid */
+ const char *zId; /* Event identifier */
+ const char *zVerbose; /* Value of verbose option */
+ char *zETime; /* Time of the tech-note */
+ char *zATime; /* Time the artifact was created */
+ int specRid; /* rid specified by aid= parameter */
+ int prevRid, nextRid; /* Previous or next edits of this tech-note */
+ Manifest *pTNote; /* Parsed technote artifact */
+ Blob fullbody; /* Complete content of the technote body */
+ Blob title; /* Title extracted from the technote body */
+ Blob tail; /* Event body that comes after the title */
+ Stmt q1; /* Query to search for the technote */
+ int verboseFlag; /* True to show details */
+ const char *zMimetype = 0; /* Mimetype of the document */
+ const char *zFullId; /* Full event identifier */
+
+
+ /* wiki-read privilege is needed in order to read tech-notes.
+ */
+ login_check_credentials();
+ if( !g.perm.RdWiki ){
+ login_needed(g.anon.RdWiki);
+ return;
+ }
+
+ zId = P("name");
+ if( zId==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",
+ zId
+ );
+ 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);
+ style_set_current_feature("event");
+ if( rid==0 || (specRid!=0 && specRid!=rid) ){
+ style_header("No Such Tech-Note");
+ @ Cannot locate a technical note called <b>%h(zId)</b>.
+ style_finish_page();
+ return;
+ }
+ zUuid = rid_to_uuid(rid);
+ zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
+ " FROM tag"
+ " WHERE tagname GLOB 'event-%q*'",
+ zId);
+ zVerbose = P("v");
+ if( !zVerbose ){
+ zVerbose = P("verbose");
+ }
+ if( !zVerbose ){
+ zVerbose = P("detail"); /* deprecated */
+ }
+ verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
+
+ /* Extract the event content.
+ */
+ cgi_check_for_malice();
+ pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
+ if( pTNote==0 ){
+ fossil_fatal("Object #%d is not a tech-note", rid);
+ }
+ zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype));
+ blob_init(&fullbody, pTNote->zWiki, -1);
+ blob_init(&title, 0, 0);
+ blob_init(&tail, 0, 0);
+ if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
+ if( !wiki_find_title(&fullbody, &title, &tail) ){
+ blob_appendf(&title, "Tech-note %S", zId);
+ tail = fullbody;
+ }
+ }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
+ markdown_to_html(&fullbody, &title, &tail);
+ if( blob_size(&title)==0 ){
+ blob_appendf(&title, "Tech-note %S", zId);
+ }
+ }else{
+ blob_appendf(&title, "Tech-note %S", zId);
+ tail = fullbody;
+ }
+ style_header("%s", blob_str(&title));
+ if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
+ style_submenu_element("Edit", "%R/technoteedit?name=%!S", zId);
+ if( g.perm.Attach ){
+ style_submenu_element("Attach",
+ "%R/attachadd?target=%s&from=%R/technote/%!S", zFullId, zId);
+ }
+ }
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
+ style_submenu_element("Context", "%R/timeline?c=%.20s", zId);
+ if( g.perm.Hyperlink ){
+ if( verboseFlag ){
+ style_submenu_element("Plain",
+ "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
+ zId, zUuid);
+ if( nextRid ){
+ char *zNext;
+ zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
+ style_submenu_element("Next", "%R/technote?name=%!S&aid=%s&v",
+ zId, zNext);
+ free(zNext);
+ }
+ if( prevRid ){
+ char *zPrev;
+ zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
+ style_submenu_element("Prev", "%R/technote?name=%!S&aid=%s&v",
+ zId, zPrev);
+ free(zPrev);
+ }
+ }else{
+ style_submenu_element("Detail", "%R/technote?name=%!S&aid=%s&v",
+ zId, zUuid);
+ }
+ }
+
+ if( verboseFlag && g.perm.Hyperlink ){
+ int i;
+ const char *zClr = 0;
+ Blob comment;
+
+ zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate);
+ @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at
+ @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
+ @ entered by user <b>%h(pTNote->zUser)</b> on
+ @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
+ @ <blockquote>
+ for(i=0; i<pTNote->nTag; i++){
+ if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){
+ zClr = pTNote->aTag[i].zValue;
+ }
+ }
+ if( zClr && zClr[0]==0 ) zClr = 0;
+ if( zClr ){
+ @ <div style="background-color: %h(zClr);">
+ }else{
+ @ <div>
+ }
+ blob_init(&comment, pTNote->zComment, -1);
+ wiki_convert(&comment, 0, WIKI_INLINE);
+ blob_reset(&comment);
+ @ </div>
+ @ </blockquote><hr>
+ }
+
+ if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
+ wiki_convert(&fullbody, 0, 0);
+ }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
+ cgi_append_content(blob_buffer(&tail), blob_size(&tail));
+ }else{
+ @ <pre>
+ @ %h(blob_str(&fullbody))
+ @ </pre>
+ }
+ {
+ char * z = mprintf(
+ "<h2><a href='%R/attachlist?technote=%t'>Attachments</a>:</h2>",
+ zFullId
+ );
+ attachment_list(zFullId, z, 1);
+ fossil_free(z);
+ }
+ document_emit_js();
+ style_finish_page();
+ manifest_destroy(pTNote);
+}
+
+/*
+** Add or update a new tech note to the repository. rid is id of
+** the prior version of this technote, if any.
+**
+** returns 1 if the tech note was added or updated, 0 if the
+** update failed making an invalid artifact
+*/
+int event_commit_common(
+ int rid, /* id of the prior version of the technote */
+ const char *zId, /* hash label for the technote */
+ const char *zBody, /* content of the technote */
+ char *zETime, /* timestamp for the technote */
+ const char *zMimetype, /* mimetype for the technote N-card */
+ const char *zComment, /* comment shown on the timeline */
+ const char *zTags, /* tags associated with this technote */
+ const char *zClr /* Background color */
+){
+ Blob event;
+ char *zDate;
+ Blob cksum;
+ int nrid, n;
+
+ blob_init(&event, 0, 0);
+ db_begin_transaction();
+ while( fossil_isspace(zComment[0]) ) zComment++;
+ n = strlen(zComment);
+ while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
+ if( n>0 ){
+ blob_appendf(&event, "C %#F\n", 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, zId);
+ zETime[10] = ' ';
+ if( zMimetype && zMimetype[0] ){
+ blob_appendf(&event, "N %s\n", zMimetype);
+ }
+ 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( !login_is_nobody() ){
+ blob_appendf(&event, "U %F\n", login_name());
+ }
+ 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);
+ db_add_unsent(nrid);
+ if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
+ db_end_transaction(1);
+ return 0;
+ }
+ assert( blob_is_reset(&event) );
+ content_deltify(rid, &nrid, 1, 0);
+ db_end_transaction(0);
+ return 1;
+}
+
+/*
+** WEBPAGE: technoteedit
+** WEBPAGE: eventedit
+**
+** Revise or create a technical note (formerly called an "event").
+**
+** Required query parameter:
+**
+** name=ID Hex hash ID of the technote. If omitted, a new
+** tech-note is created.
+**
+** POST parameters from the "Cancel", "Preview", or "Submit" buttons:
+**
+** w=TEXT Complete text of the technote.
+** t=TEXT Time of the technote on the timeline (ISO 8601)
+** c=TEXT Timeline comment
+** g=TEXT Tags associated with this technote
+** mimetype=TEXT Mimetype for w= text
+** newclr Use a background color
+** clr=TEXT Background color to use if newclr
+**
+** For GET requests, when editing an existing technote newclr and clr
+** are implied if a custom color has been set on the previous version
+** of the technote.
+*/
+void eventedit_page(void){
+ char *zTag;
+ int rid = 0;
+ Blob event;
+ const char *zId;
+ int n;
+ const char *z;
+ char *zBody = (char*)P("w"); /* Text of the technote */
+ char *zETime = (char*)P("t"); /* Date this technote appears */
+ const char *zComment = P("c"); /* Timeline comment */
+ const char *zTags = P("g"); /* Tags added to this technote */
+ const char *zClrFlag = ""; /* "checked" for bg color */
+ const char *zClr; /* Name of the background color */
+ const char *zMimetype = P("mimetype"); /* Mimetype of zBody */
+ int isNew = 0;
+
+ if( zBody ){
+ zBody = fossil_strdup(zBody);
+ }
+ login_check_credentials();
+ zId = P("name");
+ if( zId==0 ){
+ zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
+ isNew = 1;
+ }else{
+ int nId = strlen(zId);
+ if( !validate16(zId, nId) ){
+ fossil_redirect_home();
+ return;
+ }
+ }
+ zTag = mprintf("event-%s", zId);
+ rid = db_int(0,
+ "SELECT rid FROM tagxref"
+ " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')"
+ " ORDER BY mtime DESC", zTag
+ );
+ if( rid && strlen(zId)<HNAME_MIN ){
+ zId = db_text(0,
+ "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB '%q*'",
+ zTag
+ );
+ }
+ free(zTag);
+
+ /* Need both check-in and wiki-write or wiki-create privileges in order
+ ** to edit/create an event.
+ */
+ if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
+ login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
+ return;
+ }
+ style_set_current_feature("event");
+
+ /* Figure out the color */
+ if( rid ){
+ zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
+ if( zClr && zClr[0] ){
+ const char * zRequestMethod = P("REQUEST_METHOD");
+ if(zRequestMethod && 'G'==zRequestMethod[0]){
+ /* Apply saved color by default for GET requests
+ ** (e.g., an Edit menu link).
+ */
+ zClrFlag = " checked";
+ }
+ }
+ }else{
+ zClr = "";
+ isNew = 1;
+ }
+ if( P("newclr") ){
+ zClr = PD("clr",zClr);
+ if( zClr[0] ) zClrFlag = " checked";
+ }
+
+ /* 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 || zMimetype==0)
+ ){
+ Manifest *pTNote;
+ pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
+ if( pTNote && pTNote->type==CFTYPE_EVENT ){
+ if( zBody==0 ) zBody = pTNote->zWiki;
+ if( zETime==0 ){
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
+ }
+ if( zComment==0 ) zComment = pTNote->zComment;
+ if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
+ }
+ 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) && cgi_csrf_safe(2) ){
+ if ( !event_commit_common(rid, zId, zBody, zETime,
+ zMimetype, zComment, zTags,
+ zClrFlag[0] ? zClr : 0) ){
+ style_header("Error");
+ @ Internal error: Fossil tried to make an invalid artifact for
+ @ the edited technote.
+ style_finish_page();
+ return;
+ }
+ cgi_redirectf("%R/technote?name=%T", zId);
+ }
+ if( P("cancel")!=0 ){
+ cgi_redirectf("%R/technote?name=%T", zId);
+ return;
+ }
+ if( zBody==0 ){
+ zBody = mprintf("Insert new content here...");
+ }
+ if( isNew ){
+ style_header("New Tech-note %S", zId);
+ }else{
+ style_header("Edit Tech-note %S", zId);
+ }
+ if( P("preview")!=0 ){
+ Blob com;
+ @ <p><b>Timeline comment preview:</b></p>
+ @ <blockquote>
+ @ <table border="0">
+ if( zClrFlag[0] && zClr && zClr[0] ){
+ @ <tr><td style="background-color: %h(zClr);">
+ }else{
+ @ <tr><td>
+ }
+ blob_zero(&com);
+ blob_append(&com, zComment, -1);
+ wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
+ @ </td></tr></table>
+ @ </blockquote>
+ @ <p><b>Page content preview:</b><p>
+ @ <blockquote>
+ blob_init(&event, 0, 0);
+ blob_append(&event, zBody, -1);
+ safe_html_context(DOCSRC_WIKI);
+ wiki_render_by_mimetype(&event, zMimetype);
+ @ </blockquote><hr>
+ 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;
+ @ <form method="post" action="%R/technoteedit"><div>
+ login_insert_csrf_secret();
+ @ <input type="hidden" name="name" value="%h(zId)">
+ @ <table border="0" cellspacing="10">
+
+ @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
+ @ <td valign="top">
+ @ <input type="text" name="t" size="25" value="%h(zETime)">
+ @ </td></tr>
+
+ @ <tr><th align="right" valign="top">Timeline Comment:</th>
+ @ <td valign="top">
+ @ <textarea name="c" class="technoteedit" cols="80"
+ @ rows="3" wrap="virtual">%h(zComment)</textarea>
+ @ </td></tr>
+
+ @ <tr><th align="right" valign="top">Timeline Background Color:</th>
+ @ <td valign="top">
+ @ <input type='checkbox' name='newclr'%s(zClrFlag)>
+ @ Use custom color: \
+ @ <input type='color' name='clr' value='%s(zClr[0]?zClr:"#c0f0ff")'>
+ @ </td></tr>
+
+ @ <tr><th align="right" valign="top">Tags:</th>
+ @ <td valign="top">
+ @ <input type="text" name="g" size="40" value="%h(zTags)">
+ @ </td></tr>
+
+ @ <tr><th align="right" valign="top">\
+ @ %z(href("%R/markup_help"))Markup Style</a>:</th>
+ @ <td valign="top">
+ mimetype_option_menu(zMimetype, "mimetype");
+ @ </td></tr>
+
+ @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
+ @ <td valign="top">
+ @ <textarea name="w" class="technoteedit" cols="80"
+ @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
+ @ </td></tr>
+
+ @ <tr><td colspan="2">
+ @ <input type="submit" name="cancel" value="Cancel">
+ @ <input type="submit" name="preview" value="Preview">
+ if( P("preview") ){
+ @ <input type="submit" name="submit" value="Submit">
+ }
+ @ </td></tr></table>
+ @ </div></form>
+ style_finish_page();
+}
+
+/*
+** Add a new tech note to the repository. The timestamp is
+** given by the zETime parameter. rid must be zero to create
+** a new page. If no previous page with the name zPageName exists
+** and isNew is false, then this routine throws an error.
+*/
+void event_cmd_commit(
+ char *zETime, /* timestamp */
+ int rid, /* Artifact id of the tech note */
+ Blob *pContent, /* content of the new page */
+ const char *zMimeType, /* mimetype of the content */
+ const char *zComment, /* comment to go on the timeline */
+ const char *zTags, /* tags */
+ const char *zClr /* background color */
+){
+ const char *zId; /* id of the tech note */
+
+ if ( rid==0 ){
+ zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
+ }else{
+ zId = db_text(0,
+ "SELECT substr(tagname,7) FROM tag"
+ " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
+ rid
+ );
+ }
+
+ user_select();
+ if (event_commit_common(rid, zId, blob_str(pContent), zETime,
+ zMimeType, zComment, zTags, zClr)==0 ){
+#ifdef FOSSIL_ENABLE_JSON
+ g.json.resultCode = FSL_JSON_E_ASSERT;
+#endif
+ fossil_panic("Internal error: Fossil tried to make an "
+ "invalid artifact for the technote.");
+ }
+}