Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

All other Modules /

Discuss all other Modules here.

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

CustomSearchForm snippet


Go to End


46 Posts   17730 Views

Avatar
UncleCheese

Forum Moderator, 4102 Posts

30 April 2009 at 8:53am

Edited: 30/04/2009 8:53am

From time to time the question comes up -- how do I search on non-page objects? For example, last week, I had a client request that his GlossaryTerm dataobjects, which live on a GlossaryPage, added with a DataObjectManager, could be searchable by site search. I eventually came up with a solution. It's not the prettiest thing in the world, and not well tested, but low and behold, it works!

Step One: Extend the SearchFormClass

We need to overload the search function of the SearchForm class, and give it the ability to add DataObject classes to search. That class is attached to this post. You can place it in mysite/code.

Step Two: Configure the DataObject

To instruct the DataObject how to behave in site search, we need to add some configurations to the class.

class GlossaryTerm extends DataObject
{
	static $db = array(
		'Term' => 'Text',
		'Definition' => 'Text'
	);

	static $has_one = array(
		'Glossary' => 'Glossary'	
	);
	
	static $default_sort = "Term ASC";
	
	static $searchable_fields = array(
		'Term',
		'Definition'
	);
	
	static $search_heading = "Term";
	static $search_content = "Definition";
	
	static $indexes = array(
		"SearchFields" => "fulltext (Term, Definition)",
		"TitleSearchFields" => "fulltext (Term)"
	);
	
	public function Title()	
	{
		return DBField::create('Text',$this->record['Title']);
	}
	public function Content()
	{
		return DBField::create('Text',$this->record['Content']);
	}

	public function canView() {return true;}
	
	public function Link()
	{
		return DataObject::get_one("Glossary")->Link() ."#term".$this->ID;
	}
	

Let's go through this step-by-step.

- $searchable_fields: This tells the search class which fields will be examined for the relevance of keywords in the search.

- $search_heading: This is the name of the field that will serve as the title, or heading, in the search results.

- $search_content: The name of the field that will serve as the content in the search results page.

- $indexes: "SearchFields" should be fulltext and include all the fields listed in your $searchable_fields array. "TitleSearchFields" should be fulltext and include only your $search_heading value.

- Title() and Content() functions. The search results processor is pretty barebones. You'll get non-object errors unless you explicitly return FieldType objects.

- canView(): because SearchForm is only designed to return SiteTree objects, you have to stick this in there to tell the SearchForm that it can be viewed.

- Link(): How are we going to link off of the dataobject in a search result? In this case, I sent it to the Glossary page with a #ID in the url to skip down to the correct term.

Step Three: Build the Search Form

Nothing to it! Just change "SearchForm" to "CustomSearchForm" and add your new class!

