Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| SHA1 Hash: | f4a25366a76d89dce3dc191246bbea3babb53505 |
|---|---|
| Date: | 2010-03-18 13:26:36 |
| User: | drh |
| Comment: | Merge recent experimental changes (the attachment enhancement and the ability to delete wiki) into the trunk. |
Tags And Properties
- branch=trunk inherited from [a28c83647d] branch timeline
- sym-trunk inherited from [a28c83647d]
Changes
[hide diffs]Added src/attach.c
Changes to src/info.c
@@ -732,10 +732,52 @@
}
cnt++;
}
db_finalize(&q);
}
+ db_prepare(&q,
+ "SELECT target, filename, datetime(mtime), user, src"
+ " FROM attachment"
+ " WHERE src=(SELECT uuid FROM blob WHERE rid=%d)"
+ " ORDER BY mtime DESC",
+ rid
+ );
+ while( db_step(&q)==SQLITE_ROW ){
+ const char *zTarget = db_column_text(&q, 0);
+ const char *zFilename = db_column_text(&q, 1);
+ const char *zDate = db_column_text(&q, 2);
+ const char *zUser = db_column_text(&q, 3);
+ const char *zSrc = db_column_text(&q, 4);
+ if( cnt>0 ){
+ @ Also attachment "%h(zFilename)" to
+ }else{
+ @ Attachment "%h(zFilename)" to
+ }
+ if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
+ char zShort[20];
+ memcpy(zShort, zTarget, 10);
+ if( g.okHistory && g.okRdTkt ){
+ @ ticket [<a href="%s(g.zTop)/tktview?name=%s(zShort)">%s(zShort)</a>]
+ }else{
+ @ ticket [%s(zShort)]
+ }
+ }else{
+ if( g.okHistory && g.okRdWiki ){
+ @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>]
+ }else{
+ @ wiki page [%h(zTarget)]
+ }
+ }
+ @ added by
+ hyperlink_to_user(zUser,zDate," on");
+ hyperlink_to_date(zDate,".");
+ cnt++;
+ if( pDownloadName && blob_size(pDownloadName)==0 ){
+ blob_append(pDownloadName, zSrc, -1);
+ }
+ }
+ db_finalize(&q);
if( cnt==0 ){
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
@ Control artifact.
if( pDownloadName && blob_size(pDownloadName)==0 ){
blob_append(pDownloadName, zUuid, -1);
Changes to src/login.c
@@ -476,11 +476,12 @@
case 's': g.okSetup = 1; /* Fall thru into Admin */
case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip =
g.okRdWiki = g.okWrWiki = g.okNewWiki =
g.okApndWiki = g.okHistory = g.okClone =
g.okNewTkt = g.okPassword = g.okRdAddr =
- g.okTktFmt = 1; /* Fall thru into Read/Write */
+ g.okTktFmt = g.okAttach = 1;
+ /* Fall thru into Read/Write */
case 'i': g.okRead = g.okWrite = 1; break;
case 'o': g.okRead = 1; break;
case 'z': g.okZip = 1; break;
case 'd': g.okDelete = 1; break;
@@ -498,10 +499,11 @@
case 'n': g.okNewTkt = 1; break;
case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt =
g.okApndTkt = 1; break;
case 'c': g.okApndTkt = 1; break;
case 't': g.okTktFmt = 1; break;
+ case 'b': g.okAttach = 1; break;
/* The "u" privileges is a little different. It recursively
** inherits all privileges of the user named "reader" */
case 'u': {
if( zUser==0 ){
@@ -534,11 +536,11 @@
int rc = 1;
if( nCap<0 ) nCap = strlen(zCap);
for(i=0; i<nCap && rc && zCap[i]; i++){
switch( zCap[i] ){
case 'a': rc = g.okAdmin; break;
- /* case 'b': */
+ case 'b': rc = g.okAttach; break;
case 'c': rc = g.okApndTkt; break;
case 'd': rc = g.okDelete; break;
case 'e': rc = g.okRdAddr; break;
case 'f': rc = g.okNewWiki; break;
case 'g': rc = g.okClone; break;
Changes to src/main.c
@@ -130,10 +130,11 @@ int okWrWiki; /* k: edit wiki via web */ int okRdTkt; /* r: view tickets via web */ int okNewTkt; /* n: create new tickets */ int okApndTkt; /* c: append to tickets via the web */ int okWrTkt; /* w: make changes to tickets via web */ + int okAttach; /* b: add attachments */ int okTktFmt; /* t: create new ticket report formats */ int okRdAddr; /* e: read email addresses or other private data */ int okZip; /* z: download zipped artifact via /zip URL */ /* For defense against Cross-site Request Forgery attacks */
Changes to src/main.mk
@@ -13,10 +13,11 @@ SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/allrepo.c \ + $(SRCDIR)/attach.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/captcha.c \ @@ -86,10 +87,11 @@ $(SRCDIR)/zip.c TRANS_SRC = \ add_.c \ allrepo_.c \ + attach_.c \ bag_.c \ blob_.c \ branch_.c \ browse_.c \ captcha_.c \ @@ -159,10 +161,11 @@ zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ + $(OBJDIR)/attach.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/captcha.o \ @@ -273,16 +276,16 @@ # noop clean: rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h rm -f translate makeheaders mkindex page_index.h headers - rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h + rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h page_index.h: $(TRANS_SRC) mkindex ./mkindex $(TRANS_SRC) >$@ headers: page_index.h makeheaders VERSION.h - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h + ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h touch headers headers: Makefile Makefile: add_.c: $(SRCDIR)/add.c translate ./translate $(SRCDIR)/add.c >add_.c @@ -296,10 +299,17 @@ $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c allrepo.h: headers +attach_.c: $(SRCDIR)/attach.c translate + ./translate $(SRCDIR)/attach.c >attach_.c + +$(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c + +attach.h: headers bag_.c: $(SRCDIR)/bag.c translate ./translate $(SRCDIR)/bag.c >bag_.c $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c
Changes to src/makemake.tcl
@@ -7,10 +7,11 @@
# "translate" and "makeheaders"
#
set src {
add
allrepo
+ attach
bag
blob
branch
browse
captcha
Changes to src/manifest.c
@@ -196,11 +196,11 @@
}
if( blob_size(&a3)>0
&& (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){
goto manifest_syntax_error;
}
- p->zAttachName = zName;
+ p->zAttachName = (char*)file_tail(zName);
p->zAttachSrc = zSrc;
p->zAttachTarget = zTarget;
break;
}
@@ -1080,11 +1080,16 @@
if( m.type==CFTYPE_WIKI ){
char *zTag = mprintf("wiki-%s", m.zWikiTitle);
int tagid = tag_findid(zTag, 1);
int prior;
char *zComment;
- tag_insert(zTag, 1, 0, rid, m.rDate, rid);
+ int nWiki;
+ char zLength[40];
+ while( isspace(m.zWiki[0]) ) m.zWiki++;
+ nWiki = strlen(m.zWiki);
+ sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
+ tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
free(zTag);
prior = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=%d AND mtime<%.17g"
" ORDER BY mtime DESC",
@@ -1091,11 +1096,15 @@
tagid, m.rDate
);
if( prior ){
content_deltify(prior, rid, 0);
}
- zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
+ if( nWiki>0 ){
+ zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
+ }else{
+ zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle);
+ }
db_multi_exec(
"REPLACE INTO event(type,mtime,objid,user,comment,"
" bgcolor,euser,ecomment)"
"VALUES('w',%.17g,%d,%Q,%Q,"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
@@ -1119,20 +1128,57 @@
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
m.zTicketUuid);
}
if( m.type==CFTYPE_ATTACHMENT ){
db_multi_exec(
- "INSERT OR IGNORE INTO attachment(mtime, target, filename)"
- "VALUES(0.0,%Q,%Q)",
- m.zAttachTarget, m.zAttachName
+ "INSERT INTO attachment(attachid, mtime, src, target,"
+ "filename, comment, user)"
+ "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
+ rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName,
+ (m.zComment ? m.zComment : ""), m.zUser
);
db_multi_exec(
- "UPDATE attachment SET mtime=%.17g, src=%Q, comment=%Q, user=%Q"
- " WHERE mtime<%.17g AND target=%Q AND filename=%Q",
- m.rDate, m.zAttachSrc, m.zComment, m.zUser,
- m.rDate, m.zAttachTarget, m.zAttachName
+ "UPDATE attachment SET isLatest = (mtime=="
+ "(SELECT max(mtime) FROM attachment"
+ " WHERE target=%Q AND filename=%Q))"
+ " WHERE target=%Q AND filename=%Q",
+ m.zAttachTarget, m.zAttachName,
+ m.zAttachTarget, m.zAttachName
);
+ if( strlen(m.zAttachTarget)!=UUID_SIZE
+ || !validate16(m.zAttachTarget, UUID_SIZE)
+ ){
+ char *zComment;
+ if( m.zAttachSrc && m.zAttachSrc[0] ){
+ zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
+ m.zAttachName, m.zAttachTarget);
+ }else{
+ zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
+ m.zAttachName, m.zAttachTarget);
+ }
+ db_multi_exec(
+ "REPLACE INTO event(type,mtime,objid,user,comment)"
+ "VALUES('w',%.17g,%d,%Q,%Q)",
+ m.rDate, rid, m.zUser, zComment
+ );
+ free(zComment);
+ }else{
+ char *zComment;
+ if( m.zAttachSrc && m.zAttachSrc[0] ){
+ zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
+ m.zAttachName, m.zAttachTarget);
+ }else{
+ zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
+ m.zAttachName, m.zAttachTarget);
+ }
+ db_multi_exec(
+ "REPLACE INTO event(type,mtime,objid,user,comment)"
+ "VALUES('t',%.17g,%d,%Q,%Q)",
+ m.rDate, rid, m.zUser, zComment
+ );
+ free(zComment);
+ }
}
db_end_transaction(0);
manifest_clear(&m);
return 1;
}
Changes to src/schema.c
@@ -327,18 +327,21 @@ @ @ -- Each attachment is an entry in the following table. Only @ -- the most recent attachment (identified by the D card) is saved. @ -- @ CREATE TABLE attachment( +@ attachid INTEGER PRIMARY KEY, -- Local id for this attachment +@ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use @ mtime TIMESTAMP, -- Time when attachment last changed @ src TEXT, -- UUID of the attachment. NULL to delete -@ target TEXT, -- Object attached to +@ target TEXT, -- Object attached to. Wikiname or Tkt UUID @ filename TEXT, -- Filename for the attachment @ comment TEXT, -- Comment associated with this attachment -@ user TEXT, -- Name of user adding attachment -@ PRIMARY KEY(target, filename) +@ user TEXT -- Name of user adding attachment @ ); +@ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); +@ CREATE INDEX attachment_idx2 ON attachment(src); @ @ -- Template for the TICKET table @ -- @ -- NB: when changing the schema of the TICKET table here, also make the @ -- same change in tktsetup.c.
Changes to src/setup.c
@@ -147,10 +147,12 @@
@ <ol>
@ <li><p>The permission flags are as follows:</p>
@ <table>
@ <tr><td valign="top"><b>a</b></td>
@ <td><i>Admin:</i> Create and delete users</td></tr>
+ @ <tr><td valign="top"><b>b</b></td>
+ @ <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr>
@ <tr><td valign="top"><b>c</b></td>
@ <td><i>Append-Tkt:</i> Append to tickets</td></tr>
@ <tr><td valign="top"><b>d</b></td>
@ <td><i>Delete:</i> Delete wiki and tickets</td></tr>
@ <tr><td valign="top"><b>e</b></td>
@@ -239,11 +241,11 @@
*/
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap, *zPw;
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
- char *oat, *oau, *oav, *oaz;
+ char *oat, *oau, *oav, *oab, *oaz;
const char *inherit[128];
int doWrite;
int uid;
int higherUser = 0; /* True if user being edited is SETUP and the */
/* user doing the editing is ADMIN. Disallow editing */
@@ -276,10 +278,11 @@
doWrite = cgi_all("login","info","pw") && !higherUser;
if( doWrite ){
char zCap[50];
int i = 0;
int aa = P("aa")!=0;
+ int ab = P("ab")!=0;
int ad = P("ad")!=0;
int ae = P("ae")!=0;
int ai = P("ai")!=0;
int aj = P("aj")!=0;
int ak = P("ak")!=0;
@@ -297,10 +300,11 @@
int at = P("at")!=0;
int au = P("au")!=0;
int av = P("av")!=0;
int az = P("az")!=0;
if( aa ){ zCap[i++] = 'a'; }
+ if( ab ){ zCap[i++] = 'b'; }
if( ac ){ zCap[i++] = 'c'; }
if( ad ){ zCap[i++] = 'd'; }
if( ae ){ zCap[i++] = 'e'; }
if( af ){ zCap[i++] = 'f'; }
if( ah ){ zCap[i++] = 'h'; }
@@ -353,18 +357,19 @@
*/
zLogin = "";
zInfo = "";
zCap = "";
zPw = "";
- oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
+ oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
if( uid ){
zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
if( strchr(zCap, 'a') ) oaa = " checked";
+ if( strchr(zCap, 'b') ) oab = " checked";
if( strchr(zCap, 'c') ) oac = " checked";
if( strchr(zCap, 'd') ) oad = " checked";
if( strchr(zCap, 'e') ) oae = " checked";
if( strchr(zCap, 'f') ) oaf = " checked";
if( strchr(zCap, 'g') ) oag = " checked";
@@ -467,15 +472,16 @@
@ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br>
@ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br>
@ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br>
@ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br>
@ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br>
- @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br>
- @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br>
- @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br>
- @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br>
- @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br>
+ @ <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br>
+ @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br>
+ @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br>
+ @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br>
+ @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br>
+ @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br>
@ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip
@ </td>
@ </tr>
@ <tr>
@ <td align="right">Password:</td>
@@ -564,13 +570,13 @@
@ </li><p>
@
@ <li><p>
@ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and
@ <b>Write Wiki</b> privileges control access to wiki pages. The
- @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and
- @ <b>Write Tkt</b> privileges control access to trouble tickets.
- @ The <b>Tkt Report</b> privilege allows the user to create or edit
+ @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and
+ @ <b>Write Ticket</b> privileges control access to trouble tickets.
+ @ The <b>Ticket Report</b> privilege allows the user to create or edit
@ ticket report formats.
@ </p></li>
@
@ <li><p>
@ Users with the <b>Password</b> privilege are allowed to change their
@@ -582,10 +588,15 @@
@ The <b>EMail</b> privilege allows the display of sensitive information
@ such as the email address of users and contact information on tickets.
@ Recommended OFF for "anonymous" and for "nobody" but ON for
@ "developer".
@ </p></li>
+ @
+ @ <li><p>
+ @ The <b>Attachment</b> privilege is needed in order to add attachments
+ @ to tickets or wiki. Write privilege on the ticket or wiki is also
+ @ required.</p></li>
@
@ <li><p>
@ Login is prohibited if the password is an empty string.
@ </p></li>
@ </ul>
Changes to src/tkt.c
@@ -296,18 +296,20 @@
**
** View a ticket.
*/
void tktview_page(void){
const char *zScript;
+ char *zFullName;
+ const char *zUuid = PD("name","");
+
login_check_credentials();
if( !g.okRdTkt ){ login_needed(); return; }
if( g.okWrTkt || g.okApndTkt ){
style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
g.zTop, PD("name",""));
}
if( g.okHistory ){
- const char *zUuid = PD("name","");
style_submenu_element("History", "History Of This Ticket",
"%s/tkthistory/%T", g.zTop, zUuid);
style_submenu_element("Timeline", "Timeline Of This Ticket",
"%s/tkttimeline/%T", g.zTop, zUuid);
style_submenu_element("Check-ins", "Check-ins Of This Ticket",
@@ -315,18 +317,58 @@
}
if( g.okNewTkt ){
style_submenu_element("New Ticket", "Create a new ticket",
"%s/tktnew", g.zTop);
}
+ if( g.okApndTkt && g.okAttach ){
+ style_submenu_element("Attach", "Add An Attachment",
+ "%s/attachadd?tkt=%T&from=%s/tktview%%3fname=%t",
+ g.zTop, zUuid, g.zTop, zUuid);
+ }
style_header("View Ticket");
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
ticket_init();
initializeVariablesFromDb();
zScript = ticket_viewpage_code();
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
Th_Render(zScript);
if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
+
+ zFullName = db_text(0,
+ "SELECT tkt_uuid FROM ticket"
+ " WHERE tkt_uuid GLOB '%q*'", zUuid);
+ if( zFullName ){
+ int cnt = 0;
+ Stmt q;
+ db_prepare(&q,
+ "SELECT datetime(mtime,'localtime'), filename, user"
+ " FROM attachment"
+ " WHERE isLatest AND src!='' AND target=%Q"
+ " ORDER BY mtime DESC",
+ zFullName);
+ 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);
+ if( cnt==0 ){
+ @ <hr><h2>Attachments:</h2>
+ @ <ul>
+ }
+ cnt++;
+ @ <li><a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&file=%t(zFile)">
+ @ %h(zFile)</a> add by %h(zUser) on
+ hyperlink_to_date(zDate, ".");
+ if( g.okWrTkt && g.okAttach ){
+ @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&file=%t(zFile)&from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>]
+ }
+ }
+ if( cnt ){
+ @ </ul>
+ }
+ db_finalize(&q);
+ }
+
style_footer();
}
/*
** TH command: append_field FIELD STRING
@@ -641,15 +683,18 @@
timeline_query_for_www(), zFullUuid, zFullUuid
);
}else{
zSQL = mprintf(
"%s AND event.objid IN "
- " (SELECT rid FROM tagxref WHERE tagid=%d UNION"
- " SELECT srcid FROM backlink WHERE target GLOB '%.4s*' "
- "AND '%s' GLOB (target||'*')) "
+ " (SELECT rid FROM tagxref WHERE tagid=%d"
+ " UNION SELECT srcid FROM backlink"
+ " WHERE target GLOB '%.4s*'"
+ " AND '%s' GLOB (target||'*')"
+ " UNION SELECT attachid FROM attachment"
+ " WHERE target=%Q) "
"ORDER BY mtime DESC",
- timeline_query_for_www(), tagid, zFullUuid, zFullUuid
+ timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
);
}
db_prepare(&q, zSQL);
free(zSQL);
www_print_timeline(&q, TIMELINE_ARTID, 0);
@@ -687,37 +732,60 @@
@ No such ticket: %h(zUuid)
style_footer();
return;
}
db_prepare(&q,
- "SELECT objid, uuid FROM event, blob"
+ "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
+ " FROM event, blob"
" WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
" AND blob.rid=event.objid"
- " ORDER BY mtime DESC",
- tagid
+ " UNION "
+ "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
+ " FROM attachment, blob"
+ " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
+ " AND blob.rid=attachid"
+ " ORDER BY 1 DESC",
+ tagid, tagid
);
while( db_step(&q)==SQLITE_ROW ){
Blob content;
Manifest m;
- int rid = db_column_int(&q, 0);
- const char *zChngUuid = db_column_text(&q, 1);
- content_get(rid, &content);
- if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){
- char *zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate);
- char zUuid[12];
- memcpy(zUuid, zChngUuid, 10);
- zUuid[10] = 0;
- @
- @ Ticket change
- @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zUuid)</a>]</a>
+ char zShort[12];
+ const char *zDate = db_column_text(&q, 0);
+ int rid = db_column_int(&q, 1);
+ const char *zChngUuid = db_column_text(&q, 2);
+ const char *zFile = db_column_text(&q, 4);
+ memcpy(zShort, zChngUuid, 10);
+ zShort[10] = 0;
+ if( zFile!=0 ){
+ const char *zSrc = db_column_text(&q, 3);
+ const char *zUser = db_column_text(&q, 5);
+ if( zSrc==0 || zSrc[0]==0 ){
+ @
+ @ <p>Delete attachment "%h(zFile)"
+ }else{
+ @
+ @ <p>Add attachment "%h(zFile)"
+ }
+ @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
@ (rid %d(rid)) by
- hyperlink_to_user(m.zUser,zDate," on");
- hyperlink_to_date(zDate, ":");
- free(zDate);
- ticket_output_change_artifact(&m);
+ hyperlink_to_user(zUser,zDate," on");
+ hyperlink_to_date(zDate, ".</p>");
+ }else{
+ content_get(rid, &content);
+ if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){
+ @
+ @ <p>Ticket change
+ @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
+ @ (rid %d(rid)) by
+ hyperlink_to_user(m.zUser,zDate," on");
+ hyperlink_to_date(zDate, ":");
+ ticket_output_change_artifact(&m);
+ @ </p>
+ }
+ manifest_clear(&m);
}
- manifest_clear(&m);
}
db_finalize(&q);
style_footer();
}
Changes to src/wiki.c
@@ -127,10 +127,12 @@
int isSandbox;
Blob wiki;
Manifest m;
const char *zPageName;
char *zBody = mprintf("%s","<i>Empty Page</i>");
+ Stmt q;
+ int cnt = 0;
login_check_credentials();
if( !g.okRdWiki ){ login_needed(); return; }
zPageName = P("name");
if( zPageName==0 ){
@@ -152,11 +154,12 @@
@ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
}
@ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
@ available on this server.</li>
@ <li> <form method="GET" action="%s(g.zBaseURL)/wfind">
- @ Search wiki titles: <input type="text" name="title"/> <input type="submit" />
+ @ Search wiki titles: <input type="text" name="title"/>
+ @ <input type="submit" />
@ </li>
@ </ul>
style_footer();
return;
}
@@ -186,10 +189,15 @@
if( !g.isHome ){
if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
g.zTop, zPageName);
}
+ if( rid && g.okWrWiki && g.okAttach ){
+ style_submenu_element("Attach", "Add An Attachment",
+ "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
+ g.zTop, zPageName, g.zTop, zPageName);
+ }
if( rid && g.okApndWiki ){
style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
g.zTop, zPageName);
}
if( g.okHistory ){
@@ -199,10 +207,38 @@
}
style_header(zPageName);
blob_init(&wiki, zBody, -1);
wiki_convert(&wiki, 0, 0);
blob_reset(&wiki);
+
+ db_prepare(&q,
+ "SELECT datetime(mtime,'localtime'), filename, user"
+ " FROM attachment"
+ " WHERE isLatest AND src!='' AND target=%Q"
+ " ORDER BY mtime DESC",
+ zPageName);
+ 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);
+ if( cnt==0 ){
+ @ <hr><h2>Attachments:</h2>
+ @ <ul>
+ }
+ cnt++;
+ @ <li><a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)">
+ @ %h(zFile)</a> add by %h(zUser) on
+ hyperlink_to_date(zDate, ".");
+ if( g.okWrWiki && g.okAttach ){
+ @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>]
+ }
+ }
+ if( cnt ){
+ @ </ul>
+ }
+ db_finalize(&q);
+
if( !isSandbox ){
manifest_clear(&m);
}
style_footer();
}
@@ -515,11 +551,13 @@
/*
** Function called to output extra text at the end of each line in
** a wiki history listing.
*/
static void wiki_history_extra(int rid){
- @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a>
+ if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){
+ @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a>
+ }
}
/*
** WEBPAGE: whistory
** URL: /whistory?name=PAGENAME
@@ -538,13 +576,15 @@
style_header(zTitle);
free(zTitle);
zSQL = mprintf("%s AND event.objid IN "
" (SELECT rid FROM tagxref WHERE tagid="
- "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))"
+ "(SELECT tagid FROM tag WHERE tagname='wiki-%q')"
+ " UNION SELECT attachid FROM attachment"
+ " WHERE target=%Q)"
"ORDER BY mtime DESC",
- timeline_query_for_www(), zPageName);
+ timeline_query_for_www(), zPageName, zPageName);
db_prepare(&q, zSQL);
free(zSQL);
zWikiPageName = zPageName;
www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra);
db_finalize(&q);
@@ -605,25 +645,42 @@
}
/*
** WEBPAGE: wcontent
**
+** all=1 Show deleted pages
+**
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
Stmt q;
+ int showAll = P("all")!=0;
+
login_check_credentials();
if( !g.okRdWiki ){ login_needed(); return; }
style_header("Available Wiki Pages");
+ if( showAll ){
+ style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
+ }else{
+ style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
+ }
@ <ul>
db_prepare(&q,
- "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
+ "SELECT"
+ " substr(tagname, 6),"
+ " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)"
+ " FROM tag WHERE tagname GLOB 'wiki-*'"
" ORDER BY lower(tagname)"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
- @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
+ int size = db_column_int(&q, 1);
+ if( size>0 ){
+ @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li>
+ }else if( showAll ){
+ @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li>
+ }
}
db_finalize(&q);
@ </ul>
style_footer();
}