Tuesday, 26 January 2016

Is Your Sitecore Query Not Working?

Friends, hope you are enjoying your Sitecore Journey! Let's start our today's journey.

Scenario:
What do you do when you find that your Sitecore query is NOT retuning any result even-though query is correct? The obvisous answer is you will go to Sitecore CE and fire that query using developer center, right? Yes - this is perfect troubleshooting step.

I did the same, but was not able to understand that why Sitecore was giving error while executing that Sitecore query. I found no issue in syntax, then I tried same syntax by changing item name in query and it worked!

This gave me some clue. It was now clear that there must be something with that particular item name and that made to write this post to help my Sitecore Journey riders!

 Reason:
 If your Sitecore query contains any reserve words (keywords) then it will throw an error. In my case, item name was containing word "and" which is Sitecore keyword.



Solution:
Now you will find this post very simple because being a developer you know that every language gives some way to handle these escape characters (keywords). So you need to handle these escape characters in order to fix this issue.

I took advantage of .NET extension method feature and created extension method which will take Sitecore item as input parameter and will return formatted item path after handling these Sitecore keywords.

 public static string GetFormattedItemPath(this Item itm)
        {
            string itemPath = itm.Paths.FullPath.ToLower();
            if (itemPath.Contains(" and "))
            {
                itemPath = itemPath.Replace(" and ", " #and# ");
            }
            else if (itemPath.Contains(" or "))
            {
                itemPath = itemPath.Replace(" or ", " #or# ");
            }
            return itemPath;
        }

Above code handles only two keywords, but you can customize this code to handle entire list of Sitecore keywords as per your requirement.

After writing above extension method, you simply need to call this method using your item object.
For example:
Item currentItem = Sitecore.Context.Item;
repeaterControl.DataSource = Sitecore.Context.Database.SelectItems(currentItem.GetFormattedItemPath() + "/Comments//*[@@templatename!='Folder']")

Below is a list of words that needs to be escaped when used in Sitecore query:
  •    and
  •    or
  •    child
  •    descendant
  •    div
  •    false
  •    following
  •    mod
  •    parent
  •    preceding
  •    self
  •    ancestor
  •    true
  •    xor

Have a Happy Sitecore Journey!

Friday, 13 November 2015

How To Get "Near By Me" Location Using Sitecore Search?

Dear Sitecorians, to provide better experience to your website users sometimes you are asked implement a feature such as StoesNearByMe, EventsNearByMe or JobsNearByMe in your Sitecore solution.

In such scenarios, basically you need two information:
1) User's location (e.g latitude and longitude)
2) Search all stores, events or jobs near to user's location.

We have already seen how to get user's location in one the previous posts. If you haven't then I would suggest to read that post first.

Once you have user's location handy, then you can pass this information (e.g. latitude and longitude) to below method which will build Sitecore predicate search query to pull near by items.
  
// NearByMe | ToDo : impelement predicate for near by
public static Expression<Func<NewJobModel, bool>> ContainsNearByMe(Double Latitude, Double Longitude)
        {
            var predicate = PredicateBuilder.True<NewJobModel>();

            //Within 25 Miles (40.2336 KMs)
            double distance = 40.2336;

            // earth's radius in km = ~6371
            double radius = 6371;

            // latitude boundaries
            double minlat = Latitude - RadianToDegree(distance / radius);
            double maxlat = Latitude + RadianToDegree(distance / radius);

            // longitude boundaries (longitude gets smaller when latitude increases)            
            double minlng = Longitude - RadianToDegree(distance / radius / Math.Cos(DegreeToRadian(Latitude)));
            double maxlng = Longitude + RadianToDegree(distance / radius / Math.Cos(DegreeToRadian(Latitude)));

            //query = context.GetQueryable<NewJobModel>().Where(builder).Where
            predicate = Latitude.ToString().Aggregate(predicate, (current, temp) 
                => current.Or((item => (item.Latitude >= minlat && item.Latitude <= maxlat)              
                )));

            predicate = predicate.And(Longitude.ToString().Aggregate(predicate, (current, temp) 
                => current.Or((item => (item.Longitude >= minlng && item.Longitude <= maxlng)            
               ))));

            return predicate;
        }



  private static double RadianToDegree(double angle)
  {
      return angle * (180.0 / Math.PI);
  }


