Diff
Not logged in

Differences From Artifact [c23f0f646a]:

To Artifact [e2a6349e2d]:


533
534
535
536
537
538
539












































































































































540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575

576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
  }
  blob_append(&sql, "ORDER BY x DESC, date DESC ", -1);
  db_prepare(&q, "%s", blob_sql_text(&sql));
  blob_reset(&sql);
  print_timeline(&q, nLimit, width, 0);
  db_finalize(&q);
}













































































































































/*
** WEBPAGE: /search
**
** This is an EXPERIMENTAL page for doing search across a repository.
**
** The current implementation does a full text search over embedded
** documentation files on the tip of the "trunk" branch.  Only files
** ending in ".wiki", ".md", ".html", and ".txt" are searched.
**
** The entire text is scanned.  There is no full-text index.  This is
** experimental.  We may change to using a full-text index depending
** on performance.
**
** Other pending enhancements:
**    *   Search tickets
**    *   Search wiki
*/
void search_page(void){
  const char *zPattern = PD("s","");
  Stmt q;
  int okCheckin;
  int okDoc;
  int okTicket;
  int okWiki;
  int allOff;
  const char *zDisable;

  login_check_credentials();
  okCheckin = g.perm.Read && db_get_boolean("search-ci",0);
  okDoc = g.perm.Read && db_get_boolean("search-doc",0);
  okTicket = g.perm.RdTkt && db_get_boolean("search-tkt",0);
  okWiki = g.perm.RdWiki && db_get_boolean("search-wiki",0);
  allOff = (okCheckin + okDoc + okTicket + okWiki == 0);
  zDisable = allOff ? " disabled" : "";
  zPattern = allOff ? "" : PD("s","");

  style_header("Search");
  @ <form method="GET" action="search"><center>
  @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable)>
  @ <input type="submit" value="Search"%s(zDisable)>
  if( allOff ){
    @ <p class="generalError">Search is disabled</p>
  }
  @ </center></form>
  while( fossil_isspace(zPattern[0]) ) zPattern++;
  if( zPattern[0] ){
    search_sql_setup(g.db);
    add_content_sql_commands(g.db);
    search_init(zPattern, "<b>", "</b>", " ... ",
            SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE);
    db_multi_exec(
      "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
      "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);"
    );
    if( okDoc ){
      char *zDocGlob = db_get("doc-glob","");
      char *zDocBr = db_get("doc-branch","trunk");
      if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
        db_multi_exec(
          "INSERT INTO x(label,url,date,snip)"
          "  SELECT printf('Document: %%s',foci.filename),"
          "         printf('%R/doc/%T/%%s',foci.filename),"
          "         (SELECT datetime(event.mtime) FROM event"
          "            WHERE objid=symbolic_name_to_rid('trunk')),"
          "         snippet(stext('d',blob.rid,foci.filename))"
          "    FROM foci CROSS JOIN blob"
          "   WHERE checkinID=symbolic_name_to_rid('trunk')"
          "     AND blob.uuid=foci.uuid"
          "     AND %z",
          zDocBr, glob_expr("foci.filename", zDocGlob)
        );
      }
    }
    if( okWiki ){
      db_multi_exec(
        "WITH wiki(name,rid,mtime) AS ("
        "  SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)"
        "    FROM tag, tagxref"
        "   WHERE tag.tagname GLOB 'wiki-*'"
        "     AND tagxref.tagid=tag.tagid"
        "   GROUP BY 1"
        ")"
        "INSERT INTO x(label,url,date,snip)"
        "  SELECT printf('Wiki: %%s',name),"
        "         printf('%R/wiki?name=%%s',urlencode(name)),"
        "         datetime(mtime),"
        "         snippet(stext('w',rid,name))"
        "    FROM wiki;"
      );
    }
    if( okCheckin ){
      db_multi_exec(
        "WITH ckin(uuid,rid,mtime) AS ("
        "  SELECT blob.uuid, event.objid, event.mtime"
        "    FROM event, blob"
        "   WHERE event.type='ci'"
        "     AND blob.rid=event.objid"
        ")"
        "INSERT INTO x(label,url,date,snip)"
        "  SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
        "         printf('%R/timeline?c=%%s&n=8&y=ci',uuid),"
        "         datetime(mtime),"
        "         snippet(stext('c',rid,NULL))"
        "    FROM ckin;"
      );
    }
    if( okTicket ){
      db_multi_exec(
        "INSERT INTO x(label,url,date,snip)"
        "  SELECT printf('Ticket [%%.17s] on %%s',"
                        "tkt_uuid,datetime(tkt_mtime)),"
        "         printf('%R/tktview/%%.20s',tkt_uuid),"
        "         datetime(tkt_mtime),"
        "         snippet(stext('t',tkt_id,NULL))"
        "    FROM ticket;"
      );
    }
    db_prepare(&q, "SELECT url, substr(snip,9), label"
                   "   FROM x WHERE snip IS NOT NULL"
                   " ORDER BY substr(snip,1,8) DESC, date DESC;");
    @ <ol>
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUrl = db_column_text(&q, 0);
      const char *zSnippet = db_column_text(&q, 1);
      const char *zLabel = db_column_text(&q, 2);
      @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li>
    }
    db_finalize(&q);
    @ </ol>
  }
  style_footer();
}


