Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -41,11 +41,13 @@ if( zPage && zTkt ) zTkt = 0; login_check_credentials(); blob_zero(&sql); blob_append(&sql, - "SELECT datetime(mtime,'localtime'), src, target, filename, comment, user" + "SELECT datetime(mtime,'localtime'), src, target, filename," + " comment, user," + " (SELECT uuid FROM blob WHERE rid=attachid), attachid" " FROM attachment", -1 ); if( zPage ){ if( g.perm.RdWiki==0 ) login_needed(); @@ -59,17 +61,20 @@ if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed(); style_header("All Attachments"); } blob_appendf(&sql, " ORDER BY mtime DESC"); db_prepare(&q, "%s", blob_str(&sql)); + @
    while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zSrc = db_column_text(&q, 1); const char *zTarget = db_column_text(&q, 2); const char *zFilename = db_column_text(&q, 3); const char *zComment = db_column_text(&q, 4); const char *zUser = db_column_text(&q, 5); + const char *zUuid = db_column_text(&q, 6); + int attachid = db_column_int(&q, 7); int i; char *zUrlTail; for(i=0; zFilename[i]; i++){ if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ zFilename = &zFilename[i+1]; @@ -79,12 +84,16 @@ if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); }else{ zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } - @ - @

    %h(zFilename) + @

  1. + @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid) + if( moderation_pending(attachid) ){ + @ *** Awaiting Moderator Approval *** + } + @
    %h(zFilename) @ [download]
    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; if( zComment && zComment[0] ){ @ %w(zComment)
    } @@ -111,10 +120,11 @@ @ by %h(zUser) on hyperlink_to_date(zDate, "."); free(zUrlTail); } db_finalize(&q); + @

