Fossil SCM

Improvements to the "status=" query parameter processing on the /forum page.

drh 2026-05-28 16:12 UTC forum-statuses
Commit 57e21b8cfa61984bc9928b9a859e0eb628deb3d4a0624b7a5eb2a46c9d360cde
1 file changed +133 -94
+133 -94
--- src/forum.c
+++ src/forum.c
@@ -75,14 +75,27 @@
7575
7676
/*
7777
** A list of ForumStatus objects.
7878
*/
7979
struct ForumStatusList {
80
- struct ForumStatus * aStatus; /* List of statuses */
81
- unsigned int n; /* Number of entries */
80
+ struct ForumStatus *aStatus; /* List of statuses */
81
+ unsigned int n; /* Number of entries */
82
+};
83
+
84
+/*
85
+** Information passed into the status_match() SQL function
86
+** via the sqlite3_user_data() mechanism, and used by status_match()
87
+** to determine whether or not a particular forum thread should
88
+** be displayed.
89
+*/
90
+struct ForumStatusMatch {
91
+ const ForumStatusList *pFses; /* Parsed forum-statuses setting */
92
+ int eStatusTag; /* tagid for the "status" property */
93
+ unsigned int iMatch; /* Match this status value */
8294
};
8395
#endif /* INTERFACE */
96
+
8497
8598
/*
8699
** Returns a high-level representation of the forum-statuses setting.
87100
** This is a singleton, cached across calls.
88101
*/
@@ -114,11 +127,10 @@
114127
" SELECT e.value FROM setting s, jsonb_each(s.v) e"
115128
" WHERE json_valid(s.v, 0x02)"
116129
" )"
117130
" SELECT r->>'label', r->>'value', r->>'description'"
118131
" FROM room;"
119
-/* "INSERT OR IGNORE INTO forumstatus(1,'Open','open','Open');" */
120132
);
121133
fses.n = (unsigned)db_int(0, "SELECT count(*) FROM forumstatus");
122134
if( fses.n ){
123135
int i = 0;
124136
Stmt q;
@@ -2393,10 +2405,75 @@
23932405
}
23942406
if( i ){
23952407
style_submenu_multichoice("status", i/2, az, 0);
23962408
}
23972409
}
2410
+
2411
+/*
2412
+** Transient SQL Function: status_match(FROOT)
2413
+**
2414
+** Return true if the forum thread identified by FROOT should be included
2415
+** in a list of threads. Used to implement the status=NAME query parameter
2416
+** on /forum.
2417
+**
2418
+** The result of this routine depends on the content of the
2419
+** ForumStatusMatch *pMData object that is available via sqlite3_user_data().
2420
+**
2421
+** * If pMData==NULL, always return true. This means that no
2422
+** filtering of threads is being done. This is the common case.
2423
+**
2424
+** * If FROOT contains a status property value that matches
2425
+** pMData->iMatch, return true.
2426
+**
2427
+** * if pMData->iMatch==0 (meaning we want to match the default
2428
+** status value) and if the FROOT thread contains a status that
2429
+** is not on the list of statuses or if FROOT has no statue
2430
+** property at all, then return true. In other words, a forum
2431
+** thread with no status property or an unknown status property
2432
+** is treated as if it had the default status.
2433
+**
2434
+** * Otherwise, return false.
2435
+*/
2436
+static void forum_status_match(
2437
+ sqlite3_context *context,
2438
+ int argc,
2439
+ sqlite3_value **argv
2440
+){
2441
+ static Stmt q;
2442
+ ForumStatusMatch *pMData = sqlite3_user_data(context);
2443
+ int i;
2444
+
2445
+ if( pMData==0 ){
2446
+ sqlite3_result_int(context, 1);
2447
+ return;
2448
+ }
2449
+ db_static_prepare(&q,
2450
+ "SELECT value FROM tagxref\n"
2451
+ " WHERE tagid=%d\n"
2452
+ " AND tagtype>=1\n"
2453
+ " AND rid=:rid\n"
2454
+ " ORDER BY mtime DESC LIMIT 1",
2455
+ pMData->eStatusTag
2456
+ );
2457
+ db_bind_int(&q, ":rid", sqlite3_value_int(argv[0]));
2458
+ if( db_step(&q)==SQLITE_ROW ){
2459
+ const char *zValue = (const char*)db_column_text(&q,0);
2460
+ const ForumStatusList *pFses = pMData->pFses;
2461
+ if( zValue==0 ){
2462
+ i = 0;
2463
+ }else{
2464
+ for(i=0; i<pFses->n; i++){
2465
+ if( fossil_strcmp(pFses->aStatus[i].zValue,zValue)==0 ) break;
2466
+ }
2467
+ }
2468
+ if( i>=pMData->pFses->n ) i = 0;
2469
+ }else{
2470
+ i = 0;
2471
+ }
2472
+ db_reset(&q);
2473
+ sqlite3_result_int(context, i==pMData->iMatch);
2474
+}
23982475
23992476
/*
24002477
** WEBPAGE: forummain
24012478
** WEBPAGE: forum
24022479
**
@@ -2414,13 +2491,15 @@
24142491
Stmt q;
24152492
int iLimit = 0, iOfst, iCnt;
24162493
int srchFlags;
24172494
const int isSearch = P("s")!=0;
24182495
const char *zStatusFilter = P("status");
2419
- char const *zLimit = 0;
2420
- int eStatusTag = 0;
2421
- int bHasStatus = 0;
2496
+ char const *zLimit = 0; /* Value of the n= query parameter */
2497
+ int eStatusTag = 0; /* tagid for the "status" property */
2498
+ int bHasStatus = 0; /* True if forum-statuses setting exists */
2499
+ int bFilter = 0; /* True if status=NAME query parameter */
2500
+ ForumStatusMatch sFSM; /* Aux data to status_match() SQL function */
24222501
24232502
login_check_credentials();
24242503
srchFlags = search_restrict(SRCH_FORUM);
24252504
if( !g.perm.RdForum ){
24262505
login_needed(g.anon.RdForum);
@@ -2465,103 +2544,66 @@
24652544
}
24662545
style_submenu_entry("n","Max:",4,0);
24672546
forum_status_submenu();
24682547
iOfst = atoi(PD("x","0"));
24692548
iCnt = 0;
2470
- if( zStatusFilter &&
2471
- (!*zStatusFilter || 0==fossil_strcmp("*",zStatusFilter)) ){
2472
- zStatusFilter = 0;
2549
+ if( zStatusFilter ){
2550
+ if( zStatusFilter[0]==0 || 0==fossil_strcmp("*",zStatusFilter) ){
2551
+ zStatusFilter = 0;
2552
+ }else{
2553
+ bFilter = bHasStatus;
2554
+ }
24732555
}
24742556
if( db_table_exists("repository","forumpost") ){
2475
- const ForumStatusList * pFstat = forum_statuses();
2476
- const int iStatusTagId = bHasStatus
2477
- ? db_int(0, "SELECT tagid FROM TAG WHERE tagname='status'")
2478
- : 0;
2479
- Stmt qStat = empty_Stmt;
2480
- db_multi_exec(
2481
- /* List of forumpost.fpid values which match current
2482
- filters. */
2483
- "CREATE TEMP TABLE trootid(id INTEGER PRIMARY KEY);"
2484
- );
2557
+ const ForumStatusList *pFstat = forum_statuses();
2558
+ Stmt qStat = empty_Stmt; /* Query to get status information */
24852559
if( bHasStatus ){
2486
- db_prepare(
2487
- /* FIXME/TODO: it would be nice to be able to get this info
2488
- ** directly into the thread query below, rather than
2489
- ** requiring a separate statement and lookup inside the
2490
- ** q loop. */
2491
- &qStat,
2560
+ /* The qStat query runs once for each output row generate by the
2561
+ ** q query. It determines the value and label of the status for
2562
+ ** the row with froot=:rowid
2563
+ */
2564
+ db_prepare(&qStat,
24922565
"SELECT tagxref.value, forumstatus.label\n"
24932566
" FROM forumstatus, tagxref\n"
24942567
" WHERE tagid=%d AND tagtype>=1\n"
24952568
" AND forumstatus.value=tagxref.value\n"
24962569
" AND rid=:rid\n"
2497
- " AND if(%d=0,1,tagxref.value=%Q)\n"
2498
- " ORDER BY mtime DESC",
2499
- iStatusTagId, !!zStatusFilter, zStatusFilter
2500
- );
2501
- if( zStatusFilter ){
2502
- const int bIsDflt =
2503
- 0==fossil_strcmp(pFstat->aStatus[0].zValue, zStatusFilter);
2504
- db_multi_exec(
2505
- "WITH stati(val) AS (SELECT value FROM forumstatus)\n"
2506
- "INSERT INTO trootid\n"
2507
- /* Rules:
2508
-
2509
- (1) Filter on status=$zStatusFilter
2510
- (2) If $zStatusFilter==default status then also include
2511
- any posts with no status tag.
2512
- (3) If no status tag is found for a given post, assume a tag
2513
- with the value of the first (default) status.
2514
- (4) If a status value is found which is not in
2515
- [forumstatus] then treat it as if it were the default
2516
- value.
2517
-
2518
- We need to ensure that we filter only the most recent
2519
- value of each tag and count tagtype=0 (cancel) tags
2520
- properly.
2521
- */
2522
- " SELECT fpid FROM forumpost\n"
2523
- " LEFT JOIN tagxref x ON fpid=rid\n"
2524
- " WHERE froot=fpid AND firt IS NULL\n"
2525
- " AND (x.tagtype IS NULL OR x.tagtype>0)\n"
2526
- " AND (x.tagid IS NULL OR x.tagid=%d)\n"
2527
- " AND CASE\n"
2528
- " WHEN %d" /*bIsDflt*/
2529
- " THEN ("
2530
- " x.value IS NULL"
2531
- " OR x.value=%Q"
2532
- " OR x.value NOT IN stati" /* (4) */
2533
- " )\n" /* (2,3,4) */
2534
- " ELSE x.value=%Q\n" /* (1) */
2535
- " END",
2536
- iStatusTagId, bIsDflt, zStatusFilter, zStatusFilter
2537
- );
2538
- (void)bIsDflt;
2539
- }else{
2540
- db_multi_exec(
2541
- "INSERT INTO trootid\n"
2542
- " SELECT fpid FROM forumpost\n"
2543
- " WHERE froot=fpid AND firt IS NULL"
2544
- );
2545
- }
2546
- }else{
2547
- db_multi_exec(
2548
- "INSERT INTO trootid\n"
2549
- " SELECT fpid FROM forumpost\n"
2550
- " WHERE froot=fpid AND firt IS NULL"
2551
- );
2552
- }
2570
+ " ORDER BY mtime DESC",
2571
+ eStatusTag
2572
+ );
2573
+ }
2574
+
2575
+ /* Create the status_match() SQL function that will determine
2576
+ ** whether or not each thread in the "q" query below is eligible
2577
+ ** for display
2578
+ */
2579
+ if( bFilter ){
2580
+ sFSM.pFses = pFstat;
2581
+ sFSM.eStatusTag = eStatusTag;
2582
+ for(sFSM.iMatch=0; sFSM.iMatch<pFstat->n; sFSM.iMatch++){
2583
+ if( 0==fossil_strcmp(zStatusFilter,
2584
+ pFstat->aStatus[sFSM.iMatch].zValue) ){
2585
+ break;
2586
+ }
2587
+ }
2588
+ sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,(void*)&sFSM,
2589
+ forum_status_match, 0, 0);
2590
+ }else{
2591
+ sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,
2592
+ forum_status_match, 0, 0);
2593
+ }
2594
+
25532595
db_prepare(&q,
25542596
"WITH thread(root,endtime,lastrid) AS (\n"
25552597
" SELECT\n"
25562598
" froot,\n"
25572599
" max(fmtime),\n"
25582600
" fpid\n"
2559
- " FROM forumpost, trootid\n"
2601
+ " FROM forumpost\n"
25602602
" WHERE %s/*ModForum*/\n"
2561
- " AND froot=trootid.id\n"
25622603
" GROUP BY froot\n"
2604
+ " HAVING status_match(froot)\n"
25632605
" ORDER BY 2 DESC\n"
25642606
" LIMIT %d OFFSET %d\n"
25652607
")\n"
25662608
"SELECT\n"
25672609
" julianday('now') - thread.endtime,\n" /* 0 */
@@ -2586,36 +2628,32 @@
25862628
const char *zUuid;
25872629
const char *zTitle;
25882630
const char *zStatus;
25892631
const char *zStatusLbl;
25902632
const int bShowStatus = bHasStatus && !zStatusFilter;
2591
- const int nCols = bShowStatus ? 4 : 3
2592
- /* When filtering on status, elide the status column */;
2633
+ const int nCols = bShowStatus ? 4 : 3;
2634
+
25932635
if( qStat.pStmt ){
2636
+ /* Determine the status value for this row */
25942637
db_reset(&qStat);
25952638
db_bind_int(&qStat, ":rid", db_column_int(&q,6));
25962639
if( db_step(&qStat)==SQLITE_ROW ){
25972640
zStatus = db_column_text(&qStat, 0);
25982641
zStatusLbl = db_column_text(&qStat, 1);
25992642
}else{
26002643
zStatus = pFstat->aStatus[0].zValue;
26012644
zStatusLbl = pFstat->aStatus[0].zLabel;
26022645
}
2603
- if( zStatusFilter && 0!=fossil_strcmp(zStatusFilter,zStatus) ){
2604
- continue;
2605
- }
26062646
}else{
26072647
zStatus = zStatusLbl = NULL;
26082648
}
26092649
zAge = human_readable_age(db_column_double(&q,0));
26102650
nMsg = db_column_int(&q, 2);
26112651
zUuid = db_column_text(&q, 3);
26122652
zTitle = db_column_text(&q, 4);
26132653
if( iCnt==0 ){
2614
- char * zTail = zStatusFilter
2615
- ? mprintf(" with status=%Q", zStatusFilter)
2616
- : 0;
2654
+ char *zTail = bFilter ? mprintf(" with status=%Q", zStatusFilter): 0;
26172655
if( iOfst>0 ){
26182656
@ <h1>Threads at least %s(zAge) old%h(zTail ? zTail : "")</h1>
26192657
}else{
26202658
@ <h1>Most recent threads%h(zTail ? zTail : "")</h1>
26212659
}
@@ -2623,18 +2661,18 @@
26232661
@ <div class='forumPosts fileage'><table width="100%%">
26242662
if( iOfst>0 ){
26252663
if( iOfst>iLimit ){
26262664
@ <tr><td colspan="%d(nCols)">\
26272665
@ <a href='%R/forum?x=%d(iOfst-iLimit)&n=%d(iLimit) \
2628
- if( zStatusFilter ){
2666
+ if( bFilter ){
26292667
@ &status=%T(zStatusFilter)\
26302668
}
26312669
@ '>&uarr; Newer...</a></td></tr>
26322670
}else{
26332671
@ <tr><td colspan="%d(nCols)">\
26342672
@ <a href='%R/forum?n=%d(iLimit)\
2635
- if( zStatusFilter ){
2673
+ if( bFilter ){
26362674
@ &status=%T(zStatusFilter) \
26372675
}
26382676
@ '>&uarr; Newer...</a></td></tr>
26392677
}
26402678
}
@@ -2641,11 +2679,11 @@
26412679
}
26422680
iCnt++;
26432681
if( iCnt>iLimit ){
26442682
@ <tr><td colspan="%d(nCols)">\
26452683
@ <a href='%R/forum?x=%d(iOfst+iLimit)&n=%d(iLimit) \
2646
- if( zStatusFilter ){
2684
+ if( bFilter ){
26472685
@ &status=%T(zStatusFilter)\
26482686
}
26492687
@ '>&darr; Older...</a></td></tr>
26502688
fossil_free(zAge);
26512689
break;
@@ -2678,10 +2716,11 @@
26782716
@</tr>
26792717
fossil_free(zAge);
26802718
}
26812719
db_finalize(&q);
26822720
if( qStat.pStmt ) db_finalize(&qStat);
2721
+ sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,0,0,0);
26832722
}
26842723
if( iCnt>0 ){
26852724
@ </table></div>
26862725
}else{
26872726
@ <h1>No forum posts found</h1>
26882727
--- src/forum.c
+++ src/forum.c
@@ -75,14 +75,27 @@
75
76 /*
77 ** A list of ForumStatus objects.
78 */
79 struct ForumStatusList {
80 struct ForumStatus * aStatus; /* List of statuses */
81 unsigned int n; /* Number of entries */
 
 
 
 
 
 
 
 
 
 
 
 
82 };
83 #endif /* INTERFACE */
 
84
85 /*
86 ** Returns a high-level representation of the forum-statuses setting.
87 ** This is a singleton, cached across calls.
88 */
@@ -114,11 +127,10 @@
114 " SELECT e.value FROM setting s, jsonb_each(s.v) e"
115 " WHERE json_valid(s.v, 0x02)"
116 " )"
117 " SELECT r->>'label', r->>'value', r->>'description'"
118 " FROM room;"
119 /* "INSERT OR IGNORE INTO forumstatus(1,'Open','open','Open');" */
120 );
121 fses.n = (unsigned)db_int(0, "SELECT count(*) FROM forumstatus");
122 if( fses.n ){
123 int i = 0;
124 Stmt q;
@@ -2393,10 +2405,75 @@
2393 }
2394 if( i ){
2395 style_submenu_multichoice("status", i/2, az, 0);
2396 }
2397 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2398
2399 /*
2400 ** WEBPAGE: forummain
2401 ** WEBPAGE: forum
2402 **
@@ -2414,13 +2491,15 @@
2414 Stmt q;
2415 int iLimit = 0, iOfst, iCnt;
2416 int srchFlags;
2417 const int isSearch = P("s")!=0;
2418 const char *zStatusFilter = P("status");
2419 char const *zLimit = 0;
2420 int eStatusTag = 0;
2421 int bHasStatus = 0;
 
 
2422
2423 login_check_credentials();
2424 srchFlags = search_restrict(SRCH_FORUM);
2425 if( !g.perm.RdForum ){
2426 login_needed(g.anon.RdForum);
@@ -2465,103 +2544,66 @@
2465 }
2466 style_submenu_entry("n","Max:",4,0);
2467 forum_status_submenu();
2468 iOfst = atoi(PD("x","0"));
2469 iCnt = 0;
2470 if( zStatusFilter &&
2471 (!*zStatusFilter || 0==fossil_strcmp("*",zStatusFilter)) ){
2472 zStatusFilter = 0;
 
 
 
2473 }
2474 if( db_table_exists("repository","forumpost") ){
2475 const ForumStatusList * pFstat = forum_statuses();
2476 const int iStatusTagId = bHasStatus
2477 ? db_int(0, "SELECT tagid FROM TAG WHERE tagname='status'")
2478 : 0;
2479 Stmt qStat = empty_Stmt;
2480 db_multi_exec(
2481 /* List of forumpost.fpid values which match current
2482 filters. */
2483 "CREATE TEMP TABLE trootid(id INTEGER PRIMARY KEY);"
2484 );
2485 if( bHasStatus ){
2486 db_prepare(
2487 /* FIXME/TODO: it would be nice to be able to get this info
2488 ** directly into the thread query below, rather than
2489 ** requiring a separate statement and lookup inside the
2490 ** q loop. */
2491 &qStat,
2492 "SELECT tagxref.value, forumstatus.label\n"
2493 " FROM forumstatus, tagxref\n"
2494 " WHERE tagid=%d AND tagtype>=1\n"
2495 " AND forumstatus.value=tagxref.value\n"
2496 " AND rid=:rid\n"
2497 " AND if(%d=0,1,tagxref.value=%Q)\n"
2498 " ORDER BY mtime DESC",
2499 iStatusTagId, !!zStatusFilter, zStatusFilter
2500 );
2501 if( zStatusFilter ){
2502 const int bIsDflt =
2503 0==fossil_strcmp(pFstat->aStatus[0].zValue, zStatusFilter);
2504 db_multi_exec(
2505 "WITH stati(val) AS (SELECT value FROM forumstatus)\n"
2506 "INSERT INTO trootid\n"
2507 /* Rules:
2508
2509 (1) Filter on status=$zStatusFilter
2510 (2) If $zStatusFilter==default status then also include
2511 any posts with no status tag.
2512 (3) If no status tag is found for a given post, assume a tag
2513 with the value of the first (default) status.
2514 (4) If a status value is found which is not in
2515 [forumstatus] then treat it as if it were the default
2516 value.
2517
2518 We need to ensure that we filter only the most recent
2519 value of each tag and count tagtype=0 (cancel) tags
2520 properly.
2521 */
2522 " SELECT fpid FROM forumpost\n"
2523 " LEFT JOIN tagxref x ON fpid=rid\n"
2524 " WHERE froot=fpid AND firt IS NULL\n"
2525 " AND (x.tagtype IS NULL OR x.tagtype>0)\n"
2526 " AND (x.tagid IS NULL OR x.tagid=%d)\n"
2527 " AND CASE\n"
2528 " WHEN %d" /*bIsDflt*/
2529 " THEN ("
2530 " x.value IS NULL"
2531 " OR x.value=%Q"
2532 " OR x.value NOT IN stati" /* (4) */
2533 " )\n" /* (2,3,4) */
2534 " ELSE x.value=%Q\n" /* (1) */
2535 " END",
2536 iStatusTagId, bIsDflt, zStatusFilter, zStatusFilter
2537 );
2538 (void)bIsDflt;
2539 }else{
2540 db_multi_exec(
2541 "INSERT INTO trootid\n"
2542 " SELECT fpid FROM forumpost\n"
2543 " WHERE froot=fpid AND firt IS NULL"
2544 );
2545 }
2546 }else{
2547 db_multi_exec(
2548 "INSERT INTO trootid\n"
2549 " SELECT fpid FROM forumpost\n"
2550 " WHERE froot=fpid AND firt IS NULL"
2551 );
2552 }
2553 db_prepare(&q,
2554 "WITH thread(root,endtime,lastrid) AS (\n"
2555 " SELECT\n"
2556 " froot,\n"
2557 " max(fmtime),\n"
2558 " fpid\n"
2559 " FROM forumpost, trootid\n"
2560 " WHERE %s/*ModForum*/\n"
2561 " AND froot=trootid.id\n"
2562 " GROUP BY froot\n"
 
2563 " ORDER BY 2 DESC\n"
2564 " LIMIT %d OFFSET %d\n"
2565 ")\n"
2566 "SELECT\n"
2567 " julianday('now') - thread.endtime,\n" /* 0 */
@@ -2586,36 +2628,32 @@
2586 const char *zUuid;
2587 const char *zTitle;
2588 const char *zStatus;
2589 const char *zStatusLbl;
2590 const int bShowStatus = bHasStatus && !zStatusFilter;
2591 const int nCols = bShowStatus ? 4 : 3
2592 /* When filtering on status, elide the status column */;
2593 if( qStat.pStmt ){
 
2594 db_reset(&qStat);
2595 db_bind_int(&qStat, ":rid", db_column_int(&q,6));
2596 if( db_step(&qStat)==SQLITE_ROW ){
2597 zStatus = db_column_text(&qStat, 0);
2598 zStatusLbl = db_column_text(&qStat, 1);
2599 }else{
2600 zStatus = pFstat->aStatus[0].zValue;
2601 zStatusLbl = pFstat->aStatus[0].zLabel;
2602 }
2603 if( zStatusFilter && 0!=fossil_strcmp(zStatusFilter,zStatus) ){
2604 continue;
2605 }
2606 }else{
2607 zStatus = zStatusLbl = NULL;
2608 }
2609 zAge = human_readable_age(db_column_double(&q,0));
2610 nMsg = db_column_int(&q, 2);
2611 zUuid = db_column_text(&q, 3);
2612 zTitle = db_column_text(&q, 4);
2613 if( iCnt==0 ){
2614 char * zTail = zStatusFilter
2615 ? mprintf(" with status=%Q", zStatusFilter)
2616 : 0;
2617 if( iOfst>0 ){
2618 @ <h1>Threads at least %s(zAge) old%h(zTail ? zTail : "")</h1>
2619 }else{
2620 @ <h1>Most recent threads%h(zTail ? zTail : "")</h1>
2621 }
@@ -2623,18 +2661,18 @@
2623 @ <div class='forumPosts fileage'><table width="100%%">
2624 if( iOfst>0 ){
2625 if( iOfst>iLimit ){
2626 @ <tr><td colspan="%d(nCols)">\
2627 @ <a href='%R/forum?x=%d(iOfst-iLimit)&n=%d(iLimit) \
2628 if( zStatusFilter ){
2629 @ &status=%T(zStatusFilter)\
2630 }
2631 @ '>&uarr; Newer...</a></td></tr>
2632 }else{
2633 @ <tr><td colspan="%d(nCols)">\
2634 @ <a href='%R/forum?n=%d(iLimit)\
2635 if( zStatusFilter ){
2636 @ &status=%T(zStatusFilter) \
2637 }
2638 @ '>&uarr; Newer...</a></td></tr>
2639 }
2640 }
@@ -2641,11 +2679,11 @@
2641 }
2642 iCnt++;
2643 if( iCnt>iLimit ){
2644 @ <tr><td colspan="%d(nCols)">\
2645 @ <a href='%R/forum?x=%d(iOfst+iLimit)&n=%d(iLimit) \
2646 if( zStatusFilter ){
2647 @ &status=%T(zStatusFilter)\
2648 }
2649 @ '>&darr; Older...</a></td></tr>
2650 fossil_free(zAge);
2651 break;
@@ -2678,10 +2716,11 @@
2678 @</tr>
2679 fossil_free(zAge);
2680 }
2681 db_finalize(&q);
2682 if( qStat.pStmt ) db_finalize(&qStat);
 
