Improving XWiki’s Search

Published by Patrick on in Tips and Tricks

The XWiki.org site integrates—and has for a while—a search that’s different from the standard one. It offers users the ability of restricting a search to a specific space instead of searching all spaces at once. Here at Encodo, we’ve been keeping our weekly reports in the wiki for years and these reports have continuously muddied our search results. The new search is a great improvement over the original.

Everything you see in XWiki is editable in one way or another and that includes the search page/panel. We’re going to replace some of these standard pages with our own code.

The Search Form

First we’ll create a page for the new search (Textbox etc.). To accomplish that, point your browser to http://yourwiki/xwiki/bin/edit/Main/NewSearch and paste the following lines into the editor:

1 Search

#if(!$request.space)
  #set($space = "All")
#else
  #set($space = $request.space)
#end

#set($spacesText = {})
#set($spaces = $xwiki.spaces)
#set($ok = $spacesText.put("All","All"))
#foreach($space in $spaces)
  #set($ok = $spacesText.put($space,$space))
#end

#macro(spaceoption $space $selectspace $spacesText)
  <option value="$spacesText.get($space)" #if($selectspace == $spacesText.get($space))selected#end>$space</option>
#end

#macro(spaceselect $selectspace $spaces $spacesText)
  <select name="space">
    #spaceoption("All" $selectspace $spacesText)
    #foreach($space in $spaces)
      #spaceoption($space $selectspace $spacesText)
    #end
  </select>
#end

#if($request.getParameter("text"))
  #set($text = $request.getParameter("text"))
#else
  #set($text = "")
#end

#set($utext = $xwiki.getURLEncoded($text))
#if($space == "All")
  #set($url = $xwiki.getURL("Main.WebSearchRss", "view", "xpage=rdf&text=${utext}" ))
#else
  #set($url = $xwiki.getURL("Main.WebSearchRss", "view", "xpage=rdf&space=$space&text=${utext}"))
#end

<div style="float: right;">
  <a href="$url"><img src="$xwiki.getSkinFile("icons/black-rss.png")" border="0px" /></a>
</div>

<form action="">
  {pre}
    <div class="centered">
      Query
      <input type="text" name="text" value="$!text.replace(">", ">").replace("<", "<").replace('"', """)" size="20"/>
      in space #spaceselect($space $spaces $spacesText) <input type="submit" value="Search"/>
    </div>
  {/pre}
</form>

#if($text == "")
  ## No search
#else
  #set($text = $text.replaceAll("'", "''").replaceAll("%", "\\%"))
  #set($datedlist = $xwiki.arrayList)
  
  #set($nbitems = 50)
  
  ## ———————————————————–
  ## Non-admins should not see results from XWiki, Main, Admin 
  ## and Panels spaces. Also exclude the WebPreferences doc.
  ## ———————————————————–
  #if ($xwiki.hasAdminRights())
    #set ($excludedWebs = "")
  #else
    #set ($excludedWebs = "doc.web<>'XWiki' and doc.web<>'Admin' and doc.web<>'Panels' and doc.name<>'WebPreferences' and")
  #end

  ## ———————————————————–
  ## Display only a given space if $request.space is defined
  ## ———————————————————–
  #if($space == "All")
    #set ($webClause = "$excludedWebs")
  #else
    #set ($webClause = "doc.web='$space' and $excludedWebs")
  #end

  #macro(addelement $item $list)
    #if($xwiki.hasAccessLevel("view", $context.user, "${context.database}:${item}"))
      #set($itemdoc = $xwiki.getDocument($item))
      #set($sdate = $xwiki.formatDate($itemdoc.date, "yyyyMMddHHmmss"))
      #set($sitem = "${sdate}${item}")
      #if(!$list.contains($sitem))
        #set($discard = $list.add($sitem))
      #end
    #end
  #end

  ## ———————————————————–
  ## Search in page content
  ## ———————————————————–
  #set ($sql = "where $webClause upper(doc.content) like upper('%$!text%') order by doc.date desc")
  #foreach ($item in $xwiki.searchDocuments($sql , $nbitems, 0))
    #addelement($item $datedlist)
  #end

  ## ———————————————————–
  ## Search in text fields (simple String properties)
  ## ———————————————————–
  #set($sql= ", BaseObject as obj, StringProperty as prop where $webClause obj.name=doc.fullName and prop.id.id = obj.id and upper(prop.value) like upper('%$!text%')")
  #foreach ($item in $xwiki.searchDocuments($sql , $nbitems, 0))
    #addelement($item $datedlist)
  #end

  ## ———————————————————–
  ## Search in big text fields (textarea properties)
  ## ———————————————————–
  #set($sql= ", BaseObject as obj, LargeStringProperty as prop where $webClause obj.name=doc.fullName and prop.id.id = obj.id and upper(prop.value) like upper('%$!text%')")
  #foreach ($item in $xwiki.searchDocuments($sql , 50, 0))
    #addelement($item $datedlist)
  #end

  #set($list = $xwiki.arrayList)
  #foreach($item in $xwiki.sort($datedlist))
    #set($ok = $list.add(0, $item.substring(14)))
  #end
  
  #includeInContext("XWiki.Results")
  
#end

The Results Page

The code above gets you a new search form, but it won’t work until we update the results page as well. The results of the search page are displayed by the XWiki.Results page. Logging in as Administrator, you can replace the old one with the code-snippet below. Use the edit link on the right side of the search-page (while in edit-mode):