/*
** This is a helper function for search_stext().  Writing into pOut







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




<
<
<
<
<
<
<
<
<
<
<
|
|



|
<
<
<
<
<



|
|
|
|
|
|
|
>




|





|
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683











684
685
686
687
688
689





690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711

712






















713

























































714
715
716
717
718
719
720
  }
  blob_append(&sql, "ORDER BY x DESC, date DESC ", -1);
  db_prepare(&q, "%s", blob_sql_text(&sql));
  blob_reset(&sql);
  print_timeline(&q, nLimit, width, 0);
  db_finalize(&q);
}

#if INTERFACE
/* What to search for */
#define SRCH_CKIN   0x0001    /* Search over check-in comments */
#define SRCH_DOC    0x0002    /* Search over embedded documents */
#define SRCH_TKT    0x0004    /* Search over tickets */
#define SRCH_WIKI   0x0008    /* Search over wiki */
#define SRCH_ALL    0x000f    /* Search over everything */
#endif

/*
** Remove bits from srchFlags which are disallowed by either the
** current server configuration or by user permissions.
*/
unsigned int search_restrict(unsigned int srchFlags){
  if( (srchFlags & SRCH_CKIN)!=0 
   && (g.perm.Read==0 || db_get_boolean("search-ci",0)==0) ){
    srchFlags &= ~SRCH_CKIN;
  }
  if( (srchFlags & SRCH_DOC)!=0 
   && (g.perm.Read==0 || db_get_boolean("search-doc",0)==0) ){
    srchFlags &= ~SRCH_DOC;
  }
  if( (srchFlags & SRCH_TKT)!=0 
   && (g.perm.RdTkt==0 || db_get_boolean("search-tkt",0)==0) ){
    srchFlags &= ~SRCH_TKT;
  }
  if( (srchFlags & SRCH_WIKI)!=0 
   && (g.perm.RdWiki==0 || db_get_boolean("search-wiki",0)==0) ){
    srchFlags &= ~SRCH_WIKI;
  }
  return srchFlags;
}