style_footer(); return; } /* @@ -182,10 +192,34 @@ cgi_replace_parameter("m", mimetype_from_name(zFile)); rawartifact_page(); } } +/* +** Save an attachment control artifact into the repository +*/ +static void attach_put( + Blob *pAttach, /* Text of the Attachment record */ + int attachRid, /* RID for the file that is being attached */ + int needMod /* True if the attachment is subject to moderation */ +){ + int rid; + if( needMod ){ + rid = content_put_ex(pAttach, 0, 0, 0, 1); + moderation_table_create(); + db_multi_exec( + "INSERT INTO modreq(objid,attachRid) VALUES(%d,%d);", + rid, attachRid + ); + }else{ + rid = content_put(pAttach); + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid); + db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid); + } + manifest_crosslink(rid, pAttach); +} + /* ** WEBPAGE: attachadd ** ** tkt=TICKETUUID @@ -240,10 +274,11 @@ char *zDate; int rid; int i, n; int addCompress = 0; Manifest *pManifest; + int needModerator; db_begin_transaction(); blob_init(&content, aContent, szContent); pManifest = manifest_parse(&content, 0, 0); manifest_destroy(pManifest); @@ -250,11 +285,14 @@ blob_init(&content, aContent, szContent); if( pManifest ){ blob_compress(&content, &content); addCompress = 1; } - rid = content_put(&content); + needModerator = + (zTkt!=0 && g.perm.ModTkt==0 && db_get_boolean("modreq-tkt",0)==1) || + (zPage!=0 && g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1); + rid = content_put_ex(&content, 0, 0, 0, needModerator); zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_zero(&manifest); for(i=n=0; zName[i]; i++){ if( zName[i]=='/' || zName[i]=='\\' ) n = i; } @@ -272,12 +310,11 @@ zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); - rid = content_put(&manifest); - manifest_crosslink(rid, &manifest); + attach_put(&manifest, rid, needModerator); assert( blob_is_reset(&manifest) ); db_end_transaction(0); cgi_redirect(zFrom); } style_header("Add Attachment"); @@ -298,50 +335,85 @@ @ @ style_footer(); } - -/* -** WEBPAGE: attachdelete -** -** tkt=TICKETUUID -** page=WIKIPAGE -** file=FILENAME -** -** "Delete" an attachment. Because objects in Fossil are immutable -** the attachment isn't really deleted. Instead, we change the content -** of the attachment to NULL, which the system understands as being -** deleted. Historical values of the attachment are preserved. -*/ -void attachdel_page(void){ - const char *zPage = P("page"); - const char *zTkt = P("tkt"); - const char *zFile = P("file"); - const char *zFrom = P("from"); - const char *zTarget; - - if( zPage && zTkt ) fossil_redirect_home(); - if( zPage==0 && zTkt==0 ) fossil_redirect_home(); - if( zFile==0 ) fossil_redirect_home(); +/* +** WEBPAGE: ainfo +** URL: /ainfo?name=ARTIFACTID +** +** Show the details of an attachment artifact. +*/ +void ainfo_page(void){ + int rid; /* RID for the control artifact */ + int ridSrc; /* RID for the attached file */ + char *zDate; /* Date attached */ + const char *zUuid; /* UUID of the control artifact */ + Manifest *pAttach; /* Parse of the control artifact */ + const char *zTarget; /* Wiki or ticket attached to */ + const char *zSrc; /* UUID of the attached file */ + const char *zName; /* Name of the attached file */ + const char *zDesc; /* Description of the attached file */ + const char *zWikiName = 0; /* Wiki page name when attached to Wiki */ + const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */ + int modPending; /* True if awaiting moderation */ + const char *zModAction; /* Moderation action or NULL */ + int isModerator; /* TRUE if user is the moderator */ + const char *zMime; /* MIME Type */ + Blob attach; /* Content of the attachment */ + int wantToDelete = P("del")!=0;/* Want to delete */ + login_check_credentials(); - if( zPage ){ - if( g.perm.WrWiki==0 || g.perm.Attach==0 ) login_needed(); - zTarget = zPage; - }else{ - if( g.perm.WrTkt==0 || g.perm.Attach==0 ) login_needed(); - zTarget = zTkt; - } - if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); - if( P("cancel") ){ - cgi_redirect(zFrom); - } - if( P("confirm") ){ + if( !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; } + rid = name_to_rid_www("name"); + if( rid==0 ){ fossil_redirect_home(); } + zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); +#if 0 + /* Shunning here needs to get both the attachment control artifact and + ** the object that is attached. */ + if( g.perm.Admin ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + g.zTop, zUuid); + }else{ + style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", + g.zTop, zUuid); + } + } +#endif + pAttach = manifest_get(rid, CFTYPE_ATTACHMENT); + if( pAttach==0 ) fossil_redirect_home(); + zTarget = pAttach->zAttachTarget; + zSrc = pAttach->zAttachSrc; + ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%s'", zSrc); + zName = pAttach->zAttachName; + zDesc = pAttach->zComment; + if( validate16(zTarget, strlen(zTarget)) + && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%s'", zTarget) + ){ + zTktUuid = zTarget; + if( !g.perm.RdTkt ){ login_needed(); return; } + if( g.perm.WrTkt ){ + style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); + } + }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){ + zWikiName = zTarget; + if( !g.perm.RdWiki ){ login_needed(); return; } + if( g.perm.WrWiki ){ + style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); + } + } + zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); + + if( P("confirm") + && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) + ){ int i, n, rid; char *zDate; Blob manifest; Blob cksum; + const char *zFile = zName; db_begin_transaction(); blob_zero(&manifest); for(i=n=0; zFile[i]; i++){ if( zFile[i]=='/' || zFile[i]=='\\' ) n = i; @@ -355,24 +427,153 @@ md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); rid = content_put(&manifest); manifest_crosslink(rid, &manifest); db_end_transaction(0); - cgi_redirect(zFrom); - } - style_header("Delete Attachment"); - @
- @

Confirm that you want to delete the attachment named - @ "%h(zFile)" on %s(zTkt?"ticket":"wiki page") %h(zTarget):

- if( zTkt ){ - @ + @

The attachment below has been deleted.

+ } + + if( P("del") + && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) + ){ + @ + @

Confirm you want to delete the attachment shown below. + @ + @

+ } + + isModerator = (zTktUuid && g.perm.ModTkt) || (zWikiName && g.perm.ModWiki); + if( isModerator && (zModAction = P("modaction"))!=0 ){ + if( strcmp(zModAction,"delete")==0 ){ + moderation_disapprove(rid); + if( zTktUuid ){ + cgi_redirectf("%R/tktview/%s", zTktUuid); + }else{ + cgi_redirectf("%R/wiki?name=%t", zWikiName); + } + return; + } + if( strcmp(zModAction,"approve")==0 ){ + moderation_approve(rid); + } + } + style_header("Attachment Details"); + style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); + + @
Overview
+ @

