Fossil SCM
timeline.rss with single-ticket support, based on David Given's patch.
Commit
1bc09124bd80b0ee7dba063e165e53e68dc356fb
Parent
fab09a17105957a…
2 files changed
+4
-2
+46
+4
-2
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -1723,11 +1723,11 @@ | ||
| 1723 | 1723 | const char *zUuid; |
| 1724 | 1724 | char zTktName[UUID_SIZE+1]; |
| 1725 | 1725 | Manifest *pTktChng; |
| 1726 | 1726 | int modPending; |
| 1727 | 1727 | const char *zModAction; |
| 1728 | - | |
| 1728 | + char *zTktTitle; | |
| 1729 | 1729 | login_check_credentials(); |
| 1730 | 1730 | if( !g.perm.RdTkt ){ login_needed(); return; } |
| 1731 | 1731 | rid = name_to_rid_www("name"); |
| 1732 | 1732 | if( rid==0 ){ fossil_redirect_home(); } |
| 1733 | 1733 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| @@ -1752,10 +1752,11 @@ | ||
| 1752 | 1752 | } |
| 1753 | 1753 | if( strcmp(zModAction,"approve")==0 ){ |
| 1754 | 1754 | moderation_approve(rid); |
| 1755 | 1755 | } |
| 1756 | 1756 | } |
| 1757 | + zTktTitle = db_text("???", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName); | |
| 1757 | 1758 | style_header("Ticket Change Details"); |
| 1758 | 1759 | style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); |
| 1759 | 1760 | style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); |
| 1760 | 1761 | style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); |
| 1761 | 1762 | style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName); |
| @@ -1776,17 +1777,18 @@ | ||
| 1776 | 1777 | modPending = moderation_pending(rid); |
| 1777 | 1778 | if( modPending ){ |
| 1778 | 1779 | @ <span class="modpending">*** Awaiting Moderator Approval ***</span> |
| 1779 | 1780 | } |
| 1780 | 1781 | @ <tr><th>Ticket:</th> |
| 1781 | - @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a></td></tr> | |
| 1782 | + @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a><br>%h(zTktTitle)</td></tr> | |
| 1782 | 1783 | @ <tr><th>Date:</th><td> |
| 1783 | 1784 | hyperlink_to_date(zDate, "</td></tr>"); |
| 1784 | 1785 | @ <tr><th>User:</th><td> |
| 1785 | 1786 | hyperlink_to_user(pTktChng->zUser, zDate, "</td></tr>"); |
| 1786 | 1787 | @ </table> |
| 1787 | 1788 | free(zDate); |
| 1789 | + free(zTktTitle); | |
| 1788 | 1790 | |
| 1789 | 1791 | if( g.perm.ModTkt && modPending ){ |
| 1790 | 1792 | @ <div class="section">Moderation</div> |
| 1791 | 1793 | @ <blockquote> |
| 1792 | 1794 | @ <form method="POST" action="%R/tinfo/%s(zUuid)"> |
| 1793 | 1795 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1723,11 +1723,11 @@ | |
| 1723 | const char *zUuid; |
| 1724 | char zTktName[UUID_SIZE+1]; |
| 1725 | Manifest *pTktChng; |
| 1726 | int modPending; |
| 1727 | const char *zModAction; |
| 1728 | |
| 1729 | login_check_credentials(); |
| 1730 | if( !g.perm.RdTkt ){ login_needed(); return; } |
| 1731 | rid = name_to_rid_www("name"); |
| 1732 | if( rid==0 ){ fossil_redirect_home(); } |
| 1733 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| @@ -1752,10 +1752,11 @@ | |
| 1752 | } |
| 1753 | if( strcmp(zModAction,"approve")==0 ){ |
| 1754 | moderation_approve(rid); |
| 1755 | } |
| 1756 | } |
| 1757 | style_header("Ticket Change Details"); |
| 1758 | style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); |
| 1759 | style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); |
| 1760 | style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); |
| 1761 | style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName); |
| @@ -1776,17 +1777,18 @@ | |
| 1776 | modPending = moderation_pending(rid); |
| 1777 | if( modPending ){ |
| 1778 | @ <span class="modpending">*** Awaiting Moderator Approval ***</span> |
| 1779 | } |
| 1780 | @ <tr><th>Ticket:</th> |
| 1781 | @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a></td></tr> |
| 1782 | @ <tr><th>Date:</th><td> |
| 1783 | hyperlink_to_date(zDate, "</td></tr>"); |
| 1784 | @ <tr><th>User:</th><td> |
| 1785 | hyperlink_to_user(pTktChng->zUser, zDate, "</td></tr>"); |
| 1786 | @ </table> |
| 1787 | free(zDate); |
| 1788 | |
| 1789 | if( g.perm.ModTkt && modPending ){ |
| 1790 | @ <div class="section">Moderation</div> |
| 1791 | @ <blockquote> |
| 1792 | @ <form method="POST" action="%R/tinfo/%s(zUuid)"> |
| 1793 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1723,11 +1723,11 @@ | |
| 1723 | const char *zUuid; |
| 1724 | char zTktName[UUID_SIZE+1]; |
| 1725 | Manifest *pTktChng; |
| 1726 | int modPending; |
| 1727 | const char *zModAction; |
| 1728 | char *zTktTitle; |
| 1729 | login_check_credentials(); |
| 1730 | if( !g.perm.RdTkt ){ login_needed(); return; } |
| 1731 | rid = name_to_rid_www("name"); |
| 1732 | if( rid==0 ){ fossil_redirect_home(); } |
| 1733 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| @@ -1752,10 +1752,11 @@ | |
| 1752 | } |
| 1753 | if( strcmp(zModAction,"approve")==0 ){ |
| 1754 | moderation_approve(rid); |
| 1755 | } |
| 1756 | } |
| 1757 | zTktTitle = db_text("???", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName); |
| 1758 | style_header("Ticket Change Details"); |
| 1759 | style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); |
| 1760 | style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); |
| 1761 | style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); |
| 1762 | style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName); |
| @@ -1776,17 +1777,18 @@ | |
| 1777 | modPending = moderation_pending(rid); |
| 1778 | if( modPending ){ |
| 1779 | @ <span class="modpending">*** Awaiting Moderator Approval ***</span> |
| 1780 | } |
| 1781 | @ <tr><th>Ticket:</th> |
| 1782 | @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a><br>%h(zTktTitle)</td></tr> |
| 1783 | @ <tr><th>Date:</th><td> |
| 1784 | hyperlink_to_date(zDate, "</td></tr>"); |
| 1785 | @ <tr><th>User:</th><td> |
| 1786 | hyperlink_to_user(pTktChng->zUser, zDate, "</td></tr>"); |
| 1787 | @ </table> |
| 1788 | free(zDate); |
| 1789 | free(zTktTitle); |
| 1790 | |
| 1791 | if( g.perm.ModTkt && modPending ){ |
| 1792 | @ <div class="section">Moderation</div> |
| 1793 | @ <blockquote> |
| 1794 | @ <form method="POST" action="%R/tinfo/%s(zUuid)"> |
| 1795 |
+46
| --- src/rss.c | ||
| +++ src/rss.c | ||
| @@ -19,20 +19,48 @@ | ||
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include <time.h> |
| 22 | 22 | #include "rss.h" |
| 23 | 23 | #include <assert.h> |
| 24 | + | |
| 25 | +static int append_tag_filter(Blob* bSQL, const char* zName, const char* zType) | |
| 26 | +{ | |
| 27 | + if ( zName ){ | |
| 28 | + int nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB '%s-%q*'", | |
| 29 | + zType, zName); | |
| 30 | + if ( nTagId == 0 ){ | |
| 31 | + return 0; | |
| 32 | + } | |
| 33 | + blob_appendf(bSQL, " AND objid IN (SELECT rid FROM tagxref WHERE tagid=%d)", nTagId); | |
| 34 | + } | |
| 35 | + return 1; | |
| 36 | +} | |
| 24 | 37 | |
| 25 | 38 | /* |
| 26 | 39 | ** WEBPAGE: timeline.rss |
| 40 | +** URL: /timeline.rss/y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&name=FILENAME&wiki=NAME | |
| 41 | +** | |
| 42 | +** Produce an RSS feed of the timeline. | |
| 43 | +** | |
| 44 | +** TYPE may be: all, ci (show checkins only), t (show tickets only), | |
| 45 | +** w (show wiki only). LIMIT is the number of items to show. | |
| 46 | +** | |
| 47 | +** tkt=UUID filters for only those events for the specified ticket. tag=TAG | |
| 48 | +** filters for a tag, and name=FILENAME for a file. Some combinations may be | |
| 49 | +** used. | |
| 27 | 50 | */ |
| 51 | + | |
| 28 | 52 | void page_timeline_rss(void){ |
| 29 | 53 | Stmt q; |
| 30 | 54 | int nLine=0; |
| 31 | 55 | char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; |
| 32 | 56 | Blob bSQL; |
| 33 | 57 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 58 | + const char *zTicketUuid = PD("tkt",NULL); | |
| 59 | + const char *zTag = PD("tag",NULL); | |
| 60 | + const char *zFilename = PD("name",NULL); | |
| 61 | + const char *zWiki = PD("wiki",NULL); | |
| 34 | 62 | int nLimit = atoi(PD("n","20")); |
| 35 | 63 | const char zSQL1[] = |
| 36 | 64 | @ SELECT |
| 37 | 65 | @ blob.rid, |
| 38 | 66 | @ uuid, |
| @@ -62,10 +90,11 @@ | ||
| 62 | 90 | if( !g.perm.Read ){ |
| 63 | 91 | if( g.perm.RdTkt && g.perm.RdWiki ){ |
| 64 | 92 | blob_append(&bSQL, " AND event.type!='ci'", -1); |
| 65 | 93 | }else if( g.perm.RdTkt ){ |
| 66 | 94 | blob_append(&bSQL, " AND event.type=='t'", -1); |
| 95 | + | |
| 67 | 96 | }else{ |
| 68 | 97 | blob_append(&bSQL, " AND event.type=='w'", -1); |
| 69 | 98 | } |
| 70 | 99 | }else if( !g.perm.RdWiki ){ |
| 71 | 100 | if( g.perm.RdTkt ){ |
| @@ -76,10 +105,27 @@ | ||
| 76 | 105 | }else if( !g.perm.RdTkt ){ |
| 77 | 106 | assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki ); |
| 78 | 107 | blob_append(&bSQL, " AND event.type!='t'", -1); |
| 79 | 108 | } |
| 80 | 109 | } |
| 110 | + | |
| 111 | + if( !append_tag_filter(&bSQL, zTicketUuid, "tkt") ){ | |
| 112 | + return; | |
| 113 | + } | |
| 114 | + if( !append_tag_filter(&bSQL, zTag, "sym") ){ | |
| 115 | + return; | |
| 116 | + } | |
| 117 | + if( !append_tag_filter(&bSQL, zWiki, "wiki") ){ | |
| 118 | + return; | |
| 119 | + } | |
| 120 | + | |
| 121 | + if ( zFilename ){ | |
| 122 | + blob_appendf(&bSQL, | |
| 123 | + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", | |
| 124 | + zFilename, filename_collation() | |
| 125 | + ); | |
| 126 | + } | |
| 81 | 127 | |
| 82 | 128 | blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); |
| 83 | 129 | |
| 84 | 130 | cgi_set_content_type("application/rss+xml"); |
| 85 | 131 | |
| 86 | 132 |
| --- src/rss.c | |
| +++ src/rss.c | |
| @@ -19,20 +19,48 @@ | |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include <time.h> |
| 22 | #include "rss.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | /* |
| 26 | ** WEBPAGE: timeline.rss |
| 27 | */ |
| 28 | void page_timeline_rss(void){ |
| 29 | Stmt q; |
| 30 | int nLine=0; |
| 31 | char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; |
| 32 | Blob bSQL; |
| 33 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 34 | int nLimit = atoi(PD("n","20")); |
| 35 | const char zSQL1[] = |
| 36 | @ SELECT |
| 37 | @ blob.rid, |
| 38 | @ uuid, |
| @@ -62,10 +90,11 @@ | |
| 62 | if( !g.perm.Read ){ |
| 63 | if( g.perm.RdTkt && g.perm.RdWiki ){ |
| 64 | blob_append(&bSQL, " AND event.type!='ci'", -1); |
| 65 | }else if( g.perm.RdTkt ){ |
| 66 | blob_append(&bSQL, " AND event.type=='t'", -1); |
| 67 | }else{ |
| 68 | blob_append(&bSQL, " AND event.type=='w'", -1); |
| 69 | } |
| 70 | }else if( !g.perm.RdWiki ){ |
| 71 | if( g.perm.RdTkt ){ |
| @@ -76,10 +105,27 @@ | |
| 76 | }else if( !g.perm.RdTkt ){ |
| 77 | assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki ); |
| 78 | blob_append(&bSQL, " AND event.type!='t'", -1); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); |
| 83 | |
| 84 | cgi_set_content_type("application/rss+xml"); |
| 85 | |
| 86 |
| --- src/rss.c | |
| +++ src/rss.c | |
| @@ -19,20 +19,48 @@ | |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include <time.h> |
| 22 | #include "rss.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | static int append_tag_filter(Blob* bSQL, const char* zName, const char* zType) |
| 26 | { |
| 27 | if ( zName ){ |
| 28 | int nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB '%s-%q*'", |
| 29 | zType, zName); |
| 30 | if ( nTagId == 0 ){ |
| 31 | return 0; |
| 32 | } |
| 33 | blob_appendf(bSQL, " AND objid IN (SELECT rid FROM tagxref WHERE tagid=%d)", nTagId); |
| 34 | } |
| 35 | return 1; |
| 36 | } |
| 37 | |
| 38 | /* |
| 39 | ** WEBPAGE: timeline.rss |
| 40 | ** URL: /timeline.rss/y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&name=FILENAME&wiki=NAME |
| 41 | ** |
| 42 | ** Produce an RSS feed of the timeline. |
| 43 | ** |
| 44 | ** TYPE may be: all, ci (show checkins only), t (show tickets only), |
| 45 | ** w (show wiki only). LIMIT is the number of items to show. |
| 46 | ** |
| 47 | ** tkt=UUID filters for only those events for the specified ticket. tag=TAG |
| 48 | ** filters for a tag, and name=FILENAME for a file. Some combinations may be |
| 49 | ** used. |
| 50 | */ |
| 51 | |
| 52 | void page_timeline_rss(void){ |
| 53 | Stmt q; |
| 54 | int nLine=0; |
| 55 | char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; |
| 56 | Blob bSQL; |
| 57 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 58 | const char *zTicketUuid = PD("tkt",NULL); |
| 59 | const char *zTag = PD("tag",NULL); |
| 60 | const char *zFilename = PD("name",NULL); |
| 61 | const char *zWiki = PD("wiki",NULL); |
| 62 | int nLimit = atoi(PD("n","20")); |
| 63 | const char zSQL1[] = |
| 64 | @ SELECT |
| 65 | @ blob.rid, |
| 66 | @ uuid, |
| @@ -62,10 +90,11 @@ | |
| 90 | if( !g.perm.Read ){ |
| 91 | if( g.perm.RdTkt && g.perm.RdWiki ){ |
| 92 | blob_append(&bSQL, " AND event.type!='ci'", -1); |
| 93 | }else if( g.perm.RdTkt ){ |
| 94 | blob_append(&bSQL, " AND event.type=='t'", -1); |
| 95 | |
| 96 | }else{ |
| 97 | blob_append(&bSQL, " AND event.type=='w'", -1); |
| 98 | } |
| 99 | }else if( !g.perm.RdWiki ){ |
| 100 | if( g.perm.RdTkt ){ |
| @@ -76,10 +105,27 @@ | |
| 105 | }else if( !g.perm.RdTkt ){ |
| 106 | assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki ); |
| 107 | blob_append(&bSQL, " AND event.type!='t'", -1); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | if( !append_tag_filter(&bSQL, zTicketUuid, "tkt") ){ |
| 112 | return; |
| 113 | } |
| 114 | if( !append_tag_filter(&bSQL, zTag, "sym") ){ |
| 115 | return; |
| 116 | } |
| 117 | if( !append_tag_filter(&bSQL, zWiki, "wiki") ){ |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | if ( zFilename ){ |
| 122 | blob_appendf(&bSQL, |
| 123 | " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", |
| 124 | zFilename, filename_collation() |
| 125 | ); |
| 126 | } |
| 127 | |
| 128 | blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); |
| 129 | |
| 130 | cgi_set_content_type("application/rss+xml"); |
| 131 | |
| 132 |