Search Widgets using Lucene

Topics: Customizing Orchard, General, Writing modules, Writing themes
Dec 3, 2013 at 8:09 PM
Hi, I have a website where the client is using widgets everywhere to build the content structure of the site.

The search module in orchard works for content items. So, when the client performs a search the results are limited to a few content items, but not the content in the widgets.

Does any body knows how to achieve a search in the orchard widgets (preferred using lucene)?

If it is not possible using Lucene, what's the best way to solve this issue?

Thanks!
Dec 3, 2013 at 9:05 PM
Not a solution but a question: even if you can somehow search the widgets, how would you address them in the search results? Widgets are shared among pages thru layers, and they are not actually connected to the content item.

If the data displayed in the widgets are exclusive to a single content item, I think the way to go would be to attach the part(s) to the content item itself, and display them in zones other than Content using placement.info, if it's desired so.


Dec 3, 2013 at 10:15 PM
I already know that is an issue. I would have to figure it out how to find the proper layer(s) that is holding the widget.
What i don't want to do is to build my own search engine making queries to orchard db. I could block the site and make it very slow if i don't use the best practices. That's why i want to use Lucene, or if someone has another approach on how to do this before i just drop a query to orchard db, it will really help me a lot.

It has became a pain in the ass with the client, he just thinks it is basic and a dummy task not as hard as it has been so far.

Anyway i think you have gave me an idea. Maybe attaching a content item via handlers holding the layer and a copy of the content of the widget, could do the job. But it might not be the best way. Too waste of space and innecesary processing...

If someone has a better way to accomplish this i would love to hear it!

Thanks!
Dec 13, 2013 at 3:09 PM
I solved this overriding the Index of the Search Module in my theme. When the search hits widget content-items the view check the stereotype=widget, and query the orchard db in order to get the layer of the widget and therefore the url. This solution only works for layer rules where the url is unique.

Here is the full solution:
@model Orchard.Search.ViewModels.SearchViewModel
@using SitiosArgos.Models;
@using Orchard;
@using Orchard.Widgets.Services;


@functions {
    string getUrlFromLayerRule(string layerRule)
    {
        if (!layerRule.ToLower().Contains("url")) return null;
        string[] split = layerRule.Split('\'');
        if (split.Length < 2) return null;
        return split[1].Trim();
    }
    
    string getRedirectCulture(string expression)
    {
        return expression.Split(',').FirstOrDefault(p => p.Contains(WorkContext.CurrentCulture)).Split('=')[1].Trim();
    }
    
    string findTitleFromContentItem(Orchard.ContentManagement.ContentItem contentItem)
    {
        string[] hints = { "title","name"}; 
        foreach(var part in contentItem.Parts)
        {
            foreach(var field in part.Fields)
            {
                if(field.Name.ToLower().Contains("title") || field.Name.ToLower().Contains("name"))
                {
                    return (field as dynamic).Value;
                }
            }
        }
        return string.Empty;
    }

    string findBodyFromContentItem(Orchard.ContentManagement.ContentItem contentItem, string word)
    {
        foreach (var part in contentItem.Parts)
        {
            foreach (var field in part.Fields)
            {
                if ((field as dynamic).Value!=null)
                {
                    if ((field as dynamic).Value.Contains(word))
                    {
                        return (field as dynamic).Value;
                    }
                }
            }
        }
        return string.Empty;
    }
    
    string getSlugFromContentItem(dynamic contentItem)
    {
        try
        {
            var slug = contentItem.SlugPart.SlugPath.Value;
            string[] slugs = slug.Split(new string[] { "," }, StringSplitOptions.None);
            string localizedSlug = getLocalizedSlug(slugs, slug);
            return (Href("~/") + localizedSlug).Replace("//", "/");
        }
        catch { }

        return "/";
    }

    string getLocalizedSlug(string[] slugs, string originalSlug)
    {
        foreach (var slug in slugs)
        {
            var val = slug.Split(new string[] { "=" }, StringSplitOptions.None);
            if (val.Length == 2)
            {
                if (WorkContext.CurrentCulture.ToLower().Trim() == val[0].ToLower().Trim())
                {
                    return val[1];
                }
            }
        }
        return originalSlug;
    }

    bool hasRedirectPart(dynamic contentItem)
    {
        try
        {
            if (contentItem.RedirectPart != null) return true;
        }
        catch { }
        return false;
    }
}