#set($showdata = 0)
#set($formatDate = "yyyy MMMM dd, HH:mm")

## WARNING: Do not add any empty line inside the table element. This will potentially break 
## the Javascript we're using for filtering/sorting columns. It might work in FF but will break
## in other browsers like IE. This is because empty lines add <p class="paragraph"></p> elements
## when rendered.

<table id="searchTableUnique" class="grid sortable filterable doOddEven" cellSpacing=0 cellpadding="0" border="1">
  <tr class="sortHeader">
    <th>Page</th>
    <th width="150" class="selectFilter">Space</th>
    <th width="150">Date</th>
    <th width="150">Last Author</th>
    #if($xwiki.hasAdminRights())
      <th width="210" class="unsortable noFilter">Actions</th>
    #end
  </tr>
  #foreach ($item in $list)
    #set($troubi ="non")
      #if ($xwiki.hasAccessLevel("view", $context.user, "${context.database}:${item}"))
        #set($bentrydoc = $xwiki.getDocument($item))
        #set($cclass = $xwiki.getDocument("XWiki.XWikiComments").getxWikiClass())
        #set($comment = $cclass.newObject())
        #if($xwiki.getWebPreferenceAsInt("commentsorder",1)==0)
          #set($comments = $bentrydoc.getComments())
        #else
          #set($comments = $bentrydoc.getComments(false))
        #end
        #set($createur = $xwiki.getUserName($bentrydoc.author))
        #set($ptitle = $bentrydoc.getDisplayTitle())
        <tr><td align=left>
          #if($comments.size()>0)  
            #set($i = 0)  
            #set($cobj = $comments.get($i))  
            #set($comment = $bentrydoc.display("comment", "view", $cobj))  
            #set($date = $cobj.getXWikiObject().get("date").value)
            #if($date)
              #set($date2 = $!xwiki.formatDate($date,"yyyy MM dd HH:mm:ss")  )
            #end
            #if($bentrydoc)
              #set($date1 = $!xwiki.formatDate($!bentrydoc.date,"yyyy MM dd HH:mm:ss") )
            #end
            #if($date1.equals($date2) )
              [$ptitle>${bentrydoc.web}.$bentrydoc.name] <em>- 1 new comment</em>
              #set($troubi ="oui")
              #set($desc = $cobj.getXWikiObject().get("comment").value)
            #else
              [$bentrydoc.name>${bentrydoc.web}.$bentrydoc.name] #if ($ptitle != $bentrydoc.name) <em>- $ptitle</em>#end
            #end
          #else  
            #set($comment = "")  
            [$bentrydoc.name>${bentrydoc.web}.$bentrydoc.name.replaceAll("@","%40")] #if ($ptitle != $bentrydoc.name) <em>- $ptitle</em>#end
          #end   
        </td><td align=left>
          [$bentrydoc.web>${bentrydoc.web}.WebHome]
        </td><td align=left>
          $xwiki.formatDate($bentrydoc.date,"yyyy MMM dd") at $xwiki.formatDate($bentrydoc.date,"HH:mm")</td><td align=middle>
          #if($troubi =="oui")
            #set($createur = $xwiki.getUserName($cobj.author)   )
          #end
          #if ($createur == "XWikiGuest")
            Guest
          #else
            $createur
          #end
        </td>
        #if($xwiki.hasAdminRights())
          <td>
            <a href="$xwiki.getURL("Admin.CopyDocument", "view", "sourcedoc=${bentrydoc.fullName}")">Copy</a> − <a href="$bentrydoc.getURL("delete")">Delete</a> − <a href="$bentrydoc.getURL("view", "xpage=rename&step=1")">Rename</a> − <a href="$xwiki.getURL("Admin.PageRights", "view","page=${bentrydoc.fullName}")">Rights</a>
          </td>
        #end
      </tr>
    #end
  #end
</table>

If you open http://yourwiki/xwiki/bin/view/Main/NewSearch in your web browser, you should now be able to use the new search. But panel on the right will still be using the old search. That’s what we’ll fix next.

The Results Page

Log in as administrator (if you aren’t already) and go to http://yourwiki/xwiki/bin/view/Panels/. There you can edit/add new Panels (they are all wikipages remember?). Create a new one called “NewSearch”, using a “view” panel type.

The content of the panel is:

#panelhiddenheader("Search")
  <form action="/xwiki/bin/view/Main/NewSearch">
    <div id="globalsearch">
      <input id="globalsearchinput" type="text" name="text" value="$msg.get('Search')" size="15" onfocus="if (this.value == '$msg.get('Search')') value=''; this.select();" onblur="if (this.value == '') value='$msg.get('Search')'; this.blur()"/>
 
      <input class="button" value="Search" type="image" src="$xwiki.getSkinFile("go.png")"/>
    </div>
  </form>
#panelfooter()

This is basically a copy of the “Search” panel (the action url differs). The only thing left to do is to register the panel—or better yet, replace the old search. Open http://yourwiki/xwiki/bin/admin/XWiki/XWikiPreferences (or click on Administration/Preferences) and open the skin section. There you’ll find an edit-box to specify the panels to be displayed on the right side. Replace “Panels.Search” with “Panels.NewSearch” and you’re done.