+ @ + @ + @ + } + if( zWikiName ){ + @ + @ + } + @ "); + free(zDate); + @ "); + @ + @ + zMime = mimetype_from_name(zName); + if( g.perm.Setup ){ + @ + } + @ + @
Artifact ID:%z(href("%R/artifact/%s",zUuid))%s(zUuid) + if( g.perm.Setup ){ + @ (%d(rid)) + } + modPending = moderation_pending(rid); + if( modPending ){ + @ *** Awaiting Moderator Approval *** + } + if( zTktUuid ){ + @
Ticket:%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)
Wiki Page:%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)
Date: + hyperlink_to_date(zDate, "
User: + hyperlink_to_user(pAttach->zUser, zDate, "
Artifact Attached:%z(href("%R/artifact/%s",zSrc))%s(zSrc) + if( g.perm.Setup ){ + @ (%d(ridSrc)) + } + @
Filename:%h(zName)
MIME-Type:%h(zMime)
Description:%h(zDesc)
+ + if( isModerator && modPending ){ + @

Moderation
+ @
+ @
+ @
+ @
+ @ + @
+ @
+ } + + @
Content Appended
+ @
+ blob_zero(&attach); + if( zMime==0 || strncmp(zMime,"text/", 5)==0 ){ + const char *z; + const char *zLn = P("ln"); + content_get(ridSrc, &attach); + blob_strip_bom(&attach, 0); + z = blob_str(&attach); + if( zLn ){ + output_text_with_line_numbers(z, zLn); + }else{ + @
+      @ %h(z)
+      @ 
+ } + }else if( strncmp(zMime, "image/", 6)==0 ){ + @ }else{ - @ + int sz = db_int(0, "SELECT sz FROM blob WHERE rid=%d", ridSrc); + @ (file is %d(sz) bytes of binary data) } - @ - @ - @ - @ - @
+ @ + manifest_destroy(pAttach); + blob_reset(&attach); style_footer(); +} +/* +** Output HTML to show a list of attachments. +*/ +void attachment_list( + const char *zTarget, /* Object that things are attached to */ + const char *zHeader /* Header to display with attachments */ +){ + int cnt = 0; + Stmt q; + db_prepare(&q, + "SELECT datetime(mtime,'localtime'), filename, user," + " (SELECT uuid FROM blob WHERE rid=attachid), src" + " FROM attachment" + " WHERE isLatest AND src!='' AND target=%Q" + " ORDER BY mtime DESC", + zTarget + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zDate = db_column_text(&q, 0); + const char *zFile = db_column_text(&q, 1); + const char *zUser = db_column_text(&q, 2); + const char *zUuid = db_column_text(&q, 3); + const char *zSrc = db_column_text(&q, 4); + if( cnt==0 ){ + @ %s(zHeader) + } + cnt++; + @
  • + @ %z(href("%R/artifact/%s",zSrc))%h(zFile) + @ added by %h(zUser) on + hyperlink_to_date(zDate, "."); + @ [%z(href("%R/ainfo/%s",zUuid))details] + @
  • + } + if( cnt ){ + @ + } + db_finalize(&q); + } Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -72,10 +72,11 @@ fix_private_blob_dependencies(1); db_multi_exec( "DELETE FROM blob WHERE rid IN private;" "DELETE FROM delta wHERE rid IN private;" "DELETE FROM private;" + "DROP TABLE IF EXISTS modreq;" ); } /* Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -713,84 +713,98 @@ style_footer(); } /* ** WEBPAGE: winfo -** URL: /winfo?name=RID +** URL: /winfo?name=UUID ** ** Return information about a wiki page. */ void winfo_page(void){ - Stmt q; int rid; + Manifest *pWiki; + char *zUuid; + char *zDate; + Blob wiki; + int modPending; + const char *zModAction; login_check_credentials(); if( !g.perm.RdWiki ){ login_needed(); return; } rid = name_to_rid_www("name"); - if( rid==0 ){ + if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI))==0 ){ style_header("Wiki Page Information Error"); - @ No such object: %h(g.argv[2]) + @ No such object: %h(P("name")) style_footer(); return; } - db_prepare(&q, - "SELECT substr(tagname, 6, 1000), uuid," - " datetime(event.mtime, 'localtime'), user" - " FROM tagxref, tag, blob, event" - " WHERE tagxref.rid=%d" - " AND tag.tagid=tagxref.tagid" - " AND tag.tagname LIKE 'wiki-%%'" - " AND blob.rid=%d" - " AND event.objid=%d", - rid, rid, rid - ); - if( db_step(&q)==SQLITE_ROW ){ - const char *zName = db_column_text(&q, 0); - const char *zUuid = db_column_text(&q, 1); - char *zTitle = mprintf("Wiki Page %s", zName); - const char *zDate = db_column_text(&q,2); - const char *zUser = db_column_text(&q,3); - style_header(zTitle); - free(zTitle); - login_anonymous_available(); - @
    Overview
    - @

    - @ - @ "); - if( g.perm.Setup ){ - @ - } - @ "); - if( g.perm.Hyperlink ){ - @ - @ - @ - } - @
    Version:%s(zUuid)
    Date: - hyperlink_to_date(zDate, "
    Record ID:%d(rid)
    Original User: - hyperlink_to_user(zUser, zDate, "
    Commands: - @ %z(href("%R/whistory?name=%t",zName))history - @ | %z(href("%R/artifact/%S",zUuid))raw-text - @

    - }else{ - style_header("Wiki Information"); - rid = 0; - } - db_finalize(&q); - showTags(rid, "wiki-*"); - if( rid ){ - Manifest *pWiki; - pWiki = manifest_get(rid, CFTYPE_WIKI); - if( pWiki ){ - Blob wiki; - blob_init(&wiki, pWiki->zWiki, -1); - @
    Content
    - wiki_convert(&wiki, 0, 0); - blob_reset(&wiki); - } - manifest_destroy(pWiki); - } + if( g.perm.ModWiki && (zModAction = P("modaction"))!=0 ){ + if( strcmp(zModAction,"delete")==0 ){ + moderation_disapprove(rid); + cgi_redirectf("%R/wiki?name=%T", pWiki->zWikiTitle); + /*NOTREACHED*/ + } + if( strcmp(zModAction,"approve")==0 ){ + moderation_approve(rid); + } + } + style_header("Update of \"%h\"", pWiki->zWikiTitle); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate); + style_submenu_element("Raw", "Raw", "artifact/%S", zUuid); + style_submenu_element("History", "History", "whistory?name=%t", + pWiki->zWikiTitle); + style_submenu_element("Page", "Page", "wiki?name=%t", + pWiki->zWikiTitle); + login_anonymous_available(); + @
    Overview
    + @

    + @ + @ + @ + @ "); + @ "); + if( pWiki->nParent>0 ){ + int i; + @ + } + @
    Artifact ID:%z(href("%R/artifact/%s",zUuid))%s(zUuid) + if( g.perm.Setup ){ + @ (%d(rid)) + } + modPending = moderation_pending(rid); + if( modPending ){ + @ *** Awaiting Moderator Approval *** + } + @
    Page Name:%h(pWiki->zWikiTitle)
    Date: + hyperlink_to_date(zDate, "
    Original User: + hyperlink_to_user(pWiki->zUser, zDate, "
    Parent%s(pWiki->nParent==1?"":"s"): + for(i=0; inParent; i++){ + char *zParent = pWiki->azParent[i]; + @ %z(href("info/%S",zParent))%s(zParent) + } + @
    + + if( g.perm.ModWiki && modPending ){ + @

    Moderation
    + @
    + @
    + @
    + @
    + @ + @
    + @
    + } + + + @
    Content
    + blob_init(&wiki, pWiki->zWiki, -1); + wiki_convert(&wiki, 0, 0); + blob_reset(&wiki); + manifest_destroy(pWiki); style_footer(); } /* ** Show a webpage error message @@ -993,10 +1007,25 @@ manifest_destroy(pTo); style_footer(); } +#if INTERFACE +/* +** Possible return values from object_description() +*/ +#define OBJTYPE_CHECKIN 0x0001 +#define OBJTYPE_CONTENT 0x0002 +#define OBJTYPE_WIKI 0x0004 +#define OBJTYPE_TICKET 0x0008 +#define OBJTYPE_ATTACHMENT 0x0010 +#define OBJTYPE_EVENT 0x0020 +#define OBJTYPE_TAG 0x0040 +#define OBJTYPE_SYMLINK 0x0080 +#define OBJTYPE_EXE 0x0100 +#endif + /* ** Write a description of an object to the www reply. ** ** If the object is a file then mention: ** @@ -1008,18 +1037,19 @@ ** ** * It's artifact ID ** * date of check-in ** * Comment & user */ -void object_description( +int object_description( int rid, /* The artifact ID */ int linkToView, /* Add viewer link if true */ Blob *pDownloadName /* Fill with an appropriate download name */ ){ Stmt q; int cnt = 0; int nWiki = 0; + int objType = 0; char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); char *prevName = 0; db_prepare(&q, @@ -1051,15 +1081,18 @@ if( prevName ) { @ } if( mPerm==PERM_LNK ){ @
  • Symbolic link + objType |= OBJTYPE_SYMLINK; }else if( mPerm==PERM_EXE ){ @
  • Executable file + objType |= OBJTYPE_EXE; }else{ @
  • File } + objType |= OBJTYPE_CONTENT; @ %z(href("%R/finfo?name=%T",zName))%h(zName) @