Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -1872,10 +1872,31 @@ return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName); } void db_lset_int(const char *zName, int value){ db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%d)", zName, value); } + +/* +** Returns non-0 if the database (which must be open) table identified +** by zTableName has a column named zColName (case-sensitive), else +** returns 0. +*/ +int db_table_has_column( char const *zTableName, char const *zColName ){ + Stmt q = empty_Stmt; + int rc = 0; + db_prepare( &q, "PRAGMA table_info(%Q)", zTableName ); + while(SQLITE_ROW == db_step(&q)){ + /* Columns: (cid, name, type, notnull, dflt_value, pk) */ + char const * zCol = db_column_text(&q, 1); + if(0==fossil_strcmp(zColName, zCol)){ + rc = 1; + break; + } + } + db_finalize(&q); + return rc; +} /* ** Record the name of a local repository in the global_config() database. ** The repository filename %s is recorded as an entry with a "name" field ** of the following form: Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -1723,11 +1723,11 @@ const char *zUuid; char zTktName[UUID_SIZE+1]; Manifest *pTktChng; int modPending; const char *zModAction; - + char *zTktTitle; login_check_credentials(); if( !g.perm.RdTkt ){ 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); @@ -1752,10 +1752,13 @@ } if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } + zTktTitle = db_table_has_column( "ticket", "title" ) + ? db_text("(No title)", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName) + : 0; style_header("Ticket Change Details"); style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName); @@ -1776,17 +1779,22 @@ modPending = moderation_pending(rid); if( modPending ){ @ *** Awaiting Moderator Approval *** } @ Ticket: - @ %z(href("%R/tktview/%s",zTktName))%s(zTktName) + @ %z(href("%R/tktview/%s",zTktName))%s(zTktName) + if(zTktTitle){ + @
%h(zTktTitle) + } + @ @ Date: hyperlink_to_date(zDate, ""); @ User: hyperlink_to_user(pTktChng->zUser, zDate, ""); @ free(zDate); + free(zTktTitle); if( g.perm.ModTkt && modPending ){ @
Moderation
@
@
Index: src/rss.c ================================================================== --- src/rss.c +++ src/rss.c @@ -22,18 +22,37 @@ #include "rss.h" #include /* ** WEBPAGE: timeline.rss +** URL: /timeline.rss/y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME +** +** Produce an RSS feed of the timeline. +** +** TYPE may be: all, ci (show checkins only), t (show tickets only), +** w (show wiki only). LIMIT is the number of items to show. +** +** tkt=UUID filters for only those events for the specified ticket. tag=TAG +** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. +** +** In addition, name=FILENAME filters for a specific file. This may be +** combined with one of the other filters (useful for looking at a specific +** branch). */ + void page_timeline_rss(void){ Stmt q; int nLine=0; char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; Blob bSQL; const char *zType = PD("y","all"); /* Type of events. All if NULL */ + const char *zTicketUuid = PD("tkt",NULL); + const char *zTag = PD("tag",NULL); + const char *zFilename = PD("name",NULL); + const char *zWiki = PD("wiki",NULL); int nLimit = atoi(PD("n","20")); + int nTagId; const char zSQL1[] = @ SELECT @ blob.rid, @ uuid, @ event.mtime, @@ -62,10 +81,11 @@ if( !g.perm.Read ){ if( g.perm.RdTkt && g.perm.RdWiki ){ blob_append(&bSQL, " AND event.type!='ci'", -1); }else if( g.perm.RdTkt ){ blob_append(&bSQL, " AND event.type=='t'", -1); + }else{ blob_append(&bSQL, " AND event.type=='w'", -1); } }else if( !g.perm.RdWiki ){ if( g.perm.RdTkt ){ @@ -76,10 +96,46 @@ }else if( !g.perm.RdTkt ){ assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki ); blob_append(&bSQL, " AND event.type!='t'", -1); } } + + if( zTicketUuid ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", + zTicketUuid); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zTag ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'", + zTag); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zWiki ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'", + zWiki); + if ( nTagId==0 ){ + nTagId = -1; + } + }else{ + nTagId = 0; + } + + if( nTagId==-1 ){ + blob_appendf(&bSQL, " AND 0"); + }else if( nTagId!=0 ){ + blob_appendf(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); + } + + if( zFilename ){ + blob_appendf(&bSQL, + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", + zFilename, filename_collation() + ); + } blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); cgi_set_content_type("application/rss+xml");