Thursday, November 15, 2018

Sending SMS messages with Sitecore EXM

I attended Pete Navarra's inspiring "EXM Live!" breakaway session at the Sitecore Symposium. An audience member asked whether it was possible to send SMS messages through EXM. Pete gave an answer, but I had a super short chat with him after, telling our approach to this.
I promised another audience member who overheard to post an overview of our solution here - I'm sorry I haven't gotten to it until now!

This was made a while ago, meaning it's for Sitecore 8.2 and EXM 3.4 (update 2)

Creating a message type


First I needed to create a new message type. Since an SMS can only send text, I used Sitecore's TextMail as a base. First step is to make a new template in Sitecore, which has Sitecore's Plain Text Message as base template.
Having this template, I went ahead and created a message type in my project, including all the usual suspects. The "IsCorrectMessageItem" implementation uses the ID of my new template to identify the version.

public class SMS : TextMail
{
 private readonly TextMailSource _curSource;

 protected SMS(Item item) : base(item)
 {
  this._curSource = (base.Source as TextMailSource);
 }

 public new static bool IsCorrectMessageItem(Item item)
 {
  return ItemUtilExt.IsTemplateDescendant(item, "{185B8860-3DFE-4B9E-A5DD-04A2D7DF3EC2}");
 }

 public static SMS FromItemEx(Item item)
 {
  if (!SMS.IsCorrectMessageItem(item))
  {
   return null;
  }
  return new SMS(item);
 }

 public override string GetMessageBody(bool preview)
 {
  return _curSource.Body;
 }

 public override object Clone()
 {
  SMS sms = new SMS(base.InnerItem);
  CloneFields(sms);
  return sms;
 }
}

Friday, November 9, 2018

Publish viewer/canceller using Sitecore Powershell Extensions

I have only just recently jumped the Sitecore Powershell Extensions (SPE) bandwagon, but I absolutely love it.
Most of the tasks I've used it for so far has been either very straight forward, or very client specific. However we were just asked by a client to make a publish viewer which would list details of current publish actions and queue, as well as being able to canceling queued items.
Similar applications has existed in the past, but it didn't seem like any of them worked properly on Sitecore 8.2 and 9.0 - SPE to the rescue!

First a couple of functions

Most of this could exist in one long script, I just really like splitting them up into individual scripts for easier reuse.

Get-PublishJobs

First off I made a function to get all jobs from the jobmanager, filter them to only see the PublishManager jobs, and then sorting them by PublishDate. This gives returns me a PowerShell array of relevant jobs.

function Get-PublishJobs() 
{
  $category = 'PublishManager'
  [Sitecore.Jobs.JobManager]::GetJobs() | 
           Where-Object -FilterScript { $_.Category -eq $category } | 
           sort @{expression={$_.Options.Parameters[0].PublishDate};Descending=$false}
}

Monday, August 27, 2018

Creating the Sitecore 9 SXA search component indexes

I recently started playing around with SXA for the first time to prepare for an upcoming SXA project. I love the concept and implementation. Unfortunately I had a problem getting the search components working - they were added to the page just fine, but no results showed.

The problem


In my browsers network tab I could see that the API returned status 400: "Object reference not set to an instance of an object".

In my log files I could find some further info:

7248 10:19:18 WARN Results endpoint exception
Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.ContentSearch.SolrProvider
at Sitecore.ContentSearch.SolrProvider.SolrFieldNameTranslator.StripKnownCultures(String fieldName)
at Sitecore.ContentSearch.SolrProvider.SolrFieldNameTranslator.StripKnownExtensions(String fieldName)
at Sitecore.ContentSearch.SolrProvider.SolrFieldNameTranslator.ProcessFieldName(String fieldName, String fieldTypeKey, Type returnType, CultureInfo culture, String returnTypeString, Boolean aggressiveResolver)
at Sitecore.ContentSearch.SolrProvider.SolrFieldNameTranslator.GetIndexFieldName(MemberInfo member, CultureInfo culture)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitItemProperty(MemberExpression expression)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitBinary(BinaryExpression expression)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitBinary(BinaryExpression expression)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitWhereMethod(MethodCallExpression methodCall)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitWhereMethod(MethodCallExpression methodCall)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitWhereMethod(MethodCallExpression methodCall)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitWhereMethod(MethodCallExpression methodCall)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.VisitCountMethod(MethodCallExpression methodCall)
at Sitecore.ContentSearch.Linq.Parsing.ExpressionParser.Parse(Expression expression)
at Sitecore.XA.Foundation.Search.Spatial.GenericQueryable`2.GetQuery(Expression expression)
at Sitecore.ContentSearch.Linq.Parsing.GenericQueryable`2.Execute[TResult](Expression expression)
at Sitecore.XA.Feature.Search.Controllers.SearchController.GetResults(String v, String q, String s, String l, String g, String o, Int32 e, Int32 p, String sig, String site, String itemid)
Not terribly informative. I tried checking whatever relevant resources I could find on setting up SXA and search, such as setting_up/sxa_searchconfiguring/configure_sxa_indexing, and the SXA installation guide but all with no luck.

The solution

The solution is actually quite simple - the indexes aren't created automatically during installation and must be manually created. Easy peasy - once you think of it.
I'm unsure of the "right" way to do this, since it's not documented, but what I did was these simple steps:

  1. Go to [solrpath]\server\solr and copy [mysitename]_master_index folder, name it sitecore_sxa_master_index
  2. Go into that folder, open core.properties and replace [mysite]_master_index to sitecore_sxa_master_index
  3. Delete the data folder
  4. Repeat steps 1-3 for web
  5. Restart the solr service
  6. Reindex the 2 new indexes from the controlpanel