Above algorithm calculates near by location within 25 miles radius, you can change it as per your need.
Finally You can call above function using your PredicatBuilder like this:
  
 var builder = PredicateBuilder.True<newjobmodel>();

  //Location near me
  if (jobSearchModel.Latitude != 0 &amp;&amp; jobSearchModel.Longitude != 0)
  {
       builder = builder.And(ContainsNearByMe(jobSearchModel.Latitude, jobSearchModel.Longitude));
  }

This will be gold dust for your Sitecore search implementation.

Monday, 2 November 2015

Does Your Sitecore 8 Hang While Package Upload?

This is a quick tip for all those who are using Sitecore.NET 8.0 rev. 150223 (8.0 Update 2) and facing hang issue while uploading Sitecore package.


Solution:
There are two options to get rid of this issue.

Option-1:
Move to next upgrade as it is known issue and resolved in Sitecore 8.0 Update 3, you can find it at official release notes.


https://dev.sitecore.net/Downloads/Sitecore%20Experience%20Platform/8%200/Sitecore%20Experience%20Platform%2080%20Update3/Release%20notes

 But naturally this is not an easy solution.

Option-2:
This issue occurs when there is NO packages folder found under /Data folder at your Sitecore instance. So create packages folder manually under /Data folder in order to fix this issue.

Thanks to my colleague Mahek who introduced me this issue.

Enjoy your Sitecore Packaging now!

Wednesday, 28 October 2015

Sitecore "Add To Calendar" Module

Whether you are building corporate website, commercial website or any other type of website, one of the famous feature demanding from your client is Calendar Event. There should be feature in your website from where user should be able to download calendar event (e.g. .ICS) file and save it as reminder. Such calendar event is required typically on news or event section of your website.



Clicking on Add To Calendar link will download calendar event - .ICS file as shown below.




If you are looking for similar thing, then you are at right place. Add To Calendar Sitecore module is all about this.

Features of Add To Calendar module:

1) Supports multiple time zones
It will convert date and save calendar event based on end user's timezone no matter in which time zone you have configured event.
For example: If you have configured event in US time zone and if end user is downloading that event from India, then it will convert event timings in IST.

2) Supports multiple occurrence of event in a week
If you have multiple occurrence of event falling under one week, then this module will pick best suitable event for the user which means it will download very next upcoming occurrence of that event.

For example: If you have configured event for Monday, Wednesday & Friday and if the user downloads event on Tuesday then module is intelligent enough to understand very next event and will download Wednesday event for the user.

3) Calendar details are fully configurable
You can manage event title, subject and body content straight away from Sitecore.

4) You can customize it if you want
The code of Add To Calendar Sitecore module is available on GitHub, so you can customize or contribute it as per your wish at https://github.com/nileshthakkar/AddToCalendar

Download

You can download this module from Sitecore Market Place.

Configuration

Once you install it, you will find this module at /sitecore/system/Modules/Add To Calendar
Second column in Event Dates field is for timings. You can specify any time range in 24 hours format.



The templates are at:



The controller can be found at:



So the only thing you need to do is add this controller to your page and you are good to go!


Make sure that you set the data source of Calendar Event item to AddToCalendar controller.


Source Code

