Limiting Search to Sections of Site

Recipes for showing a search box with options to search only in this section of the site, or other sections of the site.

Suppose you have a plone site for your intranet with numerous folders under the main site-root that represent different departments, projects, etc. You want to allow users to search either the entire site, or a particular subsection (i.e., someone looking for the latest expense report form probably doesn't want a search a search returning documents about the latest board meeting, as well as the desired form).

Here's how I customized the portal_search template to give users to option to only search the current folder and its children:

-I went to the ZMI and customized plone_forms/search_form

-I edited my customized copy to include (right after the field for the text to be searched):

  <div class="field">
                <input type="text" 
                       name="SearchableText" 
                       size="25"
                       tabindex="" 
                       onfocus="formtooltip('text_search_help',1)" 
                       onblur="formtooltip('text_search_help',0)"
                       tal:attributes="tabindex tabindex/next;" 
                       />
                       <input type="checkbox" 
                           name="path" 
                           value="#"
                           class="noborder"
               checked="checked"
                           tabindex=""
               onfocus="formtooltip('limit_search_help',1)" 
               onblur="formtooltip('limit_search_help',0)" 
                           tal:attributes="tabindex tabindex/next;value
               string:/gri${request/URLPATH1};" 
               /> Search only the current folder (and sub-folders) 
              </div>

Note that in the line ...string:/gri${request/URLPATH1};... the "gri" refers to the plone instance (which is hidden behind apache, so www.mysite.org redirects to www.mysite.org/gri). I couldn't figure out how to avoid hardcoding the plone instance name, though I'm sure it can be done.

This code gives the user a checkbox that, if checked, limits the search to the current folder and any sub-folders. If it's unchecked, then the search scope is the entire portal.

With a little more work, you could probably adapt this technique to provide, for example, a list of sections to be searched and allow the user to select one or more sub-sections to be included in the search scope.

As done on plone.org

Additional notes by pupq (Joel Burton) on how this is implemented on plone.org

We do something very similiar on plone.org; allowing searching in two sections (documentation and development), and in current folder. We try to be smart--when you're in the top of documentation section, you should get that as the default, and when you're in the top of the development section, you should get that.

The full global_searchbox for plone.org (as of Feb 2004):

    <div id="portal-searchbox" metal:define-macro="quick_search">
      <form name="searchform"
            style="position: relative; top: -0.6em;"
            action="search"
            tal:attributes="action string:${portal_url}/search" >
          <label for="searchGadget" class="hiddenStructure" i18n:translate="text_search">Search</label>
          <input id="searchGadget"
                name="SearchableText"
                type="text"
                size="30"
                value=""
                alt="Search"
              style="font-family: Verdana; font-size: 110%; margin-bottom: 3px; margin-right: 0; padding: 3px;"
            title="Search"
            accesskey="accesskeys-search"
            i18n:attributes="alt accesskey title"
            tal:attributes="value request/SearchableText|nothing;
                                tabindex tabindex/next" class="visibility:visible;" /><br />
          <select id="searchContext" name="path" style="font-family: Verdana; font-size: 100%;"
                      tal:define="path request/form/path|nothing;">
            <option value="/plone.org"
                    tal:attributes="selected python:test(path=='/plone.org', 'selected', nothing)">All of plone.org</option>
            <option value="/plone.org/documentation"
                    tal:attributes="selected python:test(path=='/plone.org/documentation', 'selected', nothing)">In Documentation</option>
            <option value="/plone.org/development"
                    tal:attributes="selected python:test(path=='/plone.org/development', 'selected', nothing)">In Development</option>
            <option tal:condition="python: here.aq_parent != portal_object" 
                    tal:attributes="value python:'/'.join(here.aq_parent.getPhysicalPath())">In this folder</option>
          </select>
          <input class="searchButton"
                type="submit"
                value="Search"
              accesskey="accesskeys-search"
              tal:attributes="tabindex tabindex/next"
                i18n:attributes="value accesskey" />
      </form>
    </div>

Additional ideas from Bill Page

I really liked this idea but I had a lot of trouble getting the right search path. In the end I changed the action for the Search tab in portal_action as described above. Then I customized plong_form/search_form by inserting the following code for the path:

    <div class="field">
        <input type="text" 
               name="SearchableText" 
               size="25" style="width:100%"
               tabindex="" 
               onfocus="formtooltip('text_search_help',1)" 
               onblur="formtooltip('text_search_help',0)"
               tal:attributes="tabindex tabindex/next;" 
               /> <br />
        <input type="checkbox"
               name="path"
               value="#"
               class="noborder"
               checked="checked"
               tabindex=""
               onfocus="formtooltip('limit_search_help',1)"
               onblur="formtooltip('limit_search_help',0)"
               tal:attributes="tabindex tabindex/next;
                               value python:'/'.join(here.getPhysicalPath())"
               /> Search only the current folder (and sub-folders) 

    </div>

Notice that I want the path to the current folder not the path to the parent so I use just here.getPhysicalPath

The other thing I did was to tidy up the checkboxes for the item types:

    <div class="row">
    <div class="label">
        <span i18n:translate="label_item_type">Item type</span>
        <div id="item_type_search_help"
            i18n:translate="help_search_item_type" 
            class="help" 
            style="visibility:hidden">
        Return items of a specific type.
        </div>
    </div>

    <input
    class="noborder" type="checkbox"
    src="select_all_icon.gif"
    name="selectButton" title="Select all items"
    alt="Select all items"
    onclick="toggleSelect(this); return false;"
    tal:attributes="src string:$portal_url/select_all_icon.gif" />
    All / None<br />
    <div class="field">
        <span tal:repeat="type python:container.portal_types.listContentTypes()">
        <div nowrap="true" style="float: left; width:34ex">
            <metal:block tal:define="tabindex tabindex/next">
            <input type="checkbox" 
                  name="portal_type:list" 
                  value="#"
                  class="noborder"
                  checked="checked"
                  tabindex=""
                  onfocus="formtooltip('item_type_search_help',1)" 
                  onblur="formtooltip('item_type_search_help',0)" 
                  tal:attributes="value type;
                                  tabindex tabindex;
                                  id string:portal_type_${tabindex}"/> 
            <label for=""
                  i18n:translate=""
                  tal:attributes="for string:portal_type_${tabindex}"
                  tal:content="python:here.portal_types.getTypeInfo(type).Title()"
                  />
            </metal:block>
        </div></span>       
    </div>

The first checkbox uses a bit of javascript cribbed from elsewhere in Plone:

    &lt;disabled script&gt;
    // Functions for selecting all checkboxes

    isSelected = true;

    function selectAll() {
    checkboxes = document.getElementsByName('portal_type:list');
    for (i = 0; i < checkboxes.length; i++)
    checkboxes[i].checked = true ;
    isSelected = true;
    return isSelected;
    }

    function deselectAll() {
    checkboxes = document.getElementsByName('portal_type:list');
    for (i = 0; i < checkboxes.length; i++)
    checkboxes[i].checked = false ;
    isSelected = false;
    return isSelected;
    }

    function toggleSelect(selectbutton) {
    if (isSelected == false) {
    selectbutton.setAttribute('src','<dtml-var portal_url>/select_none_icon.gif');
    return selectAll();
    }
    else {
    selectbutton.setAttribute('src','<dtml-var portal_url>/select_all_icon.gif');
    return deselectAll();
    }
    }
    &lt;disabled /script&gt;

and the following bit of inline CSS:

    <div nowrap="true" style="float: left; width:34ex">

permits the permits the browser to arrange the type checkboxes in a multi-column format to save space.

I'm happily using Plone 1.0.5 on Zope 2.6.2 on Solaris.

Limiting search to a member's folder

I needed to be able to have each member of my site search only within their own member folder. So I modified global_searchbox.pt and search_form.pt and added the following code:

      <form name="searchform"
            action="search"
            tal:attributes="action string:${portal_url}/search"
            tal:define="member_folder python:context.get_aq_member_folder" >

          <!-- specify the path as the member folder, if any -->
          <input type="hidden"
                name="path"
                tal:attributes="value member_folder"
                tal:condition="member_folder" />

Then you need to put a Python script named get_aq_member_folder in your skin folder::

## Script (Python) "get_aq_member_folder_id" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters= ##title= ## # returns the member folder path in the aq chain, if any. # if no member, it returns None

members = context.portal_membership.getMembersFolder() path_is_long_enough = len(context.getPhysicalPath()) len(members.getPhysicalPath())

# no need to check for member, if we can see on the path length that # we are not below the Members folder if path_is_long_enough: parents = context.REQUEST.PARENTS for i in range(len(parents)): if parents[i] == members: member_folder = parents[i-1].getPhysicalPath() return /.join(member_folder) return None

Thanks to Max M for get_aq_member_folder_id which was the starting point for this script. The only difference is that the original script returns the ID of the member folder instead of the full path of the member folder: return parents[i-1].getId()

limiting search as done in plone

Posted by caynolos at Jun 07, 2005 06:45 AM
i followed the instructions but it didn't work. i replaced all the data with mine but it still didn't return anything.

also doesn't work for me

Posted by Arthur Kalmenson at Jun 14, 2005 03:42 PM
I have the same problem. I pinpointed the problem as being the follow statement:

tal:attributes="selected python:test(path=='/plone.org/development', 'selected', nothing)"

I think that what this statement is doing is figuring out which drop down menu option to display (ie which one the user previously chose?). I was putting the ZMI path instead of the URL and so there were no search results. I removed this attribute and everything worked fine.

I added the tal:attributes back (this time using the URL) and everything works, however, the drop down menu resets upon each search while on plone.org it does not. Would anyone be able to tell me how this is done?

Thank you for any help!

make contents inside CMF Content Panels as searchable

Posted by Chaitali at Apr 14, 2006 12:12 PM
Hello,
 I wish to make the text inside the content panel as searchable , which currently is not searchable.When I write some text in the search box which is present in the content panel or the panel title it gives me Zero as search result.
 Can you please tell me how to make content inside content panel as searchable.
Thanks,

Getting the path

Posted by constantin at Apr 20, 2006 02:09 PM
In "search box with option part" of this tutorial the writer uses /gri${request/URLPATH1}; to get the path to the folder. Where he/she hardcodes the name of the plone instance in Zope, "gri". This could be better handled.

The "as done on plone" part uses a better but difficult way to get the path python:'/'.join(here.aq_parent.getPhysicalPath())

I think there is a better simpler solution to get the path: python:here.absolute_url_path().

Is there a reason why this is not used? Does absolute_url_path() have problems I did not encounter yet? (Apache forwarding) I have not found any problem, but did only test this on one site.

Search "here" is way cool...

Posted by Aaron Williams at Mar 23, 2007 07:21 AM
Thanks to all of the above for the code and ideas, it has enabled me to implement an elegant, global "search here" feature. I now have a small checkbox just next to the global search button that simply says "here". If you check it before hitting "search" it only searches from the current folder down.

2 lines of code, 1 style rule, 0.9 on the fun factor!

Here's how:

1. Customise /portal_skins/plone_templates/global_searchbox
2. Add this code for the checkbox where you would like it to appear:

<input type=checkbox id="search_path" name="path" tal:attributes="value python:'/'.join(here.getPhysicalPath())">
<label for="search_path" i18n:translate="here" style="color:white;font-weight:normal;font-size:small;">here</label>

3. Add this style rule to /portal_skins/custom/ploneCustom.css

input#search_path {border:0;margin:0;padding:0;height:10px;width:10px;}


Please excuse the inline style... i like to "keep it together" sometimes for individually customised pieces.

And Live Search?

Posted by Russell Brown at Jun 07, 2007 09:04 AM
Hi,
I followed this instructions and it worked lovely. I also added the same constraint to the advanced search form too. I don't know how to make live search take account of this path field though: do you?

Figured it out myself

Posted by Russell Brown at Jun 07, 2007 09:44 AM
You need to create a custom livesearch_reply script and add the path you want in the Catalog search there.
You also need to add the path field to the query params passed to the script and some code to parse those params.

So you need to cutomise plone_skins/plone_3rdparty/livesearch.js too to pass the value from your (in my case hidden) "path" form element too.

Changes

Posted by Maciek Dziergwa at Dec 17, 2007 09:21 PM
In livesearch.js you have to chege XMLHttpRequest part:

    liveSearchReq = new XMLHttpRequest();
    liveSearchReq.onreadystatechange= liveSearchProcessReqChange;
    // need to use encodeURIComponent instead of encodeURI, to escape +
    path = document.getElementById("path");
    pathQS = '';
    if (path.checked ) {
        pathQS = "&path=" + path.value;
    }
    //alert(liveSearchRoot + queryTarget + encodeURIComponent(searchInput.value) + pathQS);
    
    liveSearchReq.open("GET", liveSearchRoot + queryTarget + encodeURIComponent(searchInput.value) + pathQS);
    liveSearchLast = searchInput.value;
    liveSearchReq.send(null);
    }

and in livesearch_reply.py

##parameters=q, path='/', limit=10

...

if not path:
    results = catalog(SearchableText=r, portal_type=friendly_types)
else:
    results = catalog(SearchableText=r, portal_type=friendly_types, path=path)