@{
    Style.Require("Search");

    IEnumerable<object> searchResults = Model.ContentItems;
    Model.ContentItems.Classes.Add("content-items");
    Model.ContentItems.Classes.Add("search-results");
    
    
    var services = WorkContext.Resolve<IOrchardServices>();
    var widgetsService = WorkContext.Resolve<IWidgetsService>();

}

<section id="main-content" class="wrapper container">
    <div class="row-fluid">
        <div class="span16 span-internal-16">
            <article class="content-item page">
                <header>
                    <h1>@Html.TitleForPage(T("Search").Text)</h1>
                </header>
                <div class="row-fluid show-grid">
                    <span class="offset1 span1 visible-desktop">&nbsp;</span>
                    <div class="span14 row-fluid show-grid">
                        <h6 class="span14">@T("Search for:") &nbsp @Model.Query</h6> 
                        @if (HasText(Model.Query))
                        {
                            if (searchResults.Count() == 0)
                            {
                            <span class="number-result span14">@T.Plural("There is <em>one</em> result", "<em>zero</em> results", searchResults.Count())</span>
                            }
                            else
                            { 
                            <span class="number-result span14">@T.Plural("There is <em>one</em> result", "<em>{1} - {2}</em> of <em>{0}</em> results", Model.TotalItemCount, Model.StartPosition, Model.EndPosition)</span>
                            }
                        }
                        
                        @if (searchResults != null && searchResults.Count() > 0)
                        {
                            foreach (dynamic item in searchResults)
                            {
                                Orchard.ContentManagement.ContentItem contentItem = item.ContentItem;
                                bool isWidget = false;
                                bool hasRedirect = false;
                                string link = string.Empty;
                                
                                string titleText = string.Empty;
                                string bodyText = string.Empty;

                                string stereotype = null;
                                if (contentItem.TypeDefinition.Settings.Keys.Contains("Stereotype"))
                                {
                                    stereotype = contentItem.TypeDefinition.Settings["Stereotype"];
                                }

                                if (hasRedirectPart(contentItem))
                                {
                                    hasRedirect = true;
                                    try
                                    {
                                        var redirectPath = (contentItem as dynamic).RedirectPart.RedirectPath.PartFieldDefinition.Settings["TextFieldSettings.Hint"];
                                        link = this.getRedirectCulture(redirectPath);
                                    }
                                    catch { hasRedirect = false; }
                                    titleText = findTitleFromContentItem(contentItem);
                                    bodyText = findBodyFromContentItem(contentItem, Model.Query);
                                }
                                
                                try
                                {
                                    titleText = item.ContentItem.TitlePart.Title;
                                }
                                catch { }

                                try
                                {
                                    bodyText = item.ContentItem.BodyPart.Text;
                                }
                                catch { }
                                
                                
                                if (stereotype == "Widget")
                                {
                                    isWidget = true;
                                    var widgets = widgetsService.GetWidgets().Where(p => p.Id == contentItem.Id).ToList();
                                    var widget = widgets.FirstOrDefault();
                                    if (widget != null)
                                    {
                                        link = getUrlFromLayerRule(widget.LayerPart.LayerRule) ?? string.Empty;
                                    }
                                    titleText = widget.Title;
                                }
                                if (bodyText == null) { bodyText = string.Empty; }
                                string bodyHtml = bodyText.ToString();
                                var body = new HtmlString(Html.Excerpt(bodyHtml, 200).ToString());
                                
                                 
                            <span class="keyword span14">@titleText</span>
                            <article class="span14 content-result-data">@body
                                @if(isWidget || hasRedirect){
                                    <a href='@Href(link)'>@T("more").ToString()</a>
                                }
                                else {
                                    @Html.ItemDisplayLink(T("more").ToString(), contentItem)
                                }
                            </article>
                            }
                            
                            <div class="span14">
                                @Display(Model.Pager)
                            </div>
                        }
                    </div>
                </div>
            </article>
        </div>
    </div>
</section>