In case if you are NOT using MVC and want to have this module working, then you can create a sublayout and inject below code on click of your "Add To Calendar" link.

 public void Download(Item calenderItem)
        {          
            DateTime chatStartDate = DateTime.Today;
            DateTime chatEndDate = DateTime.Today;
            string strTimings = string.Empty;
            NameValueCollection collection = 
            Sitecore.Web.WebUtil.ParseUrlParameters(calenderItem["Event Dates"]);
            if (collection.Count > 0)
            {
                for (int i = 0; i <= 6; i++)
                {
                    foreach (string day in collection.AllKeys)
                    {
                        if (chatStartDate.Date.AddDays(i).DayOfWeek.ToString() == day)
                        {
                            chatStartDate = chatStartDate.Date.AddDays(i);
                            strTimings = collection[day];
                            i = 7;
                            break;
                        }
                    }
                }
            }

            string[] chatTimings = 
            strTimings.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
            if (chatTimings.Length > 0)
            {
                if (chatTimings[0] != null)
                {
                    TimeSpan ts = new TimeSpan(int.Parse(chatTimings[0]), 0, 0);
                    chatStartDate = chatStartDate.Date + ts;
                }

                if (chatTimings[1] != null)
                {
                    TimeSpan ts2 = new TimeSpan(int.Parse(chatTimings[1]), 0, 0);
                    chatEndDate = chatStartDate.Date + ts2;
                }

                if (calenderItem["Time Zone"] != null)
                {
                    Item timeZoneItem = Sitecore.Context.Database.GetItem(calenderItem["Time Zone"]);

                    chatStartDate = 
                    convertDateToTimeZone(chatStartDate, timeZoneItem["Name"], "UTC");
                    chatEndDate =
                    convertDateToTimeZone(chatEndDate, timeZoneItem["Name"], "UTC");

                    CalendarFile calendarFile = new CalendarFile(calenderItem["Subject"], chatStartDate, chatEndDate);
                    calendarFile.location = calenderItem["Location"];
                    calendarFile.description = calenderItem["Body"];

                    this.Response.Clear();
                    Response.Expires = 0;
                    this.Response.Buffer = true;
                    Response.ContentType = "text/calendar";
                    Response.AddHeader("Content-Disposition", "inline; filename=" + calenderItem.Name + ".ics");
                    this.Response.Write(calendarFile.ToString());
                    this.Response.Flush();
                    this.Response.End();
                }
            }
        }

        public DateTime convertDateToTimeZone(DateTime nextDate, string fromTimeZone, string toTimeZone)
        {
            TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById(fromTimeZone);
            TimeZoneInfo cstZone2 = TimeZoneInfo.FindSystemTimeZoneById(toTimeZone);
            nextDate = DateTime.SpecifyKind(nextDate, DateTimeKind.Unspecified);
            DateTime cstDateTime = TimeZoneInfo.ConvertTime(nextDate, cstZone, cstZone2);
            return cstDateTime;
        }


Feel free to customize or contribute this module by forking it at: https://github.com/nileshthakkar/AddToCalendar

Hope this effort will help someone to add Calendar Event feature in his/her project!

Tuesday, 20 October 2015

How To Create Bulk Items in Sitecore Using Powershell Script?

Many times you need to create bulk items in Sitecore for some reason. May be you want to check the performance of your newly implemented feature. Let's say you want to check how your code (e.g search code) is performing when there are tons of items.

So once again you can take the advantage of Sitecore Powershell script here. Below is the small yet powerful Sitecore Powershell script (.ps) which will empower you to create desired number of items based on given template.
#This script will create sitecore items based on given count and template
$InputCount = Show-Input "How many items do you want to create?"
$InputTemplate = Show-Input "Using which template you want to create item?"
$getCurrentItem = Get-Item .
$NewItemPath = $getCurrentItem.FullPath
$CurrentChildCount = $getCurrentItem.Children.Count+1
for ($index = $CurrentChildCount; $index -lt $CurrentChildCount+$InputCount; $index++) 
{
   new-item -Path $NewItemPath -Name $index -type $InputTemplate
}
You can create the script named Create Bulk Items  under /sitecore/system/Modules/PowerShell/Script Library/Package Generator/Content Editor/Context Menu/Packaging



When you run above script, it will ask you:

1) How many number of items do you want to create?


2) Using which template you want to create items?


Below screen summarizes the entire step by step process to create bulk items in Sitecore.


This script is powered by Sitecore Powershell Extensions, so please make sure that you have Powershell extensions installed in your Sitecore instance. If you want to download Sitecore Powershell Extensions, click here for the same.

Empower you Sitecore Journey by using Sitecore Powershell Extensions!

Thursday, 15 October 2015

Are You Getting "Key Does Not Exist In This Pattern Space" Error?

This post is related to my previous post where I illustrated how to deal with persona and pattern card programmatically.

If you are working on such stuff and found below mentioned error in log, then this post may help you to understand why it is happening and how to get rid of this error.