This fixed my issues and search worked beautifully.

I posted on the Sitecore #sxa Slack that I believed this documentation was missing - and was later asked for a solution, so I thought I'd post it here as well, to help others find it.

Next version of SXA is supposed to automatically install these cores if you use SIF install.


Thursday, August 16, 2018

pipeline.debug - a GUI tool for runtime debugging Sitecore pipelines

TL;DR

pipeline.debug is a tool that lets you inject flexible debug processors to any pipeline during runtime.

You can read the documentation and download sourcecode at: https://github.com/alphasolutionsrepo/pipeline.debug


Adding a debug processor to the httpRequestBegin pipeline.


Wednesday, June 20, 2018

FIFA World Cup part 4 of 4 - XConnect

Part 4 in a series of 4
  1. Introduction
  2. Sitecore Forms
  3. Marketing Automation
  4. XConnect
Everyone's signed up, matches has started and scoring is underway. Now all we need to do is visualize who predicted what - and more importantly, a leaderboard.

To do that I need to fetch the data from XConnect. Again, I was happy to find it well documented by Sitecore. Even better, the API seems easy to use if you're familiar with LINQ. Unfortunately almost too easy for this post. I had imagined myself having to setup some indexing and doing some clever work to get my data out as I wanted it. But between the ease of use of the API, and the simplicity of my datamodel for what I need here, it's basically down to a copy/paste of a few lines from the documentation.

Friday, June 15, 2018

FIFA World Cup part 3 of 4 - Marketing Automation

Part 3 in a series of 4
  1. Introduction
  2. Sitecore Forms
  3. Marketing Automation
  4. XConnect
Having my contestants signed up, I'm ready to watch some football (yes, football!). As results starts rolling in, I need to evaluate the bets and award points.
I have chosen to use the Marketing Automation module for this. Meaning that I need to create a plan for each match.

Now, I could have gone the easy way and created all the plans before opening the signup, and just let the start action enroll contacts once they were awarded the goal in the form. However that would require that I had actually gotten that far before I opened the forms. I figured out another way, I'll get back to that later.

FIFA World Cup part 2 of 4 - Sitecore Forms

Part 2 in a series of 4
  1. Introduction
  2. Sitecore Forms
  3. Marketing Automation
  4. XConnect
Call me crazy, but for a successful office pool, having contestants is among my top priorities.
I made the signup form using Sitecore Forms. The signup itself could easily be made by the built-in components, just create "Radio button list" with [1, x, 2] options for each match.
However, being a developer, I'll be damned if I spend around 20 minutes setting up this individually for each of the 48 matches - Let's spend hours programming it instead!

Thursday, June 14, 2018

FIFA World Cup part 1 of 4 - what a wonderful excuse to learn Sitecore 9

Introduction

Ever since Sitecore 9 was announced at the symposium my fingers have been itching to try out the new features. This has been overdue for half a year now, but finally time has allowed me to take a deeper dive into Sitecore 9.
Luckily FIFA had their planning spot on for me to apply my training to a project; make an office pool for the outcome of the World Cup group matches. My weapons of choice:
  1. Basic Sitecore
    Define the groups and matches in Sitecore for use throughout the steps.
  2. Sitecore Forms
    Create form with a custom fieldtype which reflects the groups and matches.
    Identify the contact via a save action.
    Save the answers to a new contact facet.
    Award a custom goal for contact list segmentation.
  3. Sitecore Marketing Automation
    Create a custom form action for awaiting match result and scoring contacts.
    Create a default plan and automatically duplicate when each match is concluded.
  4. Sitecore XConnect
    Gather data to display leaderboards and statistics.

Whether the chosen route is the most direct is debatable, I concede that parts of this could have been much easier by purely custom code - but the goal of this exercise is to learn, fail and inspire.

I won't cover every step I have taken, but will share my thoughts and try to link the relevant documentation and blog posts that helped me along my way.

Part 1 - Basic Sitecore

While the basic Sitecore setup brings nothing new to the table, I will do a quick rundown for reference on the later steps.

The idea is to create each individual match only once manually. The forms and Marketing Automation will use these as datasource. These matches will also have a layout, to be able to show statistics and scoring for the individual matches.

Without further ado, this is the setup:

Screenshot of the matches folder and data

Enrolling a contact list to a Marketing Automation plan

The new Marketing Automation in Sitecore 9 has a lot of built in options to automatically enroll contacts as they trigger goals or events, or satisfy custom conditions. However this is largely focused around user actions and not marketings desire to enroll a group of contacts into a plan.

I was posed with a problem where I needed to enroll contacts that had already ended their interaction. For this I made a simple service that would enroll all contacts from a given Sitecore List Manager list into my Marketing Automation plan.

Making this was surprisingly easy by combining the enrolling a contact in a single plan and the list manager api guides from Sitecores' documentation.

Working with the Marketing Automation api programatically

The new Marketing Automation interface is awesome and it has a lot of great functionalities. However it seems that much of the implementation has been made with the interface/frontend in mind.

I wanted to be able to duplicate a plan programmatically, much like the "Copy" button in the interface.

The copy plan button in the Marketing Automation interface