	function SearchForm() {
		$searchText = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : 'Search';
		$fields = new FieldSet(
	      	new TextField("Search", "", $searchText)
	  	);
		$actions = new FieldSet(
	      	new FormAction('results', 'Search')
	  	);

	  	$f = new CustomSearchForm($this, "SearchForm", $fields, $actions);
		$f->addSearchableClasses(array('GlossaryTerm'));	  	
	  	return $f;
	}

That's it! There's a lot of room for automation in the way this is done. I wrestled with making a SearchableDataObject as a decorator, or even a subclass of DataObject, but there were numerous constraints. I'm anxious to wrestle some more with it, and I'm definitely open to any modifications or enhancements that come out of the community.

Enjoy!

Avatar
schellmax

Community Member, 126 Posts

2 May 2009 at 1:15am

this looks very promising.
haven't tried it yet though, stumbled upon it searching for a slightly different problem:

as mentioned in various threads, sitesearch doesn't look into fields other then content and title.
as far as i can see, the customsearchform you posted here won't lift this restriction: although related dataobjects are searched, custom fields (using static $db = array()) are still unsearchable?

did i get something wrong? can your solution here be used/modified to search in custom fields as well?

Avatar
UncleCheese

Forum Moderator, 4102 Posts

2 May 2009 at 1:55am

Totally possible. There's no reason why you can't assign all those static vars to a SiteTree object. There is one thing that would need to be updated in the CustomSearchForm code, though.

Avatar
schellmax

Community Member, 126 Posts

2 May 2009 at 2:30am

Edited: 02/05/2009 2:35am

nice!
could you give me a little hint what needs to be updated in CustomSearchForm code?
i'll try then, if i succeed, there's a number of threads that can be marked as 'Solved' then, such as

http://www.silverstripe.org/archive/show/196669
http://silverstripe.org/customising-the-cms/show/256621
http://www.silverstripe.org/general-questions/show/255129
http://www.silverstripe.org/form-questions/show/256689
http://www.silverstripe.org/archive/show/12423
...

thanks a lot

Avatar
schellmax

Community Member, 126 Posts

9 May 2009 at 5:06am

unclecheese, sure you're working to capacity supporting dataobjectmanager and i really don't want to disturb you there (this pays out for everyone of us!) :-)
but as you mentioned that there's only one thing to be updated in CustomSearchForm code to make it search in custom fields too, i'm still curious wheter it's really that easy?
and one more: will it work for the new translatable too (searching in only one language tree)?
thanks

Avatar
UncleCheese

Forum Moderator, 4102 Posts

9 May 2009 at 5:14am

Edited: 09/05/2009 5:15am

You know, now that I look at it, I think you might be okay. Can you try going through the process with a regular SiteTree object? Follow the steps exactly as they are -- that is, you'll still have to tell the search form which are your Title and Content fields. We can update that later, but for now, the class is expecting a DataObject, so it doesn't make any assumptions. The key for you is going to be $indexes and $searchable_fields. Try it out and let me know how you do.

Avatar
Apophenian

Community Member, 46 Posts

11 June 2009 at 6:28pm

I had to fiddle with it a bit to get it working with regular site tree objects, but I have attached my version of customsearchform, and it seems to work for me for sitetree objects and normal dataobjects.

I'm wondering if it is worth spending some time building a proper inverted index search engine.

Does anyone have any thoughts?

Avatar
Shawn Parr

Community Member, 60 Posts

8 July 2009 at 1:47am

I've been working on this, and I have it going 99%, but I need some help with that last 1%.

Where I seem to have run into trouble is that my Dataobjects already have a link function. Many of the pages I build using dataobjects allow clicking on them to display them in their own page. As such I have a function like:

function Link() {
     return $this->NewsPage()->Link('article/'.$this->ID);
}

If I leave that then the links in my search results don't work, as it seems when the search is run the context for $this-> doesn't return a path that is usable. I.e. on my test system a valid url would be http://server/shawn/news/home/article/10 and that returns http://server/shawn/news//article/10.

I also can't use the example Uncle Cheese gives as that hardwires the links to a specific page, and there are quite a few installs we have going, and it is very unlikely all will name their pages the same, plus the issue of having multiple pages of that type.

So I build the following link function to try to get around this:

function Link() {

			$a = DataObject::get_one('NewsArticle', "`NewsArticle`.ID = '{$this->ID}'");
			$p = DataObject::get_one('NewsPage', "`SiteTree`.ID = '{$a->NewsPageID}'");
			
			$l = $p->Link('article/'.$this->ID);
			return $l;
		
		}

This almost works. It seems that when it writes the URL, it is putting an additional / in front of it, so that the browser considers it an absolute URL rather than relative and thus doesn't add it to the base href like other links on the page. So what I get are URLs like: //shawn/news/home/article/10. These links are 100% correct if the extra forward slash would not be at the beginning.

For the time being I guess I can use a php replace statement to remove those slashes. But I was curious if anyone had a better way of doing just within SS? Is there another function or call I should use to get those links? Am I missing something drop dead obvious that would make this more of a one/two liner?

Thanks as always to Uncle Cheese for making our sites extremely more usable.

Go to Top