| | @@ -535,139 +535,184 @@ |
| 535 | 535 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 536 | 536 | blob_reset(&sql); |
| 537 | 537 | print_timeline(&q, nLimit, width, 0); |
| 538 | 538 | db_finalize(&q); |
| 539 | 539 | } |
| 540 | + |
| 541 | +#if INTERFACE |
| 542 | +/* What to search for */ |
| 543 | +#define SRCH_CKIN 0x0001 /* Search over check-in comments */ |
| 544 | +#define SRCH_DOC 0x0002 /* Search over embedded documents */ |
| 545 | +#define SRCH_TKT 0x0004 /* Search over tickets */ |
| 546 | +#define SRCH_WIKI 0x0008 /* Search over wiki */ |
| 547 | +#define SRCH_ALL 0x000f /* Search over everything */ |
| 548 | +#endif |
| 549 | + |
| 550 | +/* |
| 551 | +** Remove bits from srchFlags which are disallowed by either the |
| 552 | +** current server configuration or by user permissions. |
| 553 | +*/ |
| 554 | +unsigned int search_restrict(unsigned int srchFlags){ |
| 555 | + if( (srchFlags & SRCH_CKIN)!=0 |
| 556 | + && (g.perm.Read==0 || db_get_boolean("search-ci",0)==0) ){ |
| 557 | + srchFlags &= ~SRCH_CKIN; |
| 558 | + } |
| 559 | + if( (srchFlags & SRCH_DOC)!=0 |
| 560 | + && (g.perm.Read==0 || db_get_boolean("search-doc",0)==0) ){ |
| 561 | + srchFlags &= ~SRCH_DOC; |
| 562 | + } |
| 563 | + if( (srchFlags & SRCH_TKT)!=0 |
| 564 | + && (g.perm.RdTkt==0 || db_get_boolean("search-tkt",0)==0) ){ |
| 565 | + srchFlags &= ~SRCH_TKT; |
| 566 | + } |
| 567 | + if( (srchFlags & SRCH_WIKI)!=0 |
| 568 | + && (g.perm.RdWiki==0 || db_get_boolean("search-wiki",0)==0) ){ |
| 569 | + srchFlags &= ~SRCH_WIKI; |
| 570 | + } |
| 571 | + return srchFlags; |
| 572 | +} |
| 573 | + |
| 574 | +/* |
| 575 | +** This routine generates web-page output for a search operation. |
| 576 | +** Other web-pages can invoke this routine to add search results |
| 577 | +** in the middle of the page. |
| 578 | +** |
| 579 | +** Return the number of rows. |
| 580 | +*/ |
| 581 | +int search_run_and_output( |
| 582 | + const char *zPattern, /* The query pattern */ |
| 583 | + unsigned int srchFlags /* What to search over */ |
| 584 | +){ |
| 585 | + Stmt q; |
| 586 | + int nRow = 0; |
| 587 | + |
| 588 | + srchFlags = search_restrict(srchFlags); |
| 589 | + if( srchFlags==0 ) return 0; |
| 590 | + search_sql_setup(g.db); |
| 591 | + add_content_sql_commands(g.db); |
| 592 | + search_init(zPattern, "<b>", "</b>", " ... ", |
| 593 | + SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 594 | + db_multi_exec( |
| 595 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 596 | + "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" |
| 597 | + ); |
| 598 | + if( (srchFlags & SRCH_DOC)!=0 ){ |
| 599 | + char *zDocGlob = db_get("doc-glob",""); |
| 600 | + char *zDocBr = db_get("doc-branch","trunk"); |
| 601 | + if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 602 | + db_multi_exec( |
| 603 | + "INSERT INTO x(label,url,date,snip)" |
| 604 | + " SELECT printf('Document: %%s',foci.filename)," |
| 605 | + " printf('%R/doc/%T/%%s',foci.filename)," |
| 606 | + " (SELECT datetime(event.mtime) FROM event" |
| 607 | + " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 608 | + " snippet(stext('d',blob.rid,foci.filename))" |
| 609 | + " FROM foci CROSS JOIN blob" |
| 610 | + " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 611 | + " AND blob.uuid=foci.uuid" |
| 612 | + " AND %z", |
| 613 | + zDocBr, glob_expr("foci.filename", zDocGlob) |
| 614 | + ); |
| 615 | + } |
| 616 | + } |
| 617 | + if( (srchFlags & SRCH_WIKI)!=0 ){ |
| 618 | + db_multi_exec( |
| 619 | + "WITH wiki(name,rid,mtime) AS (" |
| 620 | + " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" |
| 621 | + " FROM tag, tagxref" |
| 622 | + " WHERE tag.tagname GLOB 'wiki-*'" |
| 623 | + " AND tagxref.tagid=tag.tagid" |
| 624 | + " GROUP BY 1" |
| 625 | + ")" |
| 626 | + "INSERT INTO x(label,url,date,snip)" |
| 627 | + " SELECT printf('Wiki: %%s',name)," |
| 628 | + " printf('%R/wiki?name=%%s',urlencode(name))," |
| 629 | + " datetime(mtime)," |
| 630 | + " snippet(stext('w',rid,name))" |
| 631 | + " FROM wiki;" |
| 632 | + ); |
| 633 | + } |
| 634 | + if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 635 | + db_multi_exec( |
| 636 | + "WITH ckin(uuid,rid,mtime) AS (" |
| 637 | + " SELECT blob.uuid, event.objid, event.mtime" |
| 638 | + " FROM event, blob" |
| 639 | + " WHERE event.type='ci'" |
| 640 | + " AND blob.rid=event.objid" |
| 641 | + ")" |
| 642 | + "INSERT INTO x(label,url,date,snip)" |
| 643 | + " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 644 | + " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 645 | + " datetime(mtime)," |
| 646 | + " snippet(stext('c',rid,NULL))" |
| 647 | + " FROM ckin;" |
| 648 | + ); |
| 649 | + } |
| 650 | + if( (srchFlags & SRCH_TKT)!=0 ){ |
| 651 | + db_multi_exec( |
| 652 | + "INSERT INTO x(label,url,date,snip)" |
| 653 | + " SELECT printf('Ticket [%%.17s] on %%s'," |
| 654 | + "tkt_uuid,datetime(tkt_mtime))," |
| 655 | + " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 656 | + " datetime(tkt_mtime)," |
| 657 | + " snippet(stext('t',tkt_id,NULL))" |
| 658 | + " FROM ticket;" |
| 659 | + ); |
| 660 | + } |
| 661 | + db_prepare(&q, "SELECT url, substr(snip,9), label" |
| 662 | + " FROM x WHERE snip IS NOT NULL" |
| 663 | + " ORDER BY substr(snip,1,8) DESC, date DESC;"); |
| 664 | + while( db_step(&q)==SQLITE_ROW ){ |
| 665 | + const char *zUrl = db_column_text(&q, 0); |
| 666 | + const char *zSnippet = db_column_text(&q, 1); |
| 667 | + const char *zLabel = db_column_text(&q, 2); |
| 668 | + if( nRow==0 ){ |
| 669 | + @ <ol> |
| 670 | + } |
| 671 | + nRow++; |
| 672 | + @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li> |
| 673 | + } |
| 674 | + db_finalize(&q); |
| 675 | + if( nRow ){ |
| 676 | + @ </ol> |
| 677 | + } |
| 678 | + return nRow; |
| 679 | +} |
| 540 | 680 | |
| 541 | 681 | /* |
| 542 | 682 | ** WEBPAGE: /search |
| 543 | 683 | ** |
| 544 | | -** This is an EXPERIMENTAL page for doing search across a repository. |
| 545 | | -** |
| 546 | | -** The current implementation does a full text search over embedded |
| 547 | | -** documentation files on the tip of the "trunk" branch. Only files |
| 548 | | -** ending in ".wiki", ".md", ".html", and ".txt" are searched. |
| 549 | | -** |
| 550 | | -** The entire text is scanned. There is no full-text index. This is |
| 551 | | -** experimental. We may change to using a full-text index depending |
| 552 | | -** on performance. |
| 553 | | -** |
| 554 | | -** Other pending enhancements: |
| 555 | | -** * Search tickets |
| 556 | | -** * Search wiki |
| 684 | +** Search for check-in comments, documents, tickets, or wiki that |
| 685 | +** match a user-supplied pattern. |
| 557 | 686 | */ |
| 558 | 687 | void search_page(void){ |
| 559 | 688 | const char *zPattern = PD("s",""); |
| 560 | | - Stmt q; |
| 561 | | - int okCheckin; |
| 562 | | - int okDoc; |
| 563 | | - int okTicket; |
| 564 | | - int okWiki; |
| 565 | | - int allOff; |
| 689 | + unsigned srchFlags = 0; |
| 566 | 690 | const char *zDisable; |
| 567 | 691 | |
| 568 | 692 | login_check_credentials(); |
| 569 | | - okCheckin = g.perm.Read && db_get_boolean("search-ci",0); |
| 570 | | - okDoc = g.perm.Read && db_get_boolean("search-doc",0); |
| 571 | | - okTicket = g.perm.RdTkt && db_get_boolean("search-tkt",0); |
| 572 | | - okWiki = g.perm.RdWiki && db_get_boolean("search-wiki",0); |
| 573 | | - allOff = (okCheckin + okDoc + okTicket + okWiki == 0); |
| 574 | | - zDisable = allOff ? " disabled" : ""; |
| 575 | | - zPattern = allOff ? "" : PD("s",""); |
| 693 | + srchFlags = search_restrict(SRCH_ALL); |
| 694 | + if( srchFlags==0 ){ |
| 695 | + zDisable = " disabled"; |
| 696 | + zPattern = ""; |
| 697 | + }else{ |
| 698 | + zDisable = ""; |
| 699 | + zPattern = PD("s",""); |
| 700 | + } |
| 576 | 701 | style_header("Search"); |
| 577 | 702 | @ <form method="GET" action="search"><center> |
| 578 | 703 | @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable)> |
| 579 | 704 | @ <input type="submit" value="Search"%s(zDisable)> |
| 580 | | - if( allOff ){ |
| 705 | + if( srchFlags==0 ){ |
| 581 | 706 | @ <p class="generalError">Search is disabled</p> |
| 582 | 707 | } |
| 583 | 708 | @ </center></form> |
| 584 | 709 | while( fossil_isspace(zPattern[0]) ) zPattern++; |
| 585 | 710 | if( zPattern[0] ){ |
| 586 | | - search_sql_setup(g.db); |
| 587 | | - add_content_sql_commands(g.db); |
| 588 | | - search_init(zPattern, "<b>", "</b>", " ... ", |
| 589 | | - SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 590 | | - db_multi_exec( |
| 591 | | - "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 592 | | - "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" |
| 593 | | - ); |
| 594 | | - if( okDoc ){ |
| 595 | | - char *zDocGlob = db_get("doc-glob",""); |
| 596 | | - char *zDocBr = db_get("doc-branch","trunk"); |
| 597 | | - if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 598 | | - db_multi_exec( |
| 599 | | - "INSERT INTO x(label,url,date,snip)" |
| 600 | | - " SELECT printf('Document: %%s',foci.filename)," |
| 601 | | - " printf('%R/doc/%T/%%s',foci.filename)," |
| 602 | | - " (SELECT datetime(event.mtime) FROM event" |
| 603 | | - " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 604 | | - " snippet(stext('d',blob.rid,foci.filename))" |
| 605 | | - " FROM foci CROSS JOIN blob" |
| 606 | | - " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 607 | | - " AND blob.uuid=foci.uuid" |
| 608 | | - " AND %z", |
| 609 | | - zDocBr, glob_expr("foci.filename", zDocGlob) |
| 610 | | - ); |
| 611 | | - } |
| 612 | | - } |
| 613 | | - if( okWiki ){ |
| 614 | | - db_multi_exec( |
| 615 | | - "WITH wiki(name,rid,mtime) AS (" |
| 616 | | - " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" |
| 617 | | - " FROM tag, tagxref" |
| 618 | | - " WHERE tag.tagname GLOB 'wiki-*'" |
| 619 | | - " AND tagxref.tagid=tag.tagid" |
| 620 | | - " GROUP BY 1" |
| 621 | | - ")" |
| 622 | | - "INSERT INTO x(label,url,date,snip)" |
| 623 | | - " SELECT printf('Wiki: %%s',name)," |
| 624 | | - " printf('%R/wiki?name=%%s',urlencode(name))," |
| 625 | | - " datetime(mtime)," |
| 626 | | - " snippet(stext('w',rid,name))" |
| 627 | | - " FROM wiki;" |
| 628 | | - ); |
| 629 | | - } |
| 630 | | - if( okCheckin ){ |
| 631 | | - db_multi_exec( |
| 632 | | - "WITH ckin(uuid,rid,mtime) AS (" |
| 633 | | - " SELECT blob.uuid, event.objid, event.mtime" |
| 634 | | - " FROM event, blob" |
| 635 | | - " WHERE event.type='ci'" |
| 636 | | - " AND blob.rid=event.objid" |
| 637 | | - ")" |
| 638 | | - "INSERT INTO x(label,url,date,snip)" |
| 639 | | - " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 640 | | - " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 641 | | - " datetime(mtime)," |
| 642 | | - " snippet(stext('c',rid,NULL))" |
| 643 | | - " FROM ckin;" |
| 644 | | - ); |
| 645 | | - } |
| 646 | | - if( okTicket ){ |
| 647 | | - db_multi_exec( |
| 648 | | - "INSERT INTO x(label,url,date,snip)" |
| 649 | | - " SELECT printf('Ticket [%%.17s] on %%s'," |
| 650 | | - "tkt_uuid,datetime(tkt_mtime))," |
| 651 | | - " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 652 | | - " datetime(tkt_mtime)," |
| 653 | | - " snippet(stext('t',tkt_id,NULL))" |
| 654 | | - " FROM ticket;" |
| 655 | | - ); |
| 656 | | - } |
| 657 | | - db_prepare(&q, "SELECT url, substr(snip,9), label" |
| 658 | | - " FROM x WHERE snip IS NOT NULL" |
| 659 | | - " ORDER BY substr(snip,1,8) DESC, date DESC;"); |
| 660 | | - @ <ol> |
| 661 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 662 | | - const char *zUrl = db_column_text(&q, 0); |
| 663 | | - const char *zSnippet = db_column_text(&q, 1); |
| 664 | | - const char *zLabel = db_column_text(&q, 2); |
| 665 | | - @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li> |
| 666 | | - } |
| 667 | | - db_finalize(&q); |
| 668 | | - @ </ol> |
| 711 | + if( search_run_and_output(zPattern, srchFlags)==0 ){ |
| 712 | + @ <p><i>No matches for: "%h(zPattern)"</i></p> |
| 713 | + } |
| 669 | 714 | } |
| 670 | 715 | style_footer(); |
| 671 | 716 | } |
| 672 | 717 | |
| 673 | 718 | |
| 674 | 719 | |