| | @@ -19,10 +19,195 @@ |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include <time.h> |
| 22 | 22 | #include "rss.h" |
| 23 | 23 | #include <assert.h> |
| 24 | + |
| 25 | +void forum_render_to_html(struct Blob*, const char*, const char*); |
| 26 | + |
| 27 | +/* |
| 28 | +** Append text to pOut, escaping any CDATA terminators. |
| 29 | +*/ |
| 30 | +static void rss_cdata_append(Blob *pOut, const char *zIn, int nIn){ |
| 31 | + const char *zEnd; |
| 32 | + const char *zDelim; |
| 33 | + if( pOut==0 ) return; |
| 34 | + if( zIn==0 ) zIn = ""; |
| 35 | + if( nIn<0 ) nIn = (int)strlen(zIn); |
| 36 | + zEnd = zIn + nIn; |
| 37 | + while( zIn<zEnd && (zDelim = strstr(zIn, "]]>") )!=0 ){ |
| 38 | + if( zDelim>=zEnd ) break; |
| 39 | + blob_append(pOut, zIn, (int)(zDelim - zIn)); |
| 40 | + blob_append_literal(pOut, "]]]]><![CDATA[>"); |
| 41 | + zIn = zDelim + 3; |
| 42 | + } |
| 43 | + if( zIn<zEnd ){ |
| 44 | + blob_append(pOut, zIn, (int)(zEnd - zIn)); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +/* |
| 49 | +** Return true if zIn looks like an absolute URL. |
| 50 | +*/ |
| 51 | +static int rss_is_absolute_url(const char *zIn, int nIn){ |
| 52 | + int i; |
| 53 | + if( zIn==0 || nIn<=0 ) return 0; |
| 54 | + if( nIn>=2 && zIn[0]=='/' && zIn[1]=='/' ) return 1; |
| 55 | + if( !((zIn[0]>='a' && zIn[0]<='z') || (zIn[0]>='A' && zIn[0]<='Z')) ){ |
| 56 | + return 0; |
| 57 | + } |
| 58 | + for(i=1; i<nIn; i++){ |
| 59 | + char c = zIn[i]; |
| 60 | + if( (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') |
| 61 | + || c=='+' || c=='-' || c=='.' ){ |
| 62 | + continue; |
| 63 | + } |
| 64 | + return c==':' ? 1 : 0; |
| 65 | + } |
| 66 | + return 0; |
| 67 | +} |
| 68 | + |
| 69 | +/* |
| 70 | +** Return the length of zBase without trailing slashes. |
| 71 | +*/ |
| 72 | +static int rss_trim_base(const char *zBase){ |
| 73 | + int n = zBase ? (int)strlen(zBase) : 0; |
| 74 | + while( n>0 && zBase[n-1]=='/' ) n--; |
| 75 | + return n; |
| 76 | +} |
| 77 | + |
| 78 | +/* |
| 79 | +** Compute the repository top path from zBase and return it. |
| 80 | +*/ |
| 81 | +static const char *rss_top_from_base(Blob *pTop, const char *zBase){ |
| 82 | + const char *z = zBase; |
| 83 | + const char *zSlash = 0; |
| 84 | + int n; |
| 85 | + if( zBase==0 ) return ""; |
| 86 | + if( strncmp(zBase, "http://", 7)==0 ){ |
| 87 | + z = zBase + 7; |
| 88 | + }else if( strncmp(zBase, "https://", 8)==0 ){ |
| 89 | + z = zBase + 8; |
| 90 | + }else{ |
| 91 | + return ""; |
| 92 | + } |
| 93 | + zSlash = strchr(z, '/'); |
| 94 | + if( zSlash==0 ) return ""; |
| 95 | + n = (int)strlen(zSlash); |
| 96 | + while( n>1 && zSlash[n-1]=='/' ) n--; |
| 97 | + blob_init(pTop, zSlash, n); |
| 98 | + return blob_str(pTop); |
| 99 | +} |
| 100 | + |
| 101 | +/* |
| 102 | +** Append an absolute URL to pOut, using zBase/zTop as the base. |
| 103 | +*/ |
| 104 | +static void rss_append_abs_url( |
| 105 | + Blob *pOut, |
| 106 | + const char *zBase, |
| 107 | + int nBase, |
| 108 | + const char *zTop, |
| 109 | + int nTop, |
| 110 | + const char *zRel, |
| 111 | + int nRel |
| 112 | +){ |
| 113 | + if( pOut==0 ) return; |
| 114 | + if( zRel==0 || nRel<=0 ) return; |
| 115 | + if( zBase==0 || zBase[0]==0 ){ |
| 116 | + blob_append(pOut, zRel, nRel); |
| 117 | + return; |
| 118 | + } |
| 119 | + if( zRel[0]=='#' || rss_is_absolute_url(zRel, nRel) ){ |
| 120 | + blob_append(pOut, zRel, nRel); |
| 121 | + return; |
| 122 | + } |
| 123 | + if( zRel[0]=='/' ){ |
| 124 | + if( nTop>1 && strncmp(zRel, zTop, nTop)==0 ){ |
| 125 | + blob_append(pOut, zBase, nBase); |
| 126 | + blob_append(pOut, zRel + nTop, nRel - nTop); |
| 127 | + }else{ |
| 128 | + blob_append(pOut, zBase, nBase); |
| 129 | + blob_append(pOut, zRel, nRel); |
| 130 | + } |
| 131 | + }else{ |
| 132 | + blob_append(pOut, zBase, nBase); |
| 133 | + blob_append_char(pOut, '/'); |
| 134 | + blob_append(pOut, zRel, nRel); |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +/* |
| 139 | +** Convert relative href/src attributes in zIn to absolute URLs. |
| 140 | +*/ |
| 141 | +static void rss_make_abs_links( |
| 142 | + Blob *pOut, |
| 143 | + const char *zBase, |
| 144 | + const char *zTop, |
| 145 | + const char *zIn, |
| 146 | + int nIn |
| 147 | +){ |
| 148 | + const char *z = zIn; |
| 149 | + const char *zEnd = zIn + nIn; |
| 150 | + const char *zLast = zIn; |
| 151 | + int nBase = rss_trim_base(zBase); |
| 152 | + int nTop = zTop ? (int)strlen(zTop) : 0; |
| 153 | + if( pOut==0 || zIn==0 ) return; |
| 154 | + while( z<zEnd ){ |
| 155 | + int nAttr = 0; |
| 156 | + if( (z>zIn && !(z[-1]==' ' || (z[-1]>='\t' && z[-1]<='\r')) |
| 157 | + && z[-1]!='<') ){ |
| 158 | + z++; |
| 159 | + continue; |
| 160 | + } |
| 161 | + if( zEnd - z >= 5 |
| 162 | + && (z[0]=='h' || z[0]=='H') |
| 163 | + && (z[1]=='r' || z[1]=='R') |
| 164 | + && (z[2]=='e' || z[2]=='E') |
| 165 | + && (z[3]=='f' || z[3]=='F') |
| 166 | + && z[4]=='=' |
| 167 | + ){ |
| 168 | + nAttr = 5; |
| 169 | + }else if( zEnd - z >= 4 |
| 170 | + && (z[0]=='s' || z[0]=='S') |
| 171 | + && (z[1]=='r' || z[1]=='R') |
| 172 | + && (z[2]=='c' || z[2]=='C') |
| 173 | + && z[3]=='=' |
| 174 | + ){ |
| 175 | + nAttr = 4; |
| 176 | + } |
| 177 | + if( nAttr==0 ){ |
| 178 | + z++; |
| 179 | + continue; |
| 180 | + } |
| 181 | + { |
| 182 | + const char *zVal = z + nAttr; |
| 183 | + const char *zValEnd = zVal; |
| 184 | + char quote = 0; |
| 185 | + if( zVal>=zEnd ) break; |
| 186 | + if( *zVal=='"' || *zVal=='\'' ){ |
| 187 | + quote = *zVal; |
| 188 | + zVal++; |
| 189 | + } |
| 190 | + zValEnd = zVal; |
| 191 | + while( zValEnd<zEnd ){ |
| 192 | + if( quote ){ |
| 193 | + if( *zValEnd==quote ) break; |
| 194 | + }else if( *zValEnd==' ' || (*zValEnd>='\t' && *zValEnd<='\r') |
| 195 | + || *zValEnd=='>' ){ |
| 196 | + break; |
| 197 | + } |
| 198 | + zValEnd++; |
| 199 | + } |
| 200 | + blob_append(pOut, zLast, zVal - zLast); |
| 201 | + rss_append_abs_url(pOut, zBase, nBase, zTop, nTop, |
| 202 | + zVal, (int)(zValEnd - zVal)); |
| 203 | + zLast = zValEnd; |
| 204 | + z = zValEnd; |
| 205 | + } |
| 206 | + } |
| 207 | + if( zLast<zEnd ) blob_append(pOut, zLast, (int)(zEnd - zLast)); |
| 208 | +} |
| 24 | 209 | |
| 25 | 210 | /* |
| 26 | 211 | ** WEBPAGE: timeline.rss |
| 27 | 212 | ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=HASH&tag=TAG&wiki=NAME&name=FILENAME |
| 28 | 213 | ** |
| | @@ -44,17 +229,20 @@ |
| 44 | 229 | void page_timeline_rss(void){ |
| 45 | 230 | Stmt q; |
| 46 | 231 | int nLine=0; |
| 47 | 232 | char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; |
| 48 | 233 | Blob bSQL; |
| 234 | + Blob base = BLOB_INITIALIZER; |
| 235 | + Blob top = BLOB_INITIALIZER; |
| 49 | 236 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 50 | 237 | const char *zTicketUuid = PD("tkt",NULL); |
| 51 | 238 | const char *zTag = PD("tag",NULL); |
| 52 | 239 | const char *zFilename = PD("name",NULL); |
| 53 | 240 | const char *zWiki = PD("wiki",NULL); |
| 54 | 241 | int nLimit = atoi(PD("n","20")); |
| 55 | 242 | int nTagId; |
| 243 | + int bHasForum; |
| 56 | 244 | const char zSQL1[] = |
| 57 | 245 | @ SELECT |
| 58 | 246 | @ blob.rid, |
| 59 | 247 | @ uuid, |
| 60 | 248 | @ event.mtime, |
| | @@ -69,16 +257,27 @@ |
| 69 | 257 | @ FROM event, blob |
| 70 | 258 | @ WHERE blob.rid=event.objid |
| 71 | 259 | ; |
| 72 | 260 | |
| 73 | 261 | login_check_credentials(); |
| 74 | | - if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ |
| 262 | + if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum ){ |
| 75 | 263 | return; |
| 76 | 264 | } |
| 265 | + bHasForum = db_table_exists("repository","forumpost"); |
| 77 | 266 | |
| 78 | 267 | blob_zero(&bSQL); |
| 79 | 268 | blob_append_sql( &bSQL, "%s", zSQL1/*safe-for-%s*/ ); |
| 269 | + if( bHasForum ){ |
| 270 | + blob_append_sql(&bSQL, |
| 271 | + " AND (event.type!='f' OR event.objid IN (" |
| 272 | + "SELECT fpid FROM forumpost AS f " |
| 273 | + "WHERE NOT EXISTS(SELECT 1 FROM forumpost AS nx " |
| 274 | + "WHERE nx.fprev=f.fpid)))" |
| 275 | + ); |
| 276 | + }else{ |
| 277 | + blob_append_sql(&bSQL, " AND event.type!='f'"); |
| 278 | + } |
| 80 | 279 | |
| 81 | 280 | if( zType[0]!='a' ){ |
| 82 | 281 | if( zType[0]=='c' && !g.perm.Read ) zType = "x"; |
| 83 | 282 | else if( (zType[0]=='w' || zType[0]=='e') && !g.perm.RdWiki ) zType = "x"; |
| 84 | 283 | else if( zType[0]=='t' && !g.perm.RdTkt ) zType = "x"; |
| | @@ -151,13 +350,17 @@ |
| 151 | 350 | if( zProjectDescr==0 ){ |
| 152 | 351 | zProjectDescr = zProjectName; |
| 153 | 352 | } |
| 154 | 353 | |
| 155 | 354 | zPubDate = cgi_rfc822_datestamp(time(NULL)); |
| 355 | + blob_append(&base, g.zBaseURL, -1); |
| 356 | + rss_top_from_base(&top, g.zBaseURL); |
| 156 | 357 | |
| 157 | 358 | @ <?xml version="1.0"?> |
| 158 | | - @ <rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"> |
| 359 | + @ <rss xmlns:dc="http://purl.org/dc/elements/1.1/" \ |
| 360 | + @ xmlns:content="http://purl.org/rss/1.0/modules/content/" \ |
| 361 | + @ version="2.0"> |
| 159 | 362 | @ <channel> |
| 160 | 363 | @ <title>%h(zProjectName)</title> |
| 161 | 364 | @ <link>%s(g.zBaseURL)</link> |
| 162 | 365 | @ <description>%h(zProjectDescr)</description> |
| 163 | 366 | @ <pubDate>%s(zPubDate)</pubDate> |
| | @@ -164,35 +367,39 @@ |
| 164 | 367 | @ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator> |
| 165 | 368 | free(zPubDate); |
| 166 | 369 | db_prepare(&q, "%s", blob_sql_text(&bSQL)); |
| 167 | 370 | blob_reset( &bSQL ); |
| 168 | 371 | while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){ |
| 372 | + int rid = db_column_int(&q, 0); |
| 169 | 373 | const char *zId = db_column_text(&q, 1); |
| 170 | 374 | const char *zEType = db_column_text(&q, 3); |
| 171 | 375 | const char *zCom = db_column_text(&q, 4); |
| 172 | 376 | const char *zAuthor = db_column_text(&q, 5); |
| 173 | 377 | char *zPrefix = ""; |
| 174 | 378 | char *zSuffix = 0; |
| 175 | 379 | char *zDate; |
| 176 | | - int nChild = db_column_int(&q, 5); |
| 380 | + int nChild = db_column_int(&q, 6); |
| 177 | 381 | int nParent = db_column_int(&q, 7); |
| 178 | 382 | const char *zTagList = db_column_text(&q, 8); |
| 383 | + Manifest *pPost = 0; |
| 384 | + Blob contentHtml = BLOB_INITIALIZER; |
| 385 | + int bForumContent = 0; |
| 179 | 386 | time_t ts; |
| 180 | 387 | |
| 181 | 388 | if( zTagList && zTagList[0]==0 ) zTagList = 0; |
| 182 | 389 | ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0); |
| 183 | 390 | zDate = cgi_rfc822_datestamp(ts); |
| 184 | 391 | |
| 185 | | - if('c'==zEType[0]){ |
| 392 | + if( zEType[0]=='c' ){ |
| 186 | 393 | if( nParent>1 && nChild>1 ){ |
| 187 | 394 | zPrefix = "*MERGE/FORK* "; |
| 188 | 395 | }else if( nParent>1 ){ |
| 189 | 396 | zPrefix = "*MERGE* "; |
| 190 | 397 | }else if( nChild>1 ){ |
| 191 | 398 | zPrefix = "*FORK* "; |
| 192 | 399 | } |
| 193 | | - }else if('w'==zEType[0]){ |
| 400 | + }else if( zEType[0]=='w' ){ |
| 194 | 401 | switch(zCom ? zCom[0] : 0){ |
| 195 | 402 | case ':': zPrefix = "Edit wiki page: "; break; |
| 196 | 403 | case '+': zPrefix = "Add wiki page: "; break; |
| 197 | 404 | case '-': zPrefix = "Delete wiki page: "; break; |
| 198 | 405 | } |
| | @@ -201,24 +408,60 @@ |
| 201 | 408 | |
| 202 | 409 | if( zTagList ){ |
| 203 | 410 | zSuffix = mprintf(" (tags: %s)", zTagList); |
| 204 | 411 | } |
| 205 | 412 | |
| 413 | + if( zEType[0]=='f' ){ |
| 414 | + if( !g.perm.ModForum && content_is_private(rid) ){ |
| 415 | + free(zDate); |
| 416 | + free(zSuffix); |
| 417 | + continue; |
| 418 | + } |
| 419 | + pPost = manifest_get(rid, CFTYPE_FORUM, 0); |
| 420 | + if( pPost ){ |
| 421 | + forum_render_to_html(&contentHtml, pPost->zMimetype, pPost->zWiki); |
| 422 | + if( blob_size(&contentHtml)>0 ){ |
| 423 | + Blob normalized = BLOB_INITIALIZER; |
| 424 | + rss_make_abs_links(&normalized, blob_str(&base), |
| 425 | + blob_str(&top), blob_str(&contentHtml), |
| 426 | + blob_size(&contentHtml)); |
| 427 | + blob_reset(&contentHtml); |
| 428 | + blob_append(&contentHtml, blob_str(&normalized), |
| 429 | + blob_size(&normalized)); |
| 430 | + blob_reset(&normalized); |
| 431 | + bForumContent = 1; |
| 432 | + } |
| 433 | + } |
| 434 | + } |
| 206 | 435 | @ <item> |
| 207 | 436 | @ <title>%s(zPrefix)%h(zCom)%h(zSuffix)</title> |
| 208 | 437 | @ <link>%s(g.zBaseURL)/info/%s(zId)</link> |
| 209 | 438 | @ <description>%s(zPrefix)%h(zCom)%h(zSuffix)</description> |
| 210 | 439 | @ <pubDate>%s(zDate)</pubDate> |
| 211 | 440 | @ <dc:creator>%h(zAuthor)</dc:creator> |
| 212 | 441 | @ <guid>%s(g.zBaseURL)/info/%s(zId)</guid> |
| 442 | + if( bForumContent ){ |
| 443 | + Blob cdata = BLOB_INITIALIZER; |
| 444 | + @ <dc:format>text/html</dc:format> |
| 445 | + @ <content:encoded><![CDATA[ |
| 446 | + rss_cdata_append(&cdata, blob_str(&contentHtml), |
| 447 | + blob_size(&contentHtml)); |
| 448 | + cgi_append_content(blob_str(&cdata), blob_size(&cdata)); |
| 449 | + blob_reset(&cdata); |
| 450 | + @ ]]></content:encoded> |
| 451 | + } |
| 213 | 452 | @ </item> |
| 453 | + if( pPost ) manifest_destroy(pPost); |
| 454 | + blob_reset(&contentHtml); |
| 214 | 455 | free(zDate); |
| 215 | 456 | free(zSuffix); |
| 216 | 457 | nLine++; |
| 217 | 458 | } |
| 218 | 459 | |
| 219 | 460 | db_finalize(&q); |
| 461 | + blob_reset(&base); |
| 462 | + blob_reset(&top); |
| 220 | 463 | @ </channel> |
| 221 | 464 | @ </rss> |
| 222 | 465 | |
| 223 | 466 | if( zFreeProjectName != 0 ){ |
| 224 | 467 | free( zFreeProjectName ); |
| | @@ -233,11 +476,12 @@ |
| 233 | 476 | ** The CLI variant of the /timeline.rss page, this produces an RSS |
| 234 | 477 | ** feed of the timeline to stdout. |
| 235 | 478 | ** |
| 236 | 479 | ** Options: |
| 237 | 480 | ** -type|y FLAG May be: all (default), ci (show check-ins only), |
| 238 | | -** t (show tickets only), w (show wiki only) |
| 481 | +** t (show tickets only), w (show wiki only), |
| 482 | +** e (show tech notes only), f (show forum posts only) |
| 239 | 483 | ** |
| 240 | 484 | ** -limit|n LIMIT The maximum number of items to show |
| 241 | 485 | ** |
| 242 | 486 | ** -tkt HASH Filter for only those events for the specified ticket |
| 243 | 487 | ** |
| | @@ -257,24 +501,28 @@ |
| 257 | 501 | void cmd_timeline_rss(void){ |
| 258 | 502 | Stmt q; |
| 259 | 503 | int nLine=0; |
| 260 | 504 | char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; |
| 261 | 505 | Blob bSQL; |
| 506 | + Blob base = BLOB_INITIALIZER; |
| 507 | + Blob top = BLOB_INITIALIZER; |
| 262 | 508 | const char *zType = find_option("type","y",1); /* Type of events;All if NULL*/ |
| 263 | 509 | const char *zTicketUuid = find_option("tkt",NULL,1); |
| 264 | 510 | const char *zTag = find_option("tag",NULL,1); |
| 265 | 511 | const char *zFilename = find_option("name",NULL,1); |
| 266 | 512 | const char *zWiki = find_option("wiki",NULL,1); |
| 267 | 513 | const char *zLimit = find_option("limit", "n",1); |
| 268 | 514 | const char *zBaseURL = find_option("url", NULL, 1); |
| 269 | 515 | int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" ); |
| 270 | 516 | int nTagId; |
| 517 | + int bHasForum; |
| 271 | 518 | const char zSQL1[] = |
| 272 | 519 | @ SELECT |
| 273 | 520 | @ blob.rid, |
| 274 | 521 | @ uuid, |
| 275 | 522 | @ event.mtime, |
| 523 | + @ event.type, |
| 276 | 524 | @ coalesce(ecomment,comment), |
| 277 | 525 | @ coalesce(euser,user), |
| 278 | 526 | @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), |
| 279 | 527 | @ (SELECT count(*) FROM plink WHERE cid=blob.rid), |
| 280 | 528 | @ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref |
| | @@ -295,10 +543,21 @@ |
| 295 | 543 | /* We should be done with options.. */ |
| 296 | 544 | verify_all_options(); |
| 297 | 545 | |
| 298 | 546 | blob_zero(&bSQL); |
| 299 | 547 | blob_append( &bSQL, zSQL1, -1 ); |
| 548 | + bHasForum = db_table_exists("repository","forumpost"); |
| 549 | + if( bHasForum ){ |
| 550 | + blob_append_sql(&bSQL, |
| 551 | + " AND (event.type!='f' OR event.objid IN (" |
| 552 | + "SELECT fpid FROM forumpost AS f " |
| 553 | + "WHERE NOT EXISTS(SELECT 1 FROM forumpost AS nx " |
| 554 | + "WHERE nx.fprev=f.fpid)))" |
| 555 | + ); |
| 556 | + }else{ |
| 557 | + blob_append_sql(&bSQL, " AND event.type!='f'"); |
| 558 | + } |
| 300 | 559 | |
| 301 | 560 | if( zType[0]!='a' ){ |
| 302 | 561 | blob_append_sql(&bSQL, " AND event.type=%Q", zType); |
| 303 | 562 | } |
| 304 | 563 | |
| | @@ -350,14 +609,17 @@ |
| 350 | 609 | if( zProjectDescr==0 ){ |
| 351 | 610 | zProjectDescr = zProjectName; |
| 352 | 611 | } |
| 353 | 612 | |
| 354 | 613 | zPubDate = cgi_rfc822_datestamp(time(NULL)); |
| 614 | + blob_append(&base, zBaseURL, -1); |
| 615 | + rss_top_from_base(&top, zBaseURL); |
| 355 | 616 | |
| 356 | 617 | fossil_print("<?xml version=\"1.0\"?>"); |
| 357 | 618 | fossil_print("<rss xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " |
| 358 | | - " version=\"2.0\">"); |
| 619 | + " xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" " |
| 620 | + " version=\"2.0\">\n"); |
| 359 | 621 | fossil_print("<channel>\n"); |
| 360 | 622 | fossil_print("<title>%h</title>\n", zProjectName); |
| 361 | 623 | fossil_print("<link>%s</link>\n", zBaseURL); |
| 362 | 624 | fossil_print("<description>%h</description>\n", zProjectDescr); |
| 363 | 625 | fossil_print("<pubDate>%s</pubDate>\n", zPubDate); |
| | @@ -365,53 +627,98 @@ |
| 365 | 627 | MANIFEST_VERSION, MANIFEST_DATE); |
| 366 | 628 | free(zPubDate); |
| 367 | 629 | db_prepare(&q, "%s", blob_sql_text(&bSQL)); |
| 368 | 630 | blob_reset( &bSQL ); |
| 369 | 631 | while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){ |
| 632 | + int rid = db_column_int(&q, 0); |
| 370 | 633 | const char *zId = db_column_text(&q, 1); |
| 371 | | - const char *zCom = db_column_text(&q, 3); |
| 372 | | - const char *zAuthor = db_column_text(&q, 4); |
| 634 | + const char *zEType = db_column_text(&q, 3); |
| 635 | + const char *zCom = db_column_text(&q, 4); |
| 636 | + const char *zAuthor = db_column_text(&q, 5); |
| 373 | 637 | char *zPrefix = ""; |
| 374 | 638 | char *zSuffix = 0; |
| 375 | 639 | char *zDate; |
| 376 | | - int nChild = db_column_int(&q, 5); |
| 377 | | - int nParent = db_column_int(&q, 6); |
| 378 | | - const char *zTagList = db_column_text(&q, 7); |
| 640 | + int nChild = db_column_int(&q, 6); |
| 641 | + int nParent = db_column_int(&q, 7); |
| 642 | + const char *zTagList = db_column_text(&q, 8); |
| 643 | + Manifest *pPost = 0; |
| 644 | + Blob contentHtml = BLOB_INITIALIZER; |
| 645 | + int bForumContent = 0; |
| 379 | 646 | time_t ts; |
| 380 | 647 | |
| 381 | 648 | if( zTagList && zTagList[0]==0 ) zTagList = 0; |
| 382 | 649 | ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0); |
| 383 | 650 | zDate = cgi_rfc822_datestamp(ts); |
| 384 | 651 | |
| 385 | | - if( nParent>1 && nChild>1 ){ |
| 386 | | - zPrefix = "*MERGE/FORK* "; |
| 387 | | - }else if( nParent>1 ){ |
| 388 | | - zPrefix = "*MERGE* "; |
| 389 | | - }else if( nChild>1 ){ |
| 390 | | - zPrefix = "*FORK* "; |
| 652 | + if( zEType[0]=='c' ){ |
| 653 | + if( nParent>1 && nChild>1 ){ |
| 654 | + zPrefix = "*MERGE/FORK* "; |
| 655 | + }else if( nParent>1 ){ |
| 656 | + zPrefix = "*MERGE* "; |
| 657 | + }else if( nChild>1 ){ |
| 658 | + zPrefix = "*FORK* "; |
| 659 | + } |
| 660 | + }else if( zEType[0]=='w' ){ |
| 661 | + switch(zCom ? zCom[0] : 0){ |
| 662 | + case ':': zPrefix = "Edit wiki page: "; break; |
| 663 | + case '+': zPrefix = "Add wiki page: "; break; |
| 664 | + case '-': zPrefix = "Delete wiki page: "; break; |
| 665 | + } |
| 666 | + if(*zPrefix) ++zCom; |
| 391 | 667 | } |
| 392 | 668 | |
| 393 | 669 | if( zTagList ){ |
| 394 | 670 | zSuffix = mprintf(" (tags: %s)", zTagList); |
| 395 | 671 | } |
| 396 | 672 | |
| 673 | + if( zEType[0]=='f' ){ |
| 674 | + pPost = manifest_get(rid, CFTYPE_FORUM, 0); |
| 675 | + if( pPost ){ |
| 676 | + forum_render_to_html(&contentHtml, pPost->zMimetype, pPost->zWiki); |
| 677 | + if( blob_size(&contentHtml)>0 ){ |
| 678 | + Blob normalized = BLOB_INITIALIZER; |
| 679 | + rss_make_abs_links(&normalized, blob_str(&base), |
| 680 | + blob_str(&top), blob_str(&contentHtml), |
| 681 | + blob_size(&contentHtml)); |
| 682 | + blob_reset(&contentHtml); |
| 683 | + blob_append(&contentHtml, blob_str(&normalized), |
| 684 | + blob_size(&normalized)); |
| 685 | + blob_reset(&normalized); |
| 686 | + bForumContent = 1; |
| 687 | + } |
| 688 | + } |
| 689 | + } |
| 397 | 690 | fossil_print("<item>"); |
| 398 | 691 | fossil_print("<title>%s%h%h</title>\n", zPrefix, zCom, zSuffix); |
| 399 | 692 | fossil_print("<link>%s/info/%s</link>\n", zBaseURL, zId); |
| 400 | 693 | fossil_print("<description>%s%h%h</description>\n", zPrefix, zCom, zSuffix); |
| 401 | 694 | fossil_print("<pubDate>%s</pubDate>\n", zDate); |
| 402 | 695 | fossil_print("<dc:creator>%h</dc:creator>\n", zAuthor); |
| 403 | 696 | fossil_print("<guid>%s/info/%s</guid>\n", g.zBaseURL, zId); |
| 697 | + if( bForumContent ){ |
| 698 | + Blob cdata = BLOB_INITIALIZER; |
| 699 | + fossil_print("<dc:format>text/html</dc:format>\n"); |
| 700 | + fossil_print("<content:encoded><![CDATA["); |
| 701 | + rss_cdata_append(&cdata, blob_str(&contentHtml), |
| 702 | + blob_size(&contentHtml)); |
| 703 | + fossil_print("%s", blob_str(&cdata)); |
| 704 | + blob_reset(&cdata); |
| 705 | + fossil_print("]]></content:encoded>\n"); |
| 706 | + } |
| 404 | 707 | fossil_print("</item>\n"); |
| 708 | + if( pPost ) manifest_destroy(pPost); |
| 709 | + blob_reset(&contentHtml); |
| 405 | 710 | free(zDate); |
| 406 | 711 | free(zSuffix); |
| 407 | 712 | nLine++; |
| 408 | 713 | } |
| 409 | 714 | |
| 410 | 715 | db_finalize(&q); |
| 716 | + blob_reset(&base); |
| 717 | + blob_reset(&top); |
| 411 | 718 | fossil_print("</channel>\n"); |
| 412 | 719 | fossil_print("</rss>\n"); |
| 413 | 720 | |
| 414 | 721 | if( zFreeProjectName != 0 ){ |
| 415 | 722 | free( zFreeProjectName ); |
| 416 | 723 | } |
| 417 | 724 | } |
| 418 | 725 | |