Unsupported iframe urls in Sitecore page editor mode

I was working with Sitecore 6.5 when I ran into this issue with a client application.

If the Html (in a rich text field in this case) had an embedded iframe with a youtube url or a flickr slideshow, trying to perform any action on the page in page editor mode / preview mode would result in a javascript error, as in no save, no publish, nothing!

2014-05-25_155557

I saw the error:

Uncaught SecurityError: Blocked a frame with origin “http://mysite-local.com” from accessing a frame with origin “https://www.youtube.com“. The frame requesting access has a protocol of “http”, the frame being accessed has a protocol of “https”. Protocols must match.

This occurred even though I was accessing my site using http, and i tried accessing the youtube URL

  • specifically with http
  • without any protocol – which ideally means it should match the current browser url protocol.

But the issue persisted.

Here’s how we got around it:

The content author will need to add these potential iframe tags as anchor tags in the rich text field. These anchor tags could mandate a special attribute which would be the indicator for this custom code to trigger and transform the marked anchor tags into iframes only when the page mode is normal. (FYI, This js issue will also interfere with the ‘preview’ mode)

The content author will now enter this:

2014-05-25_155746

  • Add a processor in the <renderField> pipeline (Web.Config)

<processor type="MySite.RenderExternalLinks, MySite" />

  • For the condition when page mode is normal – search for all anchor tags within the rtf and if the special attribute to indicate transform is present, replace this with an iframe tag. You can you ‘data-‘ attributes to allow the content author to add additional attributes which will be applicable to the rendered iframe tag.

public class RenderExternalLinks
 {
 public void Process(RenderFieldArgs args)
 {
 Assert.ArgumentNotNull(args, "args");
 Assert.ArgumentNotNull(args.FieldTypeKey, "args.FieldTypeKey");

 if (args.FieldTypeKey != "rich text"
 || String.IsNullOrEmpty(args.FieldValue)
 || !Context.PageMode.IsNormal)
 return;

 // Load the HTML into the HtmlAgilityPack
 var doc = new HtmlDocument { OptionWriteEmptyNodes = true };
 doc.LoadHtml(args.Result.FirstPart);

 // Search for all links
 var aTag = doc.DocumentNode.SelectNodes("//a");
 if (aTag == null || aTag.Count == 0)
 return;

 foreach (HtmlNode node in aTag)
 {
 // Look for links to YouTube
 if (node.Attributes["href"] != null && node.Attributes["data-transform"] != null && node.ParentNode != null
 && (node.Attributes["href"].Value.Contains("youtube.com") || node.Attributes["href"].Value.Contains("flickr.com")))
 {
 node.ParentNode.InnerHtml
 = node.ParentNode.InnerHtml.Replace(node.OuterHtml,
 "");
 }
 }
 // Replace the Rich Text content with the modified content
 args.Result.FirstPart = doc.DocumentNode.OuterHtml;
 }
 }

So now the content author can made all the settings needed using the anchor tag and this will get rendered as the intended iframe tag in normal mode.

ASP.Net Caching and Sitecore (HttpContext.Current.Cache vs. HttpRuntime.Cache)

This post is about using ASP.Net caching with sitecore, separate from the item caching that sitecore itself provides. (Sitecore Caching)

We are all possibly aware of the way to use the cache accessible using the HttpContext.Current.Cache property. In the scenario of Sitecore, we might also want to add the functionality to clear all cached data on any publish. Sitecore itself does clear all the data that it caches using the following event at the end of the publish process:


<event name="publish:end">
 <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
 <sites hint="list">
 <site>website</site>
 </sites>
 </handler>
 </event>
 <event name="publish:end:remote">
 <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
 <sites hint="list">
 <site>website</site>
 </sites>
 </handler>
 </event>

But this will only clear the keys which Sitecore itself created.

If there was any data which you have manually cached in your site, they will remain unaffected.

Say we used the following to insert data into the cache:

HttpContext.Current.Cache.Insert("Key", "Value");

And then to the publish pipeline we add the following:

<pipelines>
 <publishItem help="Processors should derive from Sitecore.Publishing.Pipelines.PublishItem.PublishItemProcessor">
 <processor type="MySite.Classes.ClearHttpCache, MySite" />
 </publishItem>
</pipelines>

Which would look something like this:

using Sitecore.Publishing.Pipelines.PublishItem;

namespace MySite.Classes
{
 public class ClearHttpCache : PublishItemProcessor
 {
 public override void Process(PublishItemContext context)
 {
 var cacheEnumerator = HttpContext.Current.Cache.GetEnumerator();
 while (cacheEnumerator.MoveNext())
 HttpContext.Current.Cache.Remove(cacheEnumerator.Key.ToString());
 }
 }
}

When we go ahead and implement this, you’ll see that this still doesn’t work!
Here comes the distinction between HttpContext.Current.Cache and HttpRuntime.Cache.

The cache is stored in the session established for the front end site, while we are attempting to clear this cache in a session established for the sitecore editor (a different session). And HttpContext.Current.Cache is not shared across instances. HttpContext.Current.Cache internally does use HttpRuntime.Cache which infact is shared across instances for a given app domain.

Using HttpRuntime.Cache is the key here which will enable us to achieve this functionality. You could define a prefix for the keys which you would want cleared out on publish if you wouldn’t want all the keys in cache cleared.

So you’d use the following:

// Insert
HttpRuntime.Cache.Insert("Key", "Value");

// Access
var cacheValue = HttpRuntime.Cache["Key"];

And to clear the cache – on publish:

using Sitecore.Publishing.Pipelines.PublishItem;

namespace MySite.Classes
{
 public class ClearHttpCache : PublishItemProcessor
 {
 public override void Process(PublishItemContext context)
 {
 var cacheEnumerator = HttpRuntime.Cache.GetEnumerator();
 while (cacheEnumerator.MoveNext())
 HttpRuntime.Cache.Remove(cacheEnumerator.Key.ToString());
 }
 }
}