/*
** This routine generates web-page output for a search operation.
** Other web-pages can invoke this routine to add search results
** in the middle of the page.
**
** Return the number of rows.
*/
int search_run_and_output(
  const char *zPattern,       /* The query pattern */
  unsigned int srchFlags      /* What to search over */
){
  Stmt q;
  int nRow = 0;

  srchFlags = search_restrict(srchFlags);
  if( srchFlags==0 ) return 0;
  search_sql_setup(g.db);
  add_content_sql_commands(g.db);
  search_init(zPattern, "<b>", "</b>", " ... ",
          SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE);
  db_multi_exec(
    "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
    "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);"
  );
  if( (srchFlags & SRCH_DOC)!=0 ){
    char *zDocGlob = db_get("doc-glob","");
    char *zDocBr = db_get("doc-branch","trunk");
    if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
      db_multi_exec(
        "INSERT INTO x(label,url,date,snip)"
        "  SELECT printf('Document: %%s',foci.filename),"
        "         printf('%R/doc/%T/%%s',foci.filename),"
        "         (SELECT datetime(event.mtime) FROM event"
        "            WHERE objid=symbolic_name_to_rid('trunk')),"
        "         snippet(stext('d',blob.rid,foci.filename))"
        "    FROM foci CROSS JOIN blob"
        "   WHERE checkinID=symbolic_name_to_rid('trunk')"
        "     AND blob.uuid=foci.uuid"
        "     AND %z",
        zDocBr, glob_expr("foci.filename", zDocGlob)
      );
    }
  }
  if( (srchFlags & SRCH_WIKI)!=0 ){
    db_multi_exec(
      "WITH wiki(name,rid,mtime) AS ("
      "  SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)"
      "    FROM tag, tagxref"
      "   WHERE tag.tagname GLOB 'wiki-*'"
      "     AND tagxref.tagid=tag.tagid"
      "   GROUP BY 1"
      ")"
      "INSERT INTO x(label,url,date,snip)"
      "  SELECT printf('Wiki: %%s',name),"
      "         printf('%R/wiki?name=%%s',urlencode(name)),"
      "         datetime(mtime),"
      "         snippet(stext('w',rid,name))"
      "    FROM wiki;"
    );
  }
  if( (srchFlags & SRCH_CKIN)!=0 ){
    db_multi_exec(
      "WITH ckin(uuid,rid,mtime) AS ("
      "  SELECT blob.uuid, event.objid, event.mtime"
      "    FROM event, blob"
      "   WHERE event.type='ci'"
      "     AND blob.rid=event.objid"
      ")"
      "INSERT INTO x(label,url,date,snip)"
      "  SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
      "         printf('%R/timeline?c=%%s&n=8&y=ci',uuid),"
      "         datetime(mtime),"
      "         snippet(stext('c',rid,NULL))"
      "    FROM ckin;"
    );
  }
  if( (srchFlags & SRCH_TKT)!=0 ){
    db_multi_exec(
      "INSERT INTO x(label,url,date,snip)"
      "  SELECT printf('Ticket [%%.17s] on %%s',"
                      "tkt_uuid,datetime(tkt_mtime)),"
      "         printf('%R/tktview/%%.20s',tkt_uuid),"
      "         datetime(tkt_mtime),"
      "         snippet(stext('t',tkt_id,NULL))"
      "    FROM ticket;"
    );
  }
  db_prepare(&q, "SELECT url, substr(snip,9), label"
                 "   FROM x WHERE snip IS NOT NULL"
                 " ORDER BY substr(snip,1,8) DESC, date DESC;");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUrl = db_column_text(&q, 0);
    const char *zSnippet = db_column_text(&q, 1);
    const char *zLabel = db_column_text(&q, 2);
    if( nRow==0 ){
      @ <ol>
    }
    nRow++;
    @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li>
  }
  db_finalize(&q);
  if( nRow ){
    @ </ol>
  }
  return nRow;
}

/*
** WEBPAGE: /search
**











** Search for check-in comments, documents, tickets, or wiki that
** match a user-supplied pattern.
*/
void search_page(void){
  const char *zPattern = PD("s","");
  unsigned srchFlags = 0;





  const char *zDisable;

  login_check_credentials();
  srchFlags = search_restrict(SRCH_ALL);
  if( srchFlags==0 ){
    zDisable = " disabled";
    zPattern = "";
  }else{
    zDisable = "";
    zPattern = PD("s","");
  }
  style_header("Search");
  @ <form method="GET" action="search"><center>
  @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable)>
  @ <input type="submit" value="Search"%s(zDisable)>
  if( srchFlags==0 ){
    @ <p class="generalError">Search is disabled</p>
  }
  @ </center></form>
  while( fossil_isspace(zPattern[0]) ) zPattern++;
  if( zPattern[0] ){
    if( search_run_and_output(zPattern, srchFlags)==0 ){

      @ <p><i>No matches for: "%h(zPattern)"</i></p>






















    }

























































  }
  style_footer();
}


/*
** This is a helper function for search_stext().  Writing into pOut