2683 }
2684 if( iCnt>0 ){
2685 @ </table></div>
2686 }else{
2687 @ <h1>No forum posts found</h1>
2688
--- src/forum.c
+++ src/forum.c
@@ -75,14 +75,27 @@
75
76 /*
77 ** A list of ForumStatus objects.
78 */
79 struct ForumStatusList {
80 struct ForumStatus *aStatus; /* List of statuses */
81 unsigned int n; /* Number of entries */
82 };
83
84 /*
85 ** Information passed into the status_match() SQL function
86 ** via the sqlite3_user_data() mechanism, and used by status_match()
87 ** to determine whether or not a particular forum thread should
88 ** be displayed.
89 */
90 struct ForumStatusMatch {
91 const ForumStatusList *pFses; /* Parsed forum-statuses setting */
92 int eStatusTag; /* tagid for the "status" property */
93 unsigned int iMatch; /* Match this status value */
94 };
95 #endif /* INTERFACE */
96
97
98 /*
99 ** Returns a high-level representation of the forum-statuses setting.
100 ** This is a singleton, cached across calls.
101 */
@@ -114,11 +127,10 @@
127 " SELECT e.value FROM setting s, jsonb_each(s.v) e"
128 " WHERE json_valid(s.v, 0x02)"
129 " )"
130 " SELECT r->>'label', r->>'value', r->>'description'"
131 " FROM room;"
 
132 );
133 fses.n = (unsigned)db_int(0, "SELECT count(*) FROM forumstatus");
134 if( fses.n ){
135 int i = 0;
136 Stmt q;
@@ -2393,10 +2405,75 @@
2405 }
2406 if( i ){
2407 style_submenu_multichoice("status", i/2, az, 0);
2408 }
2409 }
2410
2411 /*
2412 ** Transient SQL Function: status_match(FROOT)
2413 **
2414 ** Return true if the forum thread identified by FROOT should be included
2415 ** in a list of threads. Used to implement the status=NAME query parameter
2416 ** on /forum.
2417 **
2418 ** The result of this routine depends on the content of the
2419 ** ForumStatusMatch *pMData object that is available via sqlite3_user_data().
2420 **
2421 ** * If pMData==NULL, always return true. This means that no
2422 ** filtering of threads is being done. This is the common case.
2423 **
2424 ** * If FROOT contains a status property value that matches
2425 ** pMData->iMatch, return true.
2426 **
2427 ** * if pMData->iMatch==0 (meaning we want to match the default
2428 ** status value) and if the FROOT thread contains a status that
2429 ** is not on the list of statuses or if FROOT has no statue
2430 ** property at all, then return true. In other words, a forum
2431 ** thread with no status property or an unknown status property
2432 ** is treated as if it had the default status.
2433 **
2434 ** * Otherwise, return false.
2435 */
2436 static void forum_status_match(
2437 sqlite3_context *context,
2438 int argc,
2439 sqlite3_value **argv
2440 ){
2441 static Stmt q;
2442 ForumStatusMatch *pMData = sqlite3_user_data(context);
2443 int i;
2444
2445 if( pMData==0 ){
2446 sqlite3_result_int(context, 1);
2447 return;
2448 }
2449 db_static_prepare(&q,
2450 "SELECT value FROM tagxref\n"
2451 " WHERE tagid=%d\n"
2452 " AND tagtype>=1\n"
2453 " AND rid=:rid\n"
2454 " ORDER BY mtime DESC LIMIT 1",
2455 pMData->eStatusTag
2456 );
2457 db_bind_int(&q, ":rid", sqlite3_value_int(argv[0]));
2458 if( db_step(&q)==SQLITE_ROW ){
2459 const char *zValue = (const char*)db_column_text(&q,0);
2460 const ForumStatusList *pFses = pMData->pFses;
2461 if( zValue==0 ){
2462 i = 0;
2463 }else{
2464 for(i=0; i<pFses->n; i++){
2465 if( fossil_strcmp(pFses->aStatus[i].zValue,zValue)==0 ) break;
2466 }
2467 }
2468 if( i>=pMData->pFses->n ) i = 0;
2469 }else{
2470 i = 0;
2471 }
2472 db_reset(&q);
2473 sqlite3_result_int(context, i==pMData->iMatch);
2474 }
2475
2476 /*
2477 ** WEBPAGE: forummain
2478 ** WEBPAGE: forum
2479 **
@@ -2414,13 +2491,15 @@
2491 Stmt q;
2492 int iLimit = 0, iOfst, iCnt;
2493 int srchFlags;
2494 const int isSearch = P("s")!=0;
2495 const char *zStatusFilter = P("status");
2496 char const *zLimit = 0; /* Value of the n= query parameter */
2497 int eStatusTag = 0; /* tagid for the "status" property */
2498 int bHasStatus = 0; /* True if forum-statuses setting exists */
2499 int bFilter = 0; /* True if status=NAME query parameter */
2500 ForumStatusMatch sFSM; /* Aux data to status_match() SQL function */
2501
2502 login_check_credentials();
2503 srchFlags = search_restrict(SRCH_FORUM);
2504 if( !g.perm.RdForum ){
2505 login_needed(g.anon.RdForum);
@@ -2465,103 +2544,66 @@
2544 }
2545 style_submenu_entry("n","Max:",4,0);
2546 forum_status_submenu();
2547 iOfst = atoi(PD("x","0"));
2548 iCnt = 0;
2549 if( zStatusFilter ){
2550 if( zStatusFilter[0]==0 || 0==fossil_strcmp("*",zStatusFilter) ){
2551 zStatusFilter = 0;
2552 }else{
2553 bFilter = bHasStatus;
2554 }
2555 }
2556 if( db_table_exists("repository","forumpost") ){
2557 const ForumStatusList *pFstat = forum_statuses();
2558 Stmt qStat = empty_Stmt; /* Query to get status information */
 
 
 
 
 
 
 
 
2559 if( bHasStatus ){
2560 /* The qStat query runs once for each output row generate by the
2561 ** q query. It determines the value and label of the status for
2562 ** the row with froot=:rowid
2563 */
2564 db_prepare(&qStat,
 
2565 "SELECT tagxref.value, forumstatus.label\n"
2566 " FROM forumstatus, tagxref\n"
2567 " WHERE tagid=%d AND tagtype>=1\n"
2568 " AND forumstatus.value=tagxref.value\n"
2569 " AND rid=:rid\n"
2570 " ORDER BY mtime DESC",
2571 eStatusTag
2572 );
2573 }
2574
2575 /* Create the status_match() SQL function that will determine
2576 ** whether or not each thread in the "q" query below is eligible
2577 ** for display
2578 */
2579 if( bFilter ){
2580 sFSM.pFses = pFstat;
2581 sFSM.eStatusTag = eStatusTag;
2582 for(sFSM.iMatch=0; sFSM.iMatch<pFstat->n; sFSM.iMatch++){
2583 if( 0==fossil_strcmp(zStatusFilter,
2584 pFstat->aStatus[sFSM.iMatch].zValue) ){
2585 break;
2586 }
2587 }
2588 sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,(void*)&sFSM,
2589 forum_status_match, 0, 0);
2590 }else{
2591 sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,
2592 forum_status_match, 0, 0);
2593 }
2594
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2595 db_prepare(&q,
2596 "WITH thread(root,endtime,lastrid) AS (\n"
2597 " SELECT\n"
2598 " froot,\n"
2599 " max(fmtime),\n"
2600 " fpid\n"
2601 " FROM forumpost\n"
2602 " WHERE %s/*ModForum*/\n"
 
2603 " GROUP BY froot\n"
2604 " HAVING status_match(froot)\n"
2605 " ORDER BY 2 DESC\n"
2606 " LIMIT %d OFFSET %d\n"
2607 ")\n"
2608 "SELECT\n"
2609 " julianday('now') - thread.endtime,\n" /* 0 */
@@ -2586,36 +2628,32 @@
2628 const char *zUuid;
2629 const char *zTitle;
2630 const char *zStatus;
2631 const char *zStatusLbl;
2632 const int bShowStatus = bHasStatus && !zStatusFilter;
2633 const int nCols = bShowStatus ? 4 : 3;
2634
2635 if( qStat.pStmt ){
2636 /* Determine the status value for this row */
2637 db_reset(&qStat);
2638 db_bind_int(&qStat, ":rid", db_column_int(&q,6));
2639 if( db_step(&qStat)==SQLITE_ROW ){
2640 zStatus = db_column_text(&qStat, 0);
2641 zStatusLbl = db_column_text(&qStat, 1);
2642 }else{
2643 zStatus = pFstat->aStatus[0].zValue;
2644 zStatusLbl = pFstat->aStatus[0].zLabel;
2645 }
 
 
 
2646 }else{
2647 zStatus = zStatusLbl = NULL;
2648 }
2649 zAge = human_readable_age(db_column_double(&q,0));
2650 nMsg = db_column_int(&q, 2);
2651 zUuid = db_column_text(&q, 3);
2652 zTitle = db_column_text(&q, 4);
2653 if( iCnt==0 ){
2654 char *zTail = bFilter ? mprintf(" with status=%Q", zStatusFilter): 0;
 
 
2655 if( iOfst>0 ){
2656 @ <h1>Threads at least %s(zAge) old%h(zTail ? zTail : "")</h1>
2657 }else{
2658 @ <h1>Most recent threads%h(zTail ? zTail : "")</h1>
2659 }
@@ -2623,18 +2661,18 @@
2661 @ <div class='forumPosts fileage'><table width="100%%">
2662 if( iOfst>0 ){
2663 if( iOfst>iLimit ){
2664 @ <tr><td colspan="%d(nCols)">\
2665 @ <a href='%R/forum?x=%d(iOfst-iLimit)&n=%d(iLimit) \
2666 if( bFilter ){
2667 @ &status=%T(zStatusFilter)\
2668 }
2669 @ '>&uarr; Newer...</a></td></tr>
2670 }else{
2671 @ <tr><td colspan="%d(nCols)">\
2672 @ <a href='%R/forum?n=%d(iLimit)\
2673 if( bFilter ){
2674 @ &status=%T(zStatusFilter) \
2675 }
2676 @ '>&uarr; Newer...</a></td></tr>
2677 }
2678 }
@@ -2641,11 +2679,11 @@
2679 }
2680 iCnt++;
2681 if( iCnt>iLimit ){
2682 @ <tr><td colspan="%d(nCols)">\
2683 @ <a href='%R/forum?x=%d(iOfst+iLimit)&n=%d(iLimit) \
2684 if( bFilter ){
2685 @ &status=%T(zStatusFilter)\
2686 }
2687 @ '>&darr; Older...</a></td></tr>
2688 fossil_free(zAge);
2689 break;
@@ -2678,10 +2716,11 @@
2716 @</tr>
2717 fossil_free(zAge);
2718 }
2719 db_finalize(&q);
2720 if( qStat.pStmt ) db_finalize(&qStat);
2721 sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,0,0,0);
2722 }
2723 if( iCnt>0 ){
2724 @ </table></div>
2725 }else{
2726 @ <h1>No forum posts found</h1>
2727

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button