Message: Key 'Network Infrastructure' does not exist in this pattern space
Source: Sitecore.Analytics
   at Sitecore.Analytics.Patterns.PatternSpace.GetKeyIndex(String key)
   at Sitecore.Analytics.Patterns.PercentagePatternMapper.GetPoints(PatternSpace space, IProfileData values)
   at Sitecore.Analytics.Patterns.PatternSpace.CreatePattern(IProfileData profile)
   at System.Linq.Enumerable.<>c__DisplayClass2`3.<CombineSelectors>b__3(TSource x)
   at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
   at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
   at Sitecore.Analytics.Data.Items.ProfileItem.get_MappedPatterns()
   at Sitecore.Analytics.Data.Items.ProfileItem.MatchPattern(IProfileData profile, IPatternDistance distance)
   at Sitecore.Analytics.Tracking.Profile.UpdatePattern()


Reason:
Whenever we call profile.UpdatePattern(), Sitecore is updating all profile keys of that profile using profile key item field name instead of profile key item name.

While Sitecore is storing profile item name as key in Pattern XML field.

How to get rid of this error?
Change name to Network and Infrastructure from Network Infrastructure then it will NOT complain above error.



That's it. Have a learning Sitecore Journey!

Tuesday, 13 October 2015

How to Set Persona/Pattern Card Programmatically in Sitecore?

Recently came across a very interesting requirement of analyzing user's pattern based on search action performed by user on the site.

Every webiste has one common functionality implemented - site search. Here you can hook your code to understand user's pattern by observing the keywords user is searching.

For example: whenever user search terms like software, programming or computer then system should be intelligent enough to understand that - user matches with one of the existing persona Software Engineering.


Being a Sitecore developer, you know that how to set persona on any Sitecore item which is straight forward process (e.g. select desired persona from right top corner of content tab). But how about setting persona or pattern card programmatically at runtime?

Here are the steps I followed to accomplish above mentioned requirement.

1) Provided facility to configure all search keywords or tags.


2) Added one custom field Persona Tags in pattern card template which lists all above mentioned search tags.



3) The final step is to write custom code which will set current user's profile/persona based on search terms by associating with existing Sitecore pattern cards. You can hook this code on search button click as shown in very first screen-shot of this post.

public static void SetPatternCard(string searchedTerm)
        {
            try
            {
                Stopwatch sw = Stopwatch.StartNew();
                Sitecore.Data.Database db = Sitecore.Context.Database;

                //Get persona tag from Data folder matching the searched keyword
                var peronaDataTagsQuery = string.Format("/sitecore/content/Home/Data/Persona Tags//*[@name='{0}']", searchedTerm);
                Item matchedPeronaTag = db.SelectSingleItem(peronaDataTagsQuery);

                if (matchedPeronaTag != null)
                {
                    //Get all pattern cards which has matching persona tag as per searched keyword                
                    var query = string.Format("/sitecore/system/Marketing Control Panel/Profiles//*[@@templateid='{0}' and contains(@Persona Tags,'{1}')]", "{4A6A7E36-2481-438F-A9BA-0453ECC638FA}", matchedPeronaTag.ID);
                    Item[] allPatternCards = db.SelectItems(query);

                    foreach (Item patternCard in allPatternCards)
                    {
                        //Set current profile with respective pattern
                        var profile = Sitecore.Analytics.Tracker.Current.Interaction.Profiles[patternCard.Parent.Parent.Name];

                        Sitecore.Data.Fields.XmlField xmlData = patternCard.Fields["Pattern"];
                        XmlDocument xmlDoc = xmlData.Xml;

                        XmlNodeList parentNode = xmlDoc.GetElementsByTagName("key");
                        var scores = new Dictionary();

                        foreach (XmlNode childrenNode in parentNode)
                        {
                            if (childrenNode.Attributes["value"].Value != "0")
                            {
                                float keyValue;
                                float.TryParse(childrenNode.Attributes["value"].Value, out keyValue);
                                scores.Add(childrenNode.Attributes["name"].Value, keyValue * 3);
                            }
                        }

                        profile.Score(scores);
                        profile.UpdatePattern();
                        sw.Stop();
                        Sitecore.Diagnostics.Log.Audit("SetPatternCard took total " + sw.ElapsedMilliseconds.ToString() + " milliseconds.", new Exception());
                    }
                }
            }
            catch (Exception ex)
            {
                Sitecore.Diagnostics.Log.Error("Error occured in SetPatternCard " + ex.ToString(), new Exception());
            }

How can you confirm that it has really set the associated pattern?
Answer is simple!
You can personalize any page component based on pattern card condition. So for above Software Engineering example, you can personalize component as suggested in below screens.




This implementation will definitely add value to your digital marketing strategy!