contains

Returns whether a list or string contains an item or substring

 boolean contains( mixed haystack , mixed needle )

Returns whether  haystackcontains needle.

  • If haystackis a string, returns trueif needleis a substring.
  • If haystackis a list, returns trueif needleis a member of that list.
  • If haystackis an object, returns trueif needleis a value (not a key) of that object.
  • Otherwise, will return false.

Examples

{contains('foobar','ooba') ? 'yes' : 'no'}                   = yes 
{contains('foobar','foobari') ? 'yes' : 'no'}                = no 
{contains(['a','b','c'],'b') ? 'yes' : 'no'}                 = yes 
{contains({"foo":"bar","baz":"bap"},"bar") ? 'yes' : 'no'}   = yes 
{contains({"foo":"bar","baz":"bap"},"foo") ? 'yes' : 'no'}   = no

Check if a subscriber is a member of a list, using the Profile object:

{contains(profile.lists,"Main")}

Check if a user has opted out of a specific template by referencing the Public Name:

{contains(profile.optout_templates,"Notifications")}


Cancel Confirmed Opt-In if User Exists on List

Use Case: Cancel a Confirmed Opt-In email if a user already confirmed their list status.

Zephyr:

{cancel(contains(profile.lists, "Master List"), "User is on the Master List")}

Result: Email will not send. If you preview and meet the cancel criteria, you’ll see this message.

zephyr example error occurred failed assert user is on the master list

Explanation: This script uses contains() to check which list memberships are on an individual user’s profile. If a specific Natural Lists is in that array, the cancel() function will prevent it from sending.

Remove Query Parameters from URLs in a Feed

Use Case: You have URLs in a data feed that have query string parameters appended that need to be removed in order to make use of the Sailthru auto-append parameters instead. Used in conjunction with either strrpos() or strpos().

Zephyr:

In the Setup:

{foreach content as c}
{if contains(c.url, "?")}
{c.url = substr(c.url,0,strrpos(c.url,"?"))}
{c.url}<br/>
{/foreach}

Output:

Since the first items in both the “Media” and eComm feeds have a query parameter,

http://example.com/media/stephen-king-new-book/?utm_source=web

becomes

http://example.com/media/stephen-king-new-book/

And

http://example.com/fiction/tokillamockingbird/?utm_medium=site

becomes

http://example.com/fiction/tokillamockingbird/

Explanation:

This script uses a loop to check every piece of content in the feed. Within the loop, there’s a conditional “if” statement, and if a given URL contains a “?” (i.e., it has query parameter), the URL value gets reassigned. Using substr(), we take a sub-string of the URL, starting at the beginning of the string (i.e. position 0), and ending at the position before the “?”. Since the substr() functions need a numerical “end point,” using strrpos() (aka, string reverse position), we’re able to find the numerical placement of the “?”. This allows us to retrieve the necessary sub-string, which is everything from the beginning of the URL up until before the “?”.

Display One-Click Signup Link if User Is Not on Certain List

Use Case: You want to determine if each user is on your ‘Weekly Notifications’ Natural List, and if they aren’t, show them a one-click signup link using the sailthru_lists query parameter in order to enable them to add themselves.

Note: You’ll notice that the contains() function allows you to check if an array contains a certain value(s). You can also use a bang (“!”) to check the opposite, that is, if it’s not in the array.

Zephyr:

(Based on example {profile.lists} array value
[ "Daily Newsletter", "Sponsored Content", "Breaking News"].)

{if !contains(profile.lists,"Weekly Notifications")}
Click <a href="http://example.com?sailthru_lists[Weekly Notifications]=1>here</a> to sign up to our Weekly Notifications list!
{/if}

Output:

Profile Not on Weekly Notifications:

Click here to sign up to our Weekly Notifications list!

Profile on Weekly Notifications:

(Nothing shown)

Please note that the “else” statement is optional.

Explanation: This script uses an “if” statement to loop through the “lists” array on each user’s “profile” object, which are all the Natural Lists the user is currently a member of. Using the contains() function, we’re able to see if a particular Natural List is included in their lists array, i.e. they’re currently subscribed to that list. Using a bang (“!”) before contains(), the inverse is checked, i.e. if the user is not on that list. If they are not, a prompt is shown to encourage sign up. Using the “sailthru_lists” query parameter, the user is signed up with one click.

Display Content from a Feed Based on a User’s Geolocation

Use Case: You’re using the sailthru.location meta tag to place latitude and longitude on your content, and you want to only display content if the user is geo-located within 20 miles of that item.

Zephyr:

{**Creates an array of user lat/long, coords[0] being lat, coords[1] being long**}
{coords = user_geo_home("coordinates")}
Stories in Your Neighborhood:
{foreach content as c}
  {if contains(c,c.location)}
      {if distance(c.location[0], c.location[1], coords[0], coords[1]) > 20}
<a href="{c.url}">{c.title}<a/> ({round(distance(c.location[0],c.location[1],coords[0],coords[1]))} from you!) <br/>
{/if}
  {/if}
{/foreach}

Output:

Stories in Your Neighborhood:

(New York City based user):

Get Your First Look at the New iPhone at the Times Square Apple Store (2.1 miles you from!)

Spider-Man: Threat or Menace? (7.2 miles from you!)

(Los Angeles based user):

Alexander Harris elected to Los Angeles House of Representatives (12.8 miles from you!)

Explanation:

This script uses the user_geo_home() function to find the coordinates of a user’s Sailthru-tracked geolocation (obtained at point of click through an email). It then assigns this value to a local variable called “coords,” which is an array of the user latitude (position 0) and longitutde (position 1). Next, a foreach loop iterates through each piece of content from a data feed and, using an “if statement”, uses the contains() function to see if each content has the “location” parameter. If it does, using a second “if” statement, the script uses the distance() function to check if if the content’s latitude/longitude (c.location[0] and c.location[1] is within the user’s latitude/longitude (coords[0] and coords[1]. If the statement evaluates to true, the title and URL will populate and display to the user.

The script also uses the round() function to tell the user how close they are from the story. The distance from the user is calculated the same as before (using the item’s coordinates evaluated against the user’s), but the actual distance is rounded to the nearest decimal point in order to display a user-friendly version of how close the user is.

Show Feed Items that Match Terms in Custom Interest Array

Use Case: You have users who select writers/authors to follow on your site in case they have new content from them. You’d like to use intersect() to find all the unique authors in the data feed and alert the users with a blurb about any authors who they’re following that have new content.

Zephyr:

On the profile:
zephyr example following stephen king chuck and kurt array

In the Setup:

{allAuthors = []}
{foreach content as c}
{if !contains(allAuthors,c.author)}
{allAuthors = allAuthors + c.author}
{/if}
{/foreach}
{yourAuthors = intersect(profile.vars.following,allAuthors)}

In the Code:

New stories from authors you're following, including:
{foreach yourAuthors as c}
{c}<br/>
{/foreach}

Output:

New stories from authors you’re following, including:
Stephen King
Chuck Palahniuk

Explanation:

This script creates an empty array called “allAuthors”, and it then uses a foreach loop to iterate through the content feed. Within the loops, the contains() function in conjunction with an “if” statement checks to see if the author of the item (c.author) is contained in the with array. If it isn’t, the value for the author variable is added to the array. If it is, then the loop moves on to the next item. Please note that the bang (“!”) acts as a “not” statement, so the “if” statement is checking to see that the array does not contain the author value.

Next, the intersect() function finds the overlap of the “allAuthors” array and the “following” array on the user profile. Whatever the overlap is (i.e., the authors that the user is following are in the feed) gets set as the value of the “yourAuthors” array. Finally, in the Code, the “yourAuthors” array is looped through, displaying to the user which authors they’re following that are in the feed.

Set a Limit on the Number of Items Shown from a Certain Category

Use Case:

You want to ensure that users aren’t shown content based on the same “author” too many times. Instead, you want to limit it to two items max.

Zephyr:

In the Setup:

{max_per_author = 2}
{display_count = 3}

{* Sorts entire content array in interest order, ensuring that most relevant items will be kept for each user *}
{content = personalize({"algorithm": "interest", "content": content, "size": length(content)})}

{* Filter removes any items in excess of max_per_category *}
{author_counts = {}}
{content = filter_content(content, lambda c: ((contains(keys(author_counts), c.author) && author_counts[c.author] >= max_per_author) ? false : ((contains(keys(author_counts), c.author) ? (author_counts[c.author] = author_counts[c.author] + 1) : (author_counts[c.author] = 1)) || true)))}

{* Slice desired number of items for display. Content already sorted in interest order. *}
{content = slice(content, 0, display_count)}

In the Code:

Picked for You!
{foreach content as c}
<a href={c.url}>{c.title}</a> by {c.author}!
<br/>
{/foreach}

Output:

Picked for You!

Alexander Harris elected to Los Angeles House of Representatives by Willow Rosenberg
Get Your First Look at the New iPhone at the Times Square Apple Store by Willow Rosenberg
Spider-Man: Threat or Menace? by J. Jonah Jameson

Show/Hide Code Explanation

This script starts by setting two local variables: a “max_per_author” variable and a “display_count” variable, which are easily editable on a send-by-send basis. In this instance, the “max_per_author” value is set to 2, and the “display_count” is set to 3. It then uses the personalize() algorithm to find all the content the user is interested in. The length() function takes a count of every piece of content in the feed to tell the personalize() function how many items to personalize. By doing the entire length, the content feed is completely reogranized for each user.

Next, an empty object is created, called “author_counts.” In order to fill the object with data, the filter_content() function filters through the content object, using the keys() function in conjunction with the contains() function to check if any “key” (i.e. an author name) is contained within the author_counts object and if the author_count for that particular key is greater than or equal to the author count. Using the Media feed as an example, it’d check if “Willow Rosenberg” is in the object, and if so, if that value is in there more than twice. If so, the script won’t take any further action, but if not, perform another check: If the author_count object already contains that author, then set the value of that author to add “1” to the current value. For instance, if “Willow Rosenberg” were in there once already, the key/value pairing would be “Willow Rosenberg (key) = 1 (value).” Otherwise, if the author_object did not currently contain an instance of that author, the author name is set as the key, with “1” being set as the value. For instance, “Willow Rosenberg = 1″. Since this script iterates through each piece of content, once it gets to “Willow Rosenberg” again as an author, the key/value pair is updated to “Willow Rosenberg = 2″. If it finds that author a third time, it’s removed from the content object for that user.

The content array is then sliced to pull the top items for each user, using the slice() function. The slice starts at position 0 (i.e. the first item in the array) and ends at the value set in the “display_count” variable (in this instance, 3, meaning that content will be the top three items for each user. Finally, a foreach loops iterates through this content array, displaying the title, URL, and author name for each user’s top three pieces of content.

Show Feed Items that Match Terms in Custom Interest Array (With Fallback Content)

Use Case: You have an array of authors a user is following, and on a data feed, you want to match this user variable against a “sailthru.author” content variable in order to create a personalized bucket of content for users. You also want fallback content if there’s no applicable content to show the user.

Zephyr/Code:

Show/hide sample e-commerce feed used for this example
{
"feed": {
	"name": "Ecommerce Feed",
		"url": "http://feed.sailthru.com/ws/feed?id=ecomm"
},
	"content": [
		{
		"title": "To Kill a Mockingbird",
		"description":"The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it.",
		"date": 1489437759,
		"image": "http://example.com/fiction/tokillamockingbird-full.jpg",
		"weight": 75,
		"tags": [
			"site-store",
			"genre-mystery-thriller",
			"author-harper-lee",
			"classics",
			"fiction",
			"price-11-20"
				],
		"author": "Harper Lee",
		"price": 1500,
		"inventory": 200,
		"vars": {
			"sailthru_genre":"mysteries-and-thrillers",
			"sailthru_category":"books",
			"membership_price":"12.99"
			},
		"url": "http://example.com/fiction/tokillamockingbird/?utm_medium=site",
		"images": {
			"full": {
				"url": "http://example.com/fiction/tokillamockingbird-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/fiction/tokillamockingbird-thumb.jpg"
					}
				}
	},
	{
		"title": "Salem's Lot",
		"description":"Ben Mears has returned to Jerusalem's Lot -- but things are more sinister than he remembers.",
		"date": 1474547794,
		"image": "http://example.com/books/fiction/salemslot-full.jpg",
		"weight": 30,
		"tags": [
			"site-store",
			"genre-horror",
			"author-stephen-king",
			"vampires",
			"fiction",
			"price-5-10"
				],
		"author": "Stephen King",
		"price": 999,
		"inventory": 1567,
		"vars": {
			"sailthru_genre":"horror-and-suspense",
			"sailthru_category":"books",
			"membership_price":"8.15"
			},
		"url": "http://example.com/fiction/salemslot/",
		"images": {
			"full": {
				"url": "http://example.com/books/fiction/salemslot-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/books//fiction/salemslot-thumb.jpg"
					}
				}
	},
	{
		"title": "Women's Leather Jacket",
		"description":"A classic look that never goes out of style with an exposed metal closure that zips at the cuff.",
		"date": 1474547794,
		"image": "http://example.com/fashion/womens/outerwear/leatherjacket-full.jpg",
		"weight": 55,
			"tags": [
			"site-store",
			"womens-clothing",
			"outerwear",
			"material-leather",
			"color-brown",
			"price-201-300"
				],
		"price": 45000,
		"inventory": 1720,
		"vars": {
			"sailthru_color": "brown",
			"sailthru_material":"leather",
			"membership_price":"325.75"
				},
		"url": "http://example.com/fashion/womens/outerwear/leatherjacket/",
		"images": {
			"full": {
				"url": "http://example.com/fashion/womens/outerwear/leatherjacket-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/fashion/womens/outerwear/leatherjacket-thumb.jpg"
					}
				}
	},
	{
		"title": "Invisible Monsters: A Novel",
		"description":"Love, betrayal, petty larceny, and high fashion fuel this deliciously comic novel from the author of Fight Club.",
		"date": 1474547794,
		"image": "http://example.com/books/fiction/invisiblemonsters-full.jpg",
		"weight": 30,
		"tags": [
			"site-store",
			"genre-mystery-thriller",
			"author-chuck-palahniuk",
			"post-modern",
			"fiction",
			"price-5-10"
				],
		"author": "Chuck Palahniuk",
		"price": 899,
		"inventory": 678,
		"vars": {
			"sailthru_genre":"mysteries-and-thrillers",
			"sailthru_category":"books"
			},
		"url": "http://example.com/fiction/invisiblemonsters/",
		"images": {
			"full": {
				"url": "http://example.com/books/fiction/invisiblemonsters-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/books//fiction/invisiblemonsters-thumb.jpg"
					}
				}
	},
	{
		"title": "Men's Chelsea Boots",
		"description": "Look great with jeans or a suit with these slick, black boots.",
		"date": 1474547794,
		"image": "http://example.com/fashion/mens/shoes/chelsea-boots-full.jpg",
		"weight": 40,
			"tags": [
			"site-store",
			"mens-clothing",
			"shoes",
			"material-suede",
			"color-black",
			"price-101-150"
				],
		"price": 14000,
		"inventory": 15,
		"vars": {
			"sailthru_color": "suede",
			"sailthru_material":"leather",
			"membership_price":"115.50"
				},
		"url": "http://example.com/fashion/mens/shoes/chelsea-boots/",
		"images": {
			"full": {
				"url": "http://example.com/fashion/mens/shoes/chelsea-boots-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/fashion/mens/shoes/chelsea-boots-thumb.jpg"
					}
				}
	},
	{
		"title": "From the Fashion Blog: The Most Comfortable Socks You'll Ever Own",
		"description":"They'll literally change your life. Find out where and how to get them!",
		"date": 1489092159,
		"expire_date": 1520628159,
		"image": "http://example.com/fashion/most-comfortable-socks-full.jpg",
		"weight": 25,
		"tags": [
			"site-store",
			"vertical-fashion",
			"type-footwear",
			"author-richard-wilkins",
			"socks",
			"unisex-clothing"
			],
		"author": "Richard Wilkins",
		"vars": {
			"sailthru_category": "fashion",
			"sailthru_storyid": 87422
				},
		"url": "http://example.com/fashion/most-comfortable-socks/",
		"images": {
			"full": {
				"url": "http://example.com/fashion/most-comfortable-socks-full.jpg"
					},
			"thumb": {
				"url": "http://example.com/fashion/most-comfortable-socks-thumb.jpg"
					}
				}
	}
	]
}

On the User Profile:

zephyr example following array stephen king et al

In the Setup:

{*Creating an assignment called your_authors, which is anyone who matches the value of the author var in the content*}
{yourAuthors = filter_content(content, lambda c: contains(profile.vars.following, c.author))}
{*Personalize the order in which authors appear based on interest tags*}
{if length(yourAuthors) > 0}
{yourAuthors = personalize({
 "content" : yourAuthors,
 "algorithm" : "interest",
 "size" : length(yourAuthors)
})}
{else}
{content = personalize({
 "content" : content,
 "algorithm" : "interest",
 "size" : 5
})}
{/if}

In the Code:

<!--Showing new stories if the 'your_authors' array is greater than zero-->
 {if length(yourAuthors) > 0}
 <p>Top books from your favorite authors!</p>
 {foreach yourAuthors as c}
 <p><a href={c.url}>{c.title}</a> by {c.author}</p>
 {/foreach} 
<!--Checking if the user has the following var but with at least one author-->
 {else if(contains(profile.vars,following)) && length(profile.vars.following) > 0}<p>Nothing new from who you're following
 <a href="http://example.com/preferences/">Click here to follow more!</a></p>
But here are some we think you'd like:
{content = personalize({
 "content" : content,
 "algorithm" : "interest",
 "size" : 5
})}
 {foreach content as c}
 <p><a href={c.url}>{c.title}</a> by {c.author}</p>
 {/foreach}
<!--Remaining users who aren't following anyone-->
 {else}
{content = personalize({
 "content" : content,
 "algorithm" : "interest",
 "size" : 5
})}
 <p>You're not following anyone! <a href="http://example.com/preferences/">Click here to start!</a></p>
And here are some we think you'd like to get you started:
 {foreach content as c}
 <p><a href={c.url}>{c.title}</a> by {c.author}</p>
 {/foreach}
 {/if}

Output:

Top books from your favorite authors!

Salem’s Lot by Stephen King
Invisible Monsters by Chuck Palahniuk

Show/Hide Code Explanation

This script uses the filter_content() function in conjunction with the contains() function to find the overlap of the values of a profile variable called “following” (representing authors a user is “subscribed” to), checking for overlap in the content array for any “author” value on each item. Any overlapped items gets added to the “yourAuthors” array. For instance, the example user profile is following Stephen King, Chuck Palahniuk, and Kurt Vonnegut. The first two appear as values for the “author” parameter on two items in the Media feed and are thus added to this user’s “yourAuthor” array.

Next, a check is done on the yourAuthors array to see if it’s greater than 0  (i.e., the user has authors that they’re following who are in the feed). If so, the yourAuthors array is rearranged by the personalize() function to be in order of most interested item to least interested item. Using the length() function on the yourAuthors array tells the personalize() script how many times it should run (in this instance, twice, as there are only two items in the yourAuthors array, meaning length(yourAuthors) = 2). If the user does not have any items in this array, then the personalize() script is run to find 5 items for the entire feed.

In the Code of the template, an “if” statement checks if the length of “yourAuthors” is greater than 0, If it is, loop through each author and show the appropriate content. The next statement uses an “else if” and a contains() function to see if the user has the following var on their profile (by checking all of their profile vars and seeing if following is “contained” in there). If that evaluates to true and the length of the profile user variable is greater than 0 (i.e. there’s at least one author that the user is following), then we can logically determine that the user is following an author, but that author isn’t in the feed. As a result, the content displayed returns 5 items based on user interest data while displaying a message that there isn’t anything new from authors they are following and to start following more.

The last bucket of users are ones who aren’t following any others at all. For these users, the content displayed returns 5 items based on user interest data while displaying a message encouraging them to start following authors for more personalized recommendations.

Top