tag:blogger.com,1999:blog-52740750616226139252024-03-05T08:54:55.810-05:00Tea, sugar, snacks, and codewell ok, mostly code...Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-5274075061622613925.post-56135741838964974972016-08-16T17:54:00.001-04:002016-08-16T17:54:17.130-04:00TDS Post Deploy Step: Trigger a site publishTDS 5.5 introduced the ability to create custom post deployment steps, which are executed after a successful deployment and package install. Hedgehog is currently asking the community to contribute custom post deploy steps, so that we can <a href="http://www.teamdevelopmentforsitecore.com/post-deploy-scripts">gather a repository and share the knowledge</a> (and reduce headaches). Below is a quick contribution that will trigger a site publish after a deployment.<br />
<br />
<b>Problem</b>: We all know that sometimes a deployment adds content items that need to be published for a site to work and render properly. That's why TDS has a built-in post deploy step to publish the items that were just deployed. But what about all those content and media items that our content editors prepare in anticipation of a deployment? To publish those, we're often kicking off a full site publish.<br />
<br />
<b>Solution</b>: This post deployment step will automatically trigger a site publish after a successful deployment.<br />
<br />
<b>Installation</b>:<br />
You can download the code for the PublishSite post deploy step <a href="https://gist.github.com/ezlateva/90a3e05702389b9805db27503bd3d3fc">here</a>.<br />
If you haven't yet, check out the <a href="http://www.hhog.com/blog/creating-tds-custom-post-deploy-step/">instructions on how to add a custom post deploy step</a> to your TDS project.<br />
Once it's added to your TDS project, you can configure the following parameters:<br />
<br />
<ul>
<li><b>language </b>- one or more comma-separated languages for which the publish will be performed. If omitted, a publish will be triggered for all available languages.</li>
<li><b>target </b>- one or more comma-separated target databases. If omitted, a publish will be triggered for all available publishing targets.</li>
<li><b>mode </b>- can be either 'full', 'smart', or 'incremental'. If omitted, the default option will be 'smart'</li>
</ul>
<br />
Example:<br />
<span style="font-family: Courier New, Courier, monospace;">language=en-us,en-ca&target=cds1,cds2&mode=smart</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLVmzX5O_ktg73-4OHg8uOoUNZz0PDFSiA3X4qN31IgDvIgdALi7ncJIk_BvJPJGoysuQPVaWUPerp1A-qGTQETuftm-67XpxeQBEh3m5h9s6h8p2ENmMPMFTraAPUKZjsedUacOXNGUCy/s1600/publish_site.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="251" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLVmzX5O_ktg73-4OHg8uOoUNZz0PDFSiA3X4qN31IgDvIgdALi7ncJIk_BvJPJGoysuQPVaWUPerp1A-qGTQETuftm-67XpxeQBEh3m5h9s6h8p2ENmMPMFTraAPUKZjsedUacOXNGUCy/s640/publish_site.PNG" width="640" /></a></div>
<br />
If you have custom post deploy steps of your own, <a href="https://twitter.com/hhogdev">we'd love to hear about them</a>.Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-1692828663960783252016-02-24T16:08:00.000-05:002016-02-24T16:08:15.875-05:00TDS 5.5: Quick Shoutout<div class="MsoNormal">
This is a really quick and short shout out to the TDS 5.5 Delta Deploy feature. I’ve been using the beta
version of TDS 5.5 for a few weeks, and it's made quite the impact already. I love it! Check out this before
and after of my deployment package:<o:p></o:p></div>
<div class="MsoNormal">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvMgVLAELPLrzo5axhkzZqbNHjy6C_G4Fb-SaGMb-gtnt_5AX8U-c5DoIHt7vMp5SlcvN9fGaO7tI9JOP-YbPxuYKIWcERX-r2OirUz2nWK_-UZFGLhDWnOAgv5xP1Of-YVtN5YkMKlPFm/s1600/delta1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvMgVLAELPLrzo5axhkzZqbNHjy6C_G4Fb-SaGMb-gtnt_5AX8U-c5DoIHt7vMp5SlcvN9fGaO7tI9JOP-YbPxuYKIWcERX-r2OirUz2nWK_-UZFGLhDWnOAgv5xP1Of-YVtN5YkMKlPFm/s1600/delta1.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5L-20mkL1KEyETHD5M1yy8H2MobjhcGqjhMwAzj47pvSoUn2ZSNuzjusyVqnRZ-FTWRrmYVvbHq5rbD-4dVb672Y6nFs5dzKvNAFR__jrPKJ0Qr81JsRG7lAmNygBQElpWjQvOeSBlQRI/s1600/delta4.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5L-20mkL1KEyETHD5M1yy8H2MobjhcGqjhMwAzj47pvSoUn2ZSNuzjusyVqnRZ-FTWRrmYVvbHq5rbD-4dVb672Y6nFs5dzKvNAFR__jrPKJ0Qr81JsRG7lAmNygBQElpWjQvOeSBlQRI/s320/delta4.png" width="320" /></a>Last week, our production deployment package was 2,040KB with all items included, and installation took about 3 minutes.</div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
This week’s deployment package was only 171KB and it went through in approximately 20 seconds as it only included the items my team worked on for the current release.</div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<!--[if gte vml 1]><v:shape
id="Picture_x0020_1" o:spid="_x0000_i1025" type="#_x0000_t75" style='width:283.5pt;
height:82.5pt;visibility:visible;mso-wrap-style:square'>
<v:imagedata src="file:///C:\Users\ezlateva\AppData\Local\Packages\oice_15_974fa576_32c1d314_152c\AC\Temp\msohtmlclip1\01\clip_image004.png"
o:title=""/>
</v:shape><![endif]--><!--[if !vml]--><!--[endif]--><o:p></o:p></div>
<div class="MsoNormal">
<br />
TDS 5.5 will be available at the end of March, 2016. Contact
<a href="mailto:sales@hhogdev.com">Hedgehog Development</a> for more info! </div>
<div class="MsoNormal">
<o:p></o:p></div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-91568594521959851842015-11-24T17:00:00.000-05:002015-11-24T17:00:03.961-05:00Basics: How to publish an item programmatically to all targets in SitecoreHere's a tidbit that I found myself googling about and I couldn't find an answer to.. How to publish an item to all available publishing targets.<br />
<br />
I needed to do this to avoid hard-coding database names when publishing programmatically. So I went in to see how Sitecore does it from the ribbon command (<code>Sitecore.Shell.Framework.Commands.PublishNow</code>)<br />
<br />
After some digging around in the Kernel, I ended up with something along these lines:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Database contextDatabase = Sitecore.Context.Database;
Item itemToPublish = contextDatabase.GetItem("/sitecore/content/home"); //some item that needs to be published
//get all the available targets
List<Database> databases = new List<Database>();
ItemList targets = PublishManager.GetPublishingTargets(contextDatabase);
foreach (Item targetItem in targets)
{
Database database = Factory.GetDatabase(targetItem[FieldIDs.PublishingTargetDatabase]);
if (database != null)
{
databases.Add(database);
}
}
List<Language> languages = new List<Language>();
languages.Add(itemToPublish.Language);
//invoking the static PublishManager.PublishItem
PublishManager.PublishItem(itemToPublish, databases.ToArray(), languages.ToArray(), false, true);
</code></pre>
<br />
If working within the Sitecore Client, you will want to use <code>Sitecore.Context.ContentDatabase</code> instead of <code>Sitecore.Context.Database</code><br />
<br />
Also, if you want to publish in all languages, you can use <code>LanguageManager.GetLanguages(contextDatabase)</code> instead.
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-17009272181106774202015-09-28T16:58:00.000-04:002015-10-15T14:35:39.006-04:00Sitecore Web Forms for Marketers as a ServiceCustomizing the WFFM module for Sitecore has provided post material for quite a few blogs, including this one. It's a favorite among clients, and it's fairly extensible. However, we all know that once in a while, we get the occasional request for a marketing form, which the module simply does not work for. My last endeavor into forms included a fancy animated multiple step wizard with interdependent fields, which would be an immense undertaking to implement with WFFM versus a simple custom .net form.<br />
<br />
So the question became, how do I combine the value we get out of WFFM setup, save actions and reporting with the amazing designs and form flowcharts, which the design teams come up with and which our front end developers code into clean and beautiful html5. Especially in cases where we've already developed quite a few save actions that post to various client CRMs, create leads, and talk to third parties. Who wants to recode all that for a single custom form?<br />
<div>
<br />
<div>
So I started working towards a concept, which would provide an endpoint for WFFM form submissions. From anywhere. It is fairly simple and straight forward to implement (wait till we get to the code part), but it becomes powerful in that it allows developers to have full control over the rendered html and still take advantage of the WFFM save actions.</div>
<div>
<br />
Step 1. Run WFFM save actions on submitted form data.<br />
<br />
We'll need to define a couple of classes for consumers of the WFFM service<br />
<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="word-wrap: normal;"> public class FormData
{
public string FormId { get; set; }
public IEnumerable<FormField> Fields { get; set; }
}</code></pre>
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="word-wrap: normal;"> public class FormField
{
public string FieldName { get; set; }
public string FieldValue { get; set; }
}</code></pre>
And the meaty part - the FormProcessor, which is responsible for running the WFFM save actions, and of course has a dependency on the Sitecore and WFFM assemblies. With the help of a reflection tool, we can imitate what WFFM does behind the scenes here:<br />
<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="word-wrap: normal;"> public class FormProcessor
{
public FormProcessorResult Process(FormData data)
{
FormProcessorResult result = new FormProcessorResult();
if (string.IsNullOrEmpty(data.FormId))
{
result.Success = false;
result.ResultMessage = "Invalid Form Id";
}
else
{
bool failed = false;
ID formId = new ID(data.FormId);
FormItem formItem = FormItem.GetForm(formId);
if (formItem != null)
{
//Get form fields of the WFFM
FieldItem[] formFields = formItem.FieldItems;
//Create collection of fields
List<AdaptedControlResult> adaptedFields = new List<AdaptedControlResult>();
foreach (FormField field in data.Fields)
{
FieldItem formFieldItem = formFields.FirstOrDefault(x => x.Name == field.FieldName);
if (formFieldItem != null)
{
adaptedFields.Add(GetControlResult(field.FieldValue, formFieldItem));
}
else
{
// log and bail out
Log.Warn(string.Format("Field Item {0} not found for form with ID {1}", field.FieldName, data.FormId), this);
failed = true;
result.Success = false;
result.ResultMessage = string.Format("Invalid field name: {0}", field.FieldName);
break;
}
}
if (!failed)
{
// Get form action definitions
List<ActionDefinition> actionDefinitions = new List<ActionDefinition>();
ListDefinition definition = formItem.ActionsDefinition;
if (definition.Groups.Count > 0 && definition.Groups[0].ListItems.Count > 0)
{
foreach (GroupDefinition group in definition.Groups)
{
foreach (ListItemDefinition item in group.ListItems)
{
actionDefinitions.Add(new ActionDefinition(item.ItemID, item.Parameters)
{
UniqueKey = item.Unicid
});
}
}
}
//Execute form actions
foreach (ActionDefinition actionDefinition in actionDefinitions)
{
try
{
ActionItem action = ActionItem.GetAction(actionDefinition.ActionID);
if (action != null)
{
if (action.ActionType == ActionType.Save)
{
object saveAction = ReflectionUtil.CreateObject(action.Assembly, action.Class,
new object[0]);
ReflectionUtils.SetXmlProperties(saveAction, actionDefinition.Paramaters, true);
ReflectionUtils.SetXmlProperties(saveAction, action.GlobalParameters, true);
if (saveAction is ISaveAction)
{
((ISaveAction) saveAction).Execute(formId, adaptedFields, null);
}
}
}
}
catch (Exception ex)
{
// log and bail out
Log.Warn(ex.Message, ex, this);
result.Success = false;
result.ResultMessage = actionDefinition.GetFailureMessage();
failed = true;
break;
}
}
if (!failed)
{
// set successful result
result.Success = true;
result.ResultMessage = formItem.SuccessMessage;
}
}
}
else
{
result.Success = false;
result.ResultMessage = "Form not found: invalid form Id";
}
}
return result;
}
private AdaptedControlResult GetControlResult(string fieldValue, FieldItem fieldItem)
{
//Populate fields with values
ControlResult controlResult = new ControlResult(fieldItem.Name, HttpUtility.UrlDecode(fieldValue), string.Empty)
{
FieldID = fieldItem.ID.ToString(),
FieldName = fieldItem.Name,
Value = HttpUtility.UrlDecode(fieldValue),
Parameters = string.Empty
};
return new AdaptedControlResult(controlResult, true);
}
}</code></pre>
Step 2. Create a WebApi endpoint for clients to post to.<br />
Now that we have the basic setup, we can create a simple API controller:<br />
<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> public class WffmController : ApiController
{
[HttpPost]
public IHttpActionResult Post(FormData data)
{
FormProcessor processor = new FormProcessor();
FormProcessorResult result = processor.Process(data);
if (!result.Success)
{
return new BadRequestErrorMessageResult(result.ResultMessage, this);
}
return new OkResult(this);
}</code></pre>
Step 3. Register routes<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">
var config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute("DefaultApiRoute",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
</code></pre>
<br />
Step 4. Use with any form anywhere.<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">
var formFields = new List<FormField>();
formFields.Add(new FormField
{
FieldName = "First Name",
FieldValue = data.FirstName
});
formFields.Add(new FormField
{
FieldName = "Last Name",
FieldValue = data.LastName
});
formFields.Add(new FormField
{
FieldName = "Email",
FieldValue = data.Email
});
FormData formData = new FormData();
formData.FormId = MY_WFFM_FORM_ITEM_ID; // form item id from Sitecore
formData.Fields = formFields;
using (WebClient client = new WebClient())
{
client.UploadString(RemoteUrl, "POST", JsonConvert.SerializeObject(formData));
}
</code></pre>
</div>
<div>
<br />
Cons:<br />
- certain WFFM out-of-the-box features will be lost - validation! validation! validation!<br />
- once created, the form needs to remain fairly immutable since the form item ID and the field items are the contract with any client that will submit data.<br />
<br />
I hope that someone would find this useful the next time they're faced with a similar problem. </div>
</div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-49540277235528398532015-03-16T11:35:00.000-04:002015-03-17T14:37:10.781-04:00Sitecore error with Lucene Thai Analyzer<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;">ManagedPoolThread #1
2015:03:12 08:32:28 ERROR Exception<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;">Exception:
System.Reflection.TargetInvocationException<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;">Message: Exception has been
thrown by the target of an invocation.<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;">Source: mscorlib<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments,
Signature sig, Boolean constructor)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[]
parameters, Object[] arguments)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr,
Binder binder, Object[] parameters, CultureInfo culture)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at (Object ,
Object[] )<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Sitecore.Jobs.Job.ThreadEntry(Object state)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;">Nested Exception<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="MsoNormal">
<span style="color: red; font-family: Courier New, Courier, monospace;">Exception:
System.NotSupportedException<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="color: red; font-family: Courier New, Courier, monospace;">Message: PORT ISSUES<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="color: red; font-family: Courier New, Courier, monospace;">Source: Lucene.Net.Contrib.Analyzers<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: red;"> at
Lucene.Net.Analysis.Th.ThaiAnalyzer.ReusableTokenStream(String fieldName,
TextReader reader)</span><o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Lucene.Net.Index.DocInverterPerField.ProcessFields(IFieldable[] fields, Int32
count)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Lucene.Net.Index.DocFieldProcessorPerThread.ProcessDocument()<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Lucene.Net.Index.DocumentsWriter.UpdateDocument(Document doc, Analyzer
analyzer, Term delTerm)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Lucene.Net.Index.IndexWriter.UpdateDocument(Term term, Document doc, Analyzer
analyzer)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Sitecore.ContentSearch.LuceneProvider.LuceneUpdateContext.UpdateDocument(Object
itemToUpdate, Object criteriaForUpdate, IExecutionContext[] executionContexts)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Sitecore.ContentSearch.SitecoreItemCrawler.DoUpdate(IProviderUpdateContext
context, SitecoreIndexableItem indexable)<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"> at
Sitecore.ContentSearch.LuceneProvider.LuceneIndex.PerformUpdate(IEnumerable`1
indexableUniqueIds, IndexingOptions indexingOptions)<o:p></o:p></span></div>
<div class="MsoNormal">
<br />
In a single day, we saw this error appear over 9000 times on a production environment.<br />
<br /></div>
<div class="MsoNormal">
From what I understand (since 7.0+) Sitecore by default provides full mapping of all available Lucene.net
analyzers. They are configured under:</div>
<span style="font-family: Courier New, Courier, monospace;">indexConfigurations > defaultLuceneIndexConfiguration > analyzer > param desc="map"</span><br />
<div class="MsoNormal">
Based on the context of the
content that's indexed/searched, Sitecore will (with reflection) figure out
which mapping to use. <o:p></o:p>Here’s a great post explaining execution contexts - <a href="http://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/08/execution-contexts-explained.aspx">http://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/08/execution-contexts-explained.aspx</a></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
So the Thai Analyzer seems to be
a bit broken (read not implemented) from what I see in the Lucene.Net source. The Analyzer calls the constructor for <span style="font-family: Courier New, Courier, monospace;">ThaiWordFilter </span>with a token stream and
that constructor just throws the exception we see. You can decompile the <span style="font-family: Courier New, Courier, monospace;">Lucene.Net.Contrib.Analyzers.dll</span> or look at the source at<a href="http://lucenenet.apache.org/" target="_blank"> http://lucenenet.apache.org/</a>.<br />
<span style="font-family: Courier New, Courier, monospace;"><br />public <a href="http://lucenenet.apache.org/docs/3.0.3/d4/d5c/class_lucene_1_1_net_1_1_analysis_1_1_th_1_1_thai_word_filter.html">ThaiWordFilter</a>(<a href="http://lucenenet.apache.org/docs/3.0.3/dd/d3d/class_lucene_1_1_net_1_1_analysis_1_1_token_stream.html">TokenStream</a> input): base(input) <br />{<br /> throw new NotSupportedException("PORT ISSUES");<br /> //breaker = BreakIterator.getWordInstance(new Locale("th"));<br /> //termAtt = AddAttribute<TermAttribute>();<br /> //offsetAtt = AddAttribute<OffsetAttribute>();<br />}</span></div>
<div class="MsoNormal">
<br />
Removing or commenting out the Thai analyzer (the below <span style="font-family: Courier New, Courier, monospace;">mapEntry</span>) from the execution context mappings in the Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config should result in indexing/searching in th-TH to fall back to the standard analyzer and will get rid of the error in your log files.</div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">mapEntry</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">type</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">Sitecore.ContentSearch.LuceneProvider.Analyzers.PerExecutionContextAnalyzerMapEntry,
Sitecore.ContentSearch.LuceneProvider</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">hint</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">executionContext</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">type</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">Sitecore.ContentSearch.CultureExecutionContext,
Sitecore.ContentSearch</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">hint</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">cultureInfo</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">type</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">System.Globalization.CultureInfo,
mscorlib</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">hint</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">name</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">th-TH</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"></</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
</</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
</</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">desc</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">analyzer</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">type</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">Sitecore.ContentSearch.LuceneProvider.Analyzers.DefaultPerFieldAnalyzer,
Sitecore.ContentSearch.LuceneProvider</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">desc</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">defaultAnalyzer</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">type</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">Lucene.Net.Analysis.Th.ThaiAnalyzer,
Lucene.Net.Contrib.Analyzers</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
<</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"> </span><span style="color: red; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">hint</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">=</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">version</span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">"</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">Lucene_30</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;"></</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
</</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
</</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">param</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span><span style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><o:p></o:p></span></span></div>
<div class="MsoNormal">
<span style="font-family: Courier New, Courier, monospace;"><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">
</</span><span style="color: #a31515; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">mapEntry</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt; mso-highlight: white;">></span></span></div>
<div class="MsoNormal">
<br />
If anyone has come across this before, I'd love to hear from you!<br />
<br />
<br />
<b>Update</b>: Pavel Veller (<a href="https://twitter.com/pveller">@pveller</a>) pointed out to me that this issue has been fixed with Sitecore 7.2 Update 3. As per the release notes:<br />
<ul style="color: #4b4b4b; line-height: 15.6800003051758px;">
<li style="list-style: square outside url(http://sdn.sitecore.net/images/sdn5/bullet.gif); margin: 5px 30px 0px 0px;"><span style="font-family: inherit;">Thai Analyzer from Lucene.Net was not fully implemented and could sometimes throw Not Supported exceptions. The analyzer has been removed from the default Lucene index configuration. The default analyzer will be used instead. (420234)</span></li>
</ul>
</div>
<div class="MsoNormal">
<br /></div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-14739576088363167742015-03-11T17:29:00.000-04:002015-03-17T14:23:40.932-04:00Searchable Language Selector<div class="separator" style="clear: both; text-align: left;">
If you have ever worked in a Sitecore instance with a lot of languages, you may have noticed that sometimes it could be quite time consuming (and frustrating) to look for the language you need in the language picker. This isn't as much a developer problem as it is an issue for the content editors who often make edits in multiple languages. So, here's a quick and easy client-side solution.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The language selector is generated by an xml control located here: <span style="background-color: #eeeeee; color: #222222; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, sans-serif; font-size: 13px; line-height: 16.8999996185303px; white-space: pre-wrap;">\sitecore\shell\Applications\Content Manager\Galleries\Languages\Gallery Languages.xml</span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
A couple of modifications to add a search box, and a couple of javascript functions later, and we now have a searchable language selector:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6DCvsCI1zVYSvbEfafEM7WrlxtmRjyty58gfn6x7SY7aYHjtbOng_ZIp6HE3oG7p_a5_ec9aihCWbMTUdGmppdM6mJ8M4LvoEkVk_eY_c2l8Jhob_01dTRJAnO38NfZS3YOaU7wyocJQB/s1600/languages-1.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6DCvsCI1zVYSvbEfafEM7WrlxtmRjyty58gfn6x7SY7aYHjtbOng_ZIp6HE3oG7p_a5_ec9aihCWbMTUdGmppdM6mJ8M4LvoEkVk_eY_c2l8Jhob_01dTRJAnO38NfZS3YOaU7wyocJQB/s1600/languages-1.JPG" height="200" width="400" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNc2X4M5SrrAp7XMEH3znDSZtmcbdfmzt4Z6ToDkB0-C9VZH6C454dg3O1QX76zhCO-Q2qwf-nywNgBTr1TFWzGzKkeDYIRgdR3vsk-bPjoUwMdD6e2nUfCZ5R8zWK5Njcy9775zKWmyp-/s1600/languages-2.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNc2X4M5SrrAp7XMEH3znDSZtmcbdfmzt4Z6ToDkB0-C9VZH6C454dg3O1QX76zhCO-Q2qwf-nywNgBTr1TFWzGzKkeDYIRgdR3vsk-bPjoUwMdD6e2nUfCZ5R8zWK5Njcy9775zKWmyp-/s1600/languages-2.JPG" height="200" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
You can find the modified control up on <a href="https://github.com/ezlateva/language-selector" target="_blank">GitHub</a>. Let me know what you guys think!</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Update: This modification is now also available for download from the <a href="https://marketplace.sitecore.net/Modules/Language_Selector.aspx" target="_blank">Sitecore Marketplace</a>.</div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-89403312176660380682015-02-05T14:03:00.000-05:002015-03-13T17:37:24.037-04:00Error when rendering WFFM form<div style="background-color: white; border: 0px; clear: both; color: #222222; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 15px; line-height: 19.5px; margin-bottom: 1em; padding: 0px;">
I came across an interesting WFFM exception on a production CM environment today. It turned out to be a configuration error, so I decided to share</div>
<pre style="background-color: #ffffcc; font-family: Consolas, 'Lucida Console', monospace; line-height: 14pt; padding: 0.5em;"><span style="font-size: xx-small;">[InvalidOperationException: folder]
Sitecore.Form.Core.Configuration.ThemesManager.GetThemeName(Item form, ID fieldID) +434
Sitecore.Form.Core.Configuration.ThemesManager.GetThemeUrl(Item form, Boolean deviceDependant) +270
Sitecore.Form.Core.Configuration.ThemesManager.ScriptsTags(Item form, Item contextItem) +49
Sitecore.Form.Core.Configuration.ThemesManager.RegisterCssScript(Page page, Item form, Item contextItem) +184
Sitecore.Form.Web.UI.Controls.SitecoreSimpleFormAscx.OnInit(EventArgs e) +233
System.Web.UI.Control.InitRecursive(Control namingContainer) +186
System.Web.UI.Control.AddedControl(Control control, Int32 index) +189
Sitecore.Form.Core.Renderings.FormRender.OnInit(EventArgs e) +846
System.Web.UI.Control.InitRecursive(Control namingContainer) +186
System.Web.UI.Control.InitRecursive(Control namingContainer) +291
System.Web.UI.Control.InitRecursive(Control namingContainer) +291
System.Web.UI.Control.InitRecursive(Control namingContainer) +291
System.Web.UI.Control.InitRecursive(Control namingContainer) +291
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2098</span></pre>
<div style="background-color: white; border: 0px; clear: both; color: #222222; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 15px; line-height: 19.5px; margin-bottom: 1em; padding: 0px;">
<span style="font-family: Helvetica Neue, Helvetica, Arial, sans-serif;"><br /></span>
<span style="font-family: Helvetica Neue, Helvetica, Arial, sans-serif;">The </span>method - <span style="background-color: #eeeeee; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, sans-serif; font-size: 13px; line-height: normal; white-space: inherit;">Sitecore.Form.Core.Configuration.ThemesManager.GetThemeName(Item form, ID fieldID) - </span>looks at the Form ID that's configured as the Forms root ID in the site definition.</div>
<pre style="background-color: #eeeeee; border: 0px; color: #222222; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, sans-serif; font-size: 13px; margin-bottom: 1em; max-height: 600px; overflow: auto; padding: 5px; width: auto; word-wrap: normal;"><code style="border: 0px; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, sans-serif; margin: 0px; padding: 0px; white-space: inherit;">string formsRootForSite = SiteUtils.GetFormsRootForSite(Context.Site);
Item item = form;
if (form.TemplateID != IDs.FormFolderTemplateID)
{
item = form.Database.GetItem(formsRootForSite);
}
Assert.IsNotNull(item, "folder");
</code></pre>
<div style="background-color: white; border: 0px; clear: both; color: #222222; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 15px; line-height: 19.5px; margin-bottom: 1em; padding: 0px;">
In my case, the configured ID did not match the actual forms folder item ID in Sitecore.</div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-28254366032646162602014-10-30T18:19:00.000-04:002014-10-30T18:23:07.774-04:00Vulnerability with using the Sitecore context search indexMornings when production issues happen mean two things: no tea yet, and definitely no snacks until order is restored. So I was in a bit of a hurry today to find out what was going on when our default Sitecore content indexes seemed to have gone down. This was the exception thrown:<br />
<br />
<code>System.NullReferenceException: Object reference not set to an instance of an object. at Sitecore.ContentSearch.SitecoreItemCrawler.IsAncestorOf(Item item) at Sitecore.ContentSearch.SitecoreItemCrawler.IsExcludedFromIndex(SitecoreIndexableItem indexable, Boolean checkLocation) at
Sitecore.ContentSearch.Pipelines.GetContextIndex.FetchIndex...</code><br />
<br />
Here's a snippet that would cause the above error to be thrown:<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">Item contextItem = Sitecore.Context.Database.GetItem(SOME_ID);
if (contextItem != null)
{
ISearchIndex index = ContentSearchManager.GetIndex(new SitecoreIndexableItem(contextItem));
}
</code>
</pre>
That's just an example, but I would also see the error thrown in the __semantics field in my 'web' database.<br />
<br />
So we're failing to retrieve the ISearchIndex... but why?<br />
<br />
If you take a look at IsAncestorOf(Item item) (Sitecore 7.2+) using your favorite reflection tool, this is what you'll find:<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">// Sitecore.ContentSearch.SitecoreItemCrawler
protected virtual bool IsAncestorOf(Item item)
{
bool result;
using (new SecurityDisabler())
{
using (new CachesDisabler())
{
result = this.RootItem.Axes.IsAncestorOf(item);
}
}
return result;
}</code></pre>
No null checks here, that can't be good.<br />
The RootItem property -<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="word-wrap: normal;">// Sitecore.ContentSearch.SitecoreItemCrawler
public Item RootItem
{
get
{
if (this.rootItem == null)
{
Database database = ContentSearchManager.Locator.GetInstance<IFactory>().GetDatabase(this.database);
Assert.IsNotNull(database, "Database " + this.database + " does not exist");
using (new SecurityDisabler())
{
this.rootItem = database.GetItem(this.Root);
}
}
return this.rootItem;
}
}
</code></pre>
No null check here either. Fishy.<br />
<br />
I had to dig a bit deeper to figure out where it all originates and what exactly is null.<br />
<br />
I overwrote the <code>Sitecore.ContentSearch.Pipelines.GetContextIndex.FetchIndex, Sitecore.ContentSearch </code> pipeline processor to debug the code. It blew up trying to evaluate this:
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> System.Collections.Generic.IEnumerable<ISearchIndex> enumerable =
from searchIndex in ContentSearchManager.Indexes
from providerCrawler in searchIndex.Crawlers
where !providerCrawler.IsExcludedFromIndex(indexable)
select searchIndex;
</code></pre>
So the GetContextIndex pipeline tries to fetch all of the available crawlers, whose roots are ancestors of our indexable item (<code>IsExcludedFromIndex</code>). The ancestor check however fails on a null root item in any of the indexes.<br />
<br />
So if you remember the good old
<a href="http://stackoverflow.com/questions/14319983/sitecore-index-error-root-item-not-defined" target="_blank">"Root Item Not defined"</a> error message, it turns out this issue had the exact same cause - the RootItem for one of the search indexes was not defined in the 'web' database (i.e. the item was not yet published). Publishing is of course the quick fix.<br />
<br />
While this is in the end a configuration issue, I still feel like the unpublished site should not be affecting ALL OF SEARCH (search based on GetContextIndex that is), which is why I'm going to leave my FetchIndex override in place and add some null checks for at least a more meaningful error message.Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-4779611472751920872014-06-03T11:39:00.000-04:002015-03-17T14:25:18.514-04:00Custom rendering conditions for Sitecore presentation componentsConditional rendering rules in Sitecore are used to personalize specific components on a page based on visitor variables. It is fairly easy to extend the Rules Engine and create custom conditions to satisfy specific business needs and requirements. There are a lot of posts out there on how to create custom rules and conditions in Sitecore, so I'm going to try and keep this concise. There are three steps, The WHAT, The HOW, then Tying it all together. Enjoy!<br />
<br />
1. The WHAT<br />
The first step to creating a custom condition is defining what exactly it would do and choosing which base Sitecore condition type to inherit from. The below chart illustrates the inheritance between the base condition types in the <span style="font-family: Courier New, Courier, monospace;">Sitecore.Rules.Conditions</span> namespace:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2dHs4p49a6d2jv8dk9ipbcVXdV6O0_7vdIR1LBjT7Gl-5wl9f3SbJgVdTGFt389pKKg2kp0kKPU2Uts3yycFTuxuHJ3JZ9EWyWqOBGnAouo20Yh6X8_p3-RKXVkShl9jK29qL2djNCrNR/s1600/custom+condition+post+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2dHs4p49a6d2jv8dk9ipbcVXdV6O0_7vdIR1LBjT7Gl-5wl9f3SbJgVdTGFt389pKKg2kp0kKPU2Uts3yycFTuxuHJ3JZ9EWyWqOBGnAouo20Yh6X8_p3-RKXVkShl9jK29qL2djNCrNR/s1600/custom+condition+post+3.png" height="235" width="640" /></a></div>
<br />
<br />
In this example, we are going to allow the user to select a specific value that represents a Sitecore item on the website UI, and personalize the content we render based on that value. Since this would be similar to the <span style="font-family: Courier New, Courier, monospace;">ItemIdCondition</span>, which inherits from <span style="font-family: Courier New, Courier, monospace;">StringOperatorCondition</span><span style="font-family: inherit;">, we will go with the same base</span>.<br />
<br />
2. The HOW<br />
The below diagram illustrates the basic logic flow between our website (the almighty UI), our Sitecore rules engine component (the custom condition) and a custom value provider (a.k.a business domain code).<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSBoZT_wOwojlj_wD-7YnfgzWCHE7h8PRFDMLqoz_PlC9RuDreztPX0oSvrvGzosGXzqGYmIorpffbV4pKj5lxfBHTftS7VFzVbqDCaKjUpoeM_NNVQvv0h3arykaXNF6AxKrDpSKeTqD5/s1600/custom+condition.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSBoZT_wOwojlj_wD-7YnfgzWCHE7h8PRFDMLqoz_PlC9RuDreztPX0oSvrvGzosGXzqGYmIorpffbV4pKj5lxfBHTftS7VFzVbqDCaKjUpoeM_NNVQvv0h3arykaXNF6AxKrDpSKeTqD5/s1600/custom+condition.png" /></a></div>
This separation of concerns allows us to contain all logic regarding the storage and retrieval of our value in its dedicated ValueProvider. Whether this value comes from a database, is stored in a cookie, or is a session variable is thus irrelevant to the condition and the evaluation of the condition. It also becomes irrelevant to the website UI, which will only delegate the responsibility of getting and setting the value to the same provider as well.<br />
<br />
Here's an example of a condition class that uses a provider to retrieve the current context value. In an ideal scenario, instead of instantiated here, the provider implementation would be injected through a dependency injection container (<a href="http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx" target="_blank">pick your weapon carefully</a>).<br />
<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> public class SelectedValueCondition<T> : StringOperatorCondition<T> where T : RuleContext
{
private static ISomeValueProvider _someValueProvider;
public static ISomeValueProvider MyAwesomeProviderInstance
{
get
{
if (_someValueProvider == null)
{
_someValueProvider = new AwesomeProviderImplementation();
}
return _someValueProvider;
}
}
private ID _itemid;
public ID ItemID
{
get
{
return _itemid;
}
set
{
Assert.ArgumentNotNull(value, "item id");
_itemid = value;
}
}
public SelectedValueCondition()
{
_itemid = ID.Null;
}
public SelectedValueCondition(ID itemId)
{
Assert.ArgumentNotNull(itemId, "item id");
_itemid = itemId;
}
protected override bool Execute(T ruleContext)
{
Assert.ArgumentNotNull(ruleContext, "ruleContext");
// let's say our ValueModel class represents a domain entity that has a property named 'ID'
ValueModel model = MyAwesomeProviderInstance.GetValue();
ID modelId = !string.IsNullOrEmpty(model.ID) ? new ID(model.ID) : ID.Null;
ID itemId = ItemID;
return !modelId.IsNull && !itemId.IsNull && Compare(modelId.ToString(), _itemid.ToString());
}
}
</code></pre>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
3. Tying it all together</div>
<div class="separator" style="clear: both; text-align: left;">
Adding our condition to Sitecore is straight-forward. An important thing to remember is to set the path of the items, from which a content editor should be able to choose from:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMfeSdbqGTV2sXG02iMZpDjbR6QQ-FML-8j44ifb2zMhtrg3X8x8-_EzH6oKM6gq1OnWNgL34llhpuhp5lDvoH_-SLbiFmoH6GmfbZRnsrgieLwfQTgv03d8tJsg3j3kT5waVG3ZL1_DUr/s1600/custom+condition+post+2.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMfeSdbqGTV2sXG02iMZpDjbR6QQ-FML-8j44ifb2zMhtrg3X8x8-_EzH6oKM6gq1OnWNgL34llhpuhp5lDvoH_-SLbiFmoH6GmfbZRnsrgieLwfQTgv03d8tJsg3j3kT5waVG3ZL1_DUr/s1600/custom+condition+post+2.JPG" height="152" width="640" /></a></div>
<br />
And that is it! Now this rule is ready to be applied to any presentation component that uses a data source.Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-43226858203276377862014-05-30T11:18:00.000-04:002015-03-17T14:23:29.188-04:00Building support for multilingual content labels in Sitecore 7<div class="MsoNormal">
<span style="font-family: inherit;">When developing a multilingual site, one of the considerations
(although maybe not one of the major ones) is translating all of those labels
on buttons and other static elements on the page.<o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">There are a couple of ways you could deal with those:<o:p></o:p></span></div>
<div class="MsoNormal" style="margin-left: .5in; mso-list: l0 level1 lfo1; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list .5in; text-indent: -.25in;">
<!--[if !supportLists]--><span style="font-family: inherit;">1. <!--[endif]-->Create
a resource file to store string translations<o:p></o:p></span></div>
<div class="MsoNormal" style="margin-left: .5in; mso-list: l0 level1 lfo1; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list .5in; text-indent: -.25in;">
<!--[if !supportLists]--><span style="font-family: inherit;">2. <!--[endif]-->Use
the built-in Sitecore dictionary: </span><span style="font-family: 'Times New Roman', serif;">ex. </span><span style="font-family: 'Courier New';">Sitecore.Globalization.Translate.Text("More") </span><span style="font-family: 'Times New Roman', serif;"><o:p></o:p></span></div>
<div class="MsoNormal" style="margin-left: .5in; mso-list: l0 level1 lfo1; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list .5in; text-indent: -.25in;">
<span style="font-family: 'Courier New';"><br /></span></div>
<div class="MsoNormal">
<span style="font-family: inherit;">From the two above, I would prefer to leave all translation
responsibilities to the content editors (and possibly translation services...).
However, the Sitecore Dictionary is hidden under /sitecore/system, which is not
always available to content editors due to various permissions and access
rights. <o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<br />
<div class="MsoNormal">
<span style="font-family: inherit;">I wanted a way to allow the content editors to use a
translation layer without having to alter permissions. So I created a
Dictionary under /sitecore/content that would be accessible to content editors
to create items and language versions. Since I didn't care much about the
content hierarchy within the dictionary and I wanted quick and efficient word
lookups, I marked the DictionaryItem template as bucketable and made my
Dictionary a bucket.</span><span style="font-family: 'Times New Roman', serif; font-size: medium;"><o:p></o:p></span></div>
<div>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGUouLRwAj4I-ybA7MI4aJjBfFdKQCeVs5QzeM8o0iLns8WMZ33SlfWzjLS43G3itPfj08qIjRsbLcu6lSaQJ2McshyaCqRNZhyUB1EeqvEDq2b9ZSFPYeZjH6O_tana0LtTOcB33WvJrA/s1600/dictionary_post.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGUouLRwAj4I-ybA7MI4aJjBfFdKQCeVs5QzeM8o0iLns8WMZ33SlfWzjLS43G3itPfj08qIjRsbLcu6lSaQJ2McshyaCqRNZhyUB1EeqvEDq2b9ZSFPYeZjH6O_tana0LtTOcB33WvJrA/s1600/dictionary_post.JPG" height="192" width="640" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<span style="font-family: inherit;"><br /></span>
<br />
<div class="MsoNormal">
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">I use the name of the dictionary item as a key for my look up and
run a query to retrieve the first available search result with that name. Then
all that's left is to get the corresponding item in the language version that's
requested and return the text value. <o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">This will work when you don’t want to grant your editors access to
/sitecore/system. Content editors enter text into the item and create different
language versions, then the static elements of the page get updated in
accordance with the language version being requested. I haven’t fully tested
the performance implications of doing this vs. the built-in Sitecore Dictionary, but I don’t foresee there being any
large impact. If you have any suggestions, I’d love to hear them!</span></div>
<div class="MsoNormal">
<br /></div>
<br />
<div class="MsoNormal">
<span style="font-family: inherit;">Below is my Dictionary class:</span><span style="font-family: 'Times New Roman', serif; font-size: medium;"><o:p></o:p></span></div>
</div>
<div>
<br /></div>
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> /// <summary>
/// The Dictionary class
/// </summary>
public class Dictionary
{
private const string DICTIONARY_PATH = "/sitecore/content/Data/Dictionary";
private readonly ID DictionaryItemTemplateId = new ID("{C406784E-4E98-4671-ACC9-DCCFDF680B44}");
/// <summary>
/// Gets the dictionary value for the current language.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public string GetDictionaryValue(string key)
{
return GetDictionaryValue(key, Sitecore.Context.Item.Language);
}
/// <summary>
/// Gets the dictionary value.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="language">The language.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException"></exception>
public string GetDictionaryValue(string key, Language language)
{
string dictionaryValue;
// key is a required paramater
if (key == null)
{
throw new ArgumentNullException("key");
}
if (language != null)
{
Item dictionary = Sitecore.Context.Database.GetItem(DICTIONARY_PATH);
if (dictionary != null)
{
ISearchIndex index = ContentSearchManager.GetIndex(new SitecoreIndexableItem(dictionary));
using (IProviderSearchContext searchContext = index.CreateSearchContext())
{
SearchResultItem searchResult = searchContext.GetQueryable<SearchResultItem>()
.Where(resultItem => resultItem.Path.Contains(dictionary.Paths.FullPath))
.Where(resultItem => resultItem.TemplateId == DictionaryItemTemplateId)
.FirstOrDefault(resultItem => resultItem.Name == key);
if (searchResult != null)
{
Item dictionaryItem = Sitecore.Context.Database.GetItem(searchResult.ItemId, language);
dictionaryValue = dictionaryItem["Text"];
}
else
{
//no translation found
dictionaryValue = key;
}
}
}
else
{
// dictionary does not exist in the current context
Log.Error(string.Format("Dictionary Folder Item not found: {0}.", DICTIONARY_PATH), typeof (Dictionary));
dictionaryValue = key;
}
}
else
{
// dictionary value cannot be translated without a specified language
dictionaryValue = key;
}
return dictionaryValue;
}
}
</code></pre>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-39377674645571073042013-10-10T19:35:00.000-04:002013-10-10T19:41:18.335-04:00Sitecore upgrade hassles: From OMS Poll Module to DMS Poll ModuleI recently had to upgrade the Sitecore shared source Poll Module (<a href="http://marketplace.sitecore.net/en/Modules/Poll_Module.aspx" target="_blank">available on the Sitecore Marketplace</a>) while doing a Sitecore 6.4 to 6.5 upgrade. I couldn't find much of an upgrade path per se, so I went ahead and installed the newer version of the module. Here are a few notes to keep in mind regarding the inevitable overwrites and the necessary cleanup.<br />
<br />
All points below stem from the fact that OMS was renamed to DMS.<br />
<br />
1) Namespace changes<br />
If you are using any of the Poll Module layouts or skins in your solution, you will probably notice that the namespaces have changed from Sitecore.Polls.OMS to Sitecore.Polls.DMS. All of these will need to be updated for any customized controls to load.<br />
<br />
2) Template name changes<br />
The template name has also changed from OMS Poll to DMS Poll, so update any code and configuration where you might be using the template name.<br />
If you have made any changes to the original OMS Poll Item template, you have to be careful to merge them with the new installation package. If you're using TDS (<a href="http://www.hhogdev.com/products/team-development-for-sitecore/overview.aspx" target="_blank">Team Development for Sitecore</a>), you will be able to easily redeploy any custom fields from source control even if the new package installation deleted them.<br />
<br />
3) File name changes<br />
All file names have also changed, so after the new module installation, you will need to clean up:<br />
- \App_Config\OMPollConfig.config<br />
- \bin\OMSPollData.dll<br />
- \bin\Sitecore.Modules.OMSPoll.dll<br />
- \sitecore modules\Shell\OMS Poll Module\\*<br />
<br />
<br />Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-61075948811640601172013-01-29T12:28:00.000-05:002013-01-29T12:28:00.491-05:00Enforcing SEO-friendly URLs<div class="MsoNormal">
<span style="font-family: inherit;">Sitecore is a very extensible and flexible CMS that allows you to implement and enforce all kinds of business rules. But just because you can, does not always mean you should. URL generation for SEO is one example of what I think should be left to the editors.</span></div>
<div class="MsoNormal">
<span style="font-family: inherit;"><br /></span></div>
<div class="MsoNormal">
<span style="font-family: inherit;">The way I've seen SEO-friendly
URL enforcement implemented varies for some reason, but generally the rules
implemented are something in the line of replacing all spaces in item names with
hyphens so that ugly URLs like:<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="color: black; font-family: inherit;"><a href="http://mysite/my%20long%20item%20name">http://mysite/my%20long%20item%20name</a>
<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: inherit;">become pretty URLs like:<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="color: black; font-family: inherit;"><a href="http://mysite/my-long-item-name">http://mysite/my-long-item-name</a>.<o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">The concept of enforcing
SEO-friendly URLs has been bothering me for a while now. It started while working
on an implementation that was not as straight-forward as I expected. I
ended up using development time to discover all the enforcement rules. If you
find yourself having to implement some custom hook into the URL resolver or
write a hidden service that generates the desired URL, you are asking for
trouble. And most importantly, you're doing it wrong!<o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">Sitecore has a built-in model
for doing replacements, and it works reasonably well.<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: inherit;">Simply add replacement rules in
the <code> <encodenamereplacements> … </encodenamereplacements> </code> of the
web.config. Then to have this work successfully you would have to make <b>absolutely</b>
sure users cannot create item names that have replacement characters in them.
If you don't, you are sure to have an item that cannot be resolved at some
point. The illegal characters setting in the Web.config- <code> <setting
name="InvalidItemNameChars" … /> </code> allows you to do just that. For
example, if you are replacing spaces with hyphens, you would add the
replacement rule, and then add the hyphen as an invalid character in an item
name.<o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">With all that being said, I feel that developers should stay away from these rules to begin with. Changing the
"InvalidItemNameChars" setting could potentially limit the
extensibility of your Sitecore solution, disabling you from installing modules
that contain now "invalid" item names (or at least causing you
headaches when you try to). I've read blog posts that emphasize that
editors in general would prefer that SEO-friendly URLs are enforced so that
they wouldn't have to take the time to set them correctly.<o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: inherit;">If I were an editor though, it
would take me less time and less frustration if I could just name my item
exactly like it was going to appear in the URL rather than remember all the
replacement rules that would go on behind the scenes. It is only fair to your
solution and fair to your future users to let SEO-friendly URLs be managed by
the user in the user interface. Empower and educate your users rather than
restrict them.<o:p></o:p></span><br />
<br /></div>
<br />Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-72112363295774391842013-01-25T13:59:00.000-05:002013-01-25T17:07:33.142-05:00Sitecore field value validation: unique valueSomeone asked me today if Sitecore can validate whether an item's siblings contained unique values for a specific field. This post will describe how to create a custom field validator that will do just that.<br />
<br />
1. Create the custom field validation rule in Sitecore<br />
2. Create a class in a class library project for your code. The class should inherit from <span style="font-family: Courier New, Courier, monospace;">Sitecore.Data.Validators.StandardValidator</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiS99g8s4JZwxmxgacWpC2bmVM-bizBspGgP95XKKEKv9BqJ_TTt90Zdeir1x31lSmyNQZNHL-k_EG01KPLxZsiF09Ma5Hmyb2oVwXqVpAFfsXCxoHURMBDYuQvMfUp7M5tGCS548KtkKkD/s1600/unique_value_1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="custom field validation rule" border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiS99g8s4JZwxmxgacWpC2bmVM-bizBspGgP95XKKEKv9BqJ_TTt90Zdeir1x31lSmyNQZNHL-k_EG01KPLxZsiF09Ma5Hmyb2oVwXqVpAFfsXCxoHURMBDYuQvMfUp7M5tGCS548KtkKkD/s400/unique_value_1.PNG" title="custom field validation rule" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
3. The example here has a custom parameter defined for 'parent' item path, which would specify the sub-tree of items against which field values are validated. The query selects all items that have the same field and value for that field as the currently validated one. Starting at the 'parent'.<br />
<br />
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">
public class UniqueValueValidator : StandardValidator
{
protected override ValidatorResult Evaluate()
{
string parent = this.Parameters["parent"];
string value = base.GetControlValidationValue();
string query = string.Format("{0}//*[@{1} = '{2}']", parent, this.GetFieldDisplayName(), value);
foreach (Item item in global::Sitecore.Data.Database.GetDatabase("master").SelectItems(query))
{
//skip the current item
if (item.ID != base.GetField().Item.ID)
{
//set the error message
Text = GetText("Value must be unique. Item \"{0}\" contains the same value in field \"{1}\".", new[]{ item.DisplayName, GetFieldDisplayName()});
return ValidatorResult.Error;
}
}
return ValidatorResult.Valid;
}
protected override ValidatorResult GetMaxValidatorResult()
{
return GetFailedResult(ValidatorResult.Warning);
}
public override string Name
{
get
{
return "UniqueValue";
}
}
}
</code></pre>
<br />
It is important to note that having a custom validator in place will not prevent an editor from creating items with duplicate field values. All it will do is provide feedback, so the solution should still be able to handle duplicate field values if they do appear.Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-3174570107090210792012-09-25T15:22:00.001-04:002014-05-28T11:54:06.521-04:00Custom field type for the Sitecore Web Forms for Marketers ModuleIn my humble opinion, the Web Forms for Marketers module does an awesome job of making not-so-trivial processes like collecting and reporting on data, or tagging and emailing users rather trivial. If you've ever had to make a form with 100+ input fields (and their respective labels and validation expressions), I'm sure you would appreciate it, too.<br />
<br />
So while I'm on the custom field type theme, I'll share my experience with customizing a field 'help' text to be clickable and open a popup. This was inspired by the "What's this?" links next to CVV input fields in payment forms where a tidy popup shows you a picture of a credit card with a circled verification code.<br />
<br />
As with any custom field, the first step is to decide which of the already existing fields you want to extend. For this example, I took the <span style="font-family: Courier New, Courier, monospace;">SingleLineText </span>field and created a <span style="font-family: Courier New, Courier, monospace;">SingleLinePopupField</span>. You would need to add references to <span style="font-family: Courier New, Courier, monospace;">Sitecore.Forms.Core.dll </span>and <span style="font-family: Courier New, Courier, monospace;">Sitecore.Forms.Custom.dll</span> to the project.<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public class SingleLinePopupField : SingleLineText
{
public SingleLinePopupField()
: this(HtmlTextWriterTag.Div)
{
}
public SingleLinePopupField(HtmlTextWriterTag tag)
: base(tag)
{
}
protected override void DoRender(HtmlTextWriter writer)
{
base.DoRender(writer);
}
}
</span></pre>
<br />
Create a new field item under /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Custom/ and fill out the Assembly and Class fields. Once this is set up, you should be able to easily create a new form and add a field of your custom type.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha1oDhRiDgx0x3SfOPe1BAm4u9dGdYp_VpH7nHXgsB0VLL7IHCol4lLXGz8663WkKgtxgYr8uWsEFMd21IoKr6WyzLws-ZomTJ0L3RH94JLORc6DabVph-qg-ZvaOzrqVY8nc51uGVP18A/s1600/setup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha1oDhRiDgx0x3SfOPe1BAm4u9dGdYp_VpH7nHXgsB0VLL7IHCol4lLXGz8663WkKgtxgYr8uWsEFMd21IoKr6WyzLws-ZomTJ0L3RH94JLORc6DabVph-qg-ZvaOzrqVY8nc51uGVP18A/s400/setup.png" height="227" width="400" /></a></div>
<br />
In order to have custom properties appear in the Form Designer when you select a form field, you would need to add custom properties to the new field class. For my specific requirements, I added a link type (media library image, video, or external link that will open in a new window), link text, and the actual link.<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;"> [VisualCategory("Custom Properties")]
[VisualFieldType(typeof(LabelTypeField)), VisualProperty("Popup Link Type", 99), DefaultValue("{9975237B-B750-4A17-86C7-48D5E6D58587}")]
public string LabelLinkType { get; set; }
[VisualCategory("Custom Properties")]
[VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link Text", 99), DefaultValue("What's this?")]
public string LabelLinkText { get; set; }
[VisualCategory("Custom Properties")]
[VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link", 99), DefaultValue("")]
public string LabelLink { get; set; }
</span></pre>
<div>
<br />
The <span style="font-family: Courier New, Courier, monospace;">VisualCategory </span>attribute will group the custom fields in the Form Designer. <span style="font-family: Courier New, Courier, monospace;">VisualProperty </span>defines the field label and sort order, and <span style="font-family: Courier New, Courier, monospace;">DefaultValue </span>defines..how many cucumbers are sold obviously.<br />
<br />
I used a custom <span style="font-family: Courier New, Courier, monospace;">VisualFieldType </span>to create a drop down of link types. I added the custom link types in a new enumeration under /sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data similar to other WFFM enumerations. The custom field type should inherit from <span style="font-family: Courier New, Courier, monospace;">WebControl </span>and implement <span style="font-family: Courier New, Courier, monospace;">IVisualFieldType</span>
<br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;"> public class LabelTypeField : WebControl, IVisualFieldType
{
public LabelTypeField(): base(HtmlTextWriterTag.Select.ToString()) { }
public string DefaultValue { get; set; }
public string EmptyValue { get; set; }
public bool IsCacheable
{
get { return true; }
}
public bool Localize { get; set; }
public ValidationType Validation { get; set; }
protected virtual void OnPreRender(object sender, EventArgs ev)
{
this.Controls.Clear();
base.OnPreRender(ev);</span>
<span style="font-size: 12px; line-height: 20px;"> //Configuration.LabelLinkTypesRoot is the ID of the enumeration folder item
foreach (Item type in StaticSettings.ContextDatabase.GetItem(Configuration.LabelLinkTypesRoot).Children)
{
string str = type.ID.ToShortID().ToString();
Literal literal2 = new Literal();
literal2.Text = string.Format("<option {0} regex='{4}' value='{1}' title='{2}'>{3}</option>", new object[] { (DefaultValue == type.ID.ToString()) ? "selected='selected'" : string.Empty, str, type.DisplayName, type.DisplayName, HttpContext.Current.Server.UrlEncode(type.Fields[FieldIDs.MetaDataListItemValue].Value) });
Literal child = literal2;
this.Controls.Add(child);
}
base.Attributes["onblur"] = string.Format("Sitecore.PropertiesBuilder.onSavePredefinedValidatorValue('{0}', '{1}')", StaticSettings.prefixId + (Localize ? StaticSettings.prefixLocalizeId : string.Empty), this.ID);
base.Attributes["onchange"] = base.Attributes["onblur"];
base.Attributes["onkeyup"] = base.Attributes["onblur"];
base.Attributes["onpaste"] = base.Attributes["onblur"];
base.Attributes["oncut"] = base.Attributes["onblur"];
base.Attributes["class"] = "scFbPeValueProperty";
base.Attributes["value"] = DefaultValue;
}
public string Render()
{
this.OnPreRender(this, null);
StringWriter writer = new StringWriter();
HtmlTextWriter writer2 = new HtmlTextWriter(writer);
this.RenderControl(writer2);
return writer2.InnerWriter.ToString();
}
}
</span></pre>
<br />
Now that all properties are set up, the Form Designer should look something like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI19Bwv8PYldMJKPUMH3etxcgRi7Og0LlC8STjHSKnc6Qh0eaGUc3wSvi_hvW1Z7meBcrRQqat3FBGBT0Yt_aHhYKMADYmeknvaLHi1HlYzzfV_fLlBY58ewbbh0BQGq09XdkUd8n2KYiM/s1600/form_designer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI19Bwv8PYldMJKPUMH3etxcgRi7Og0LlC8STjHSKnc6Qh0eaGUc3wSvi_hvW1Z7meBcrRQqat3FBGBT0Yt_aHhYKMADYmeknvaLHi1HlYzzfV_fLlBY58ewbbh0BQGq09XdkUd8n2KYiM/s640/form_designer.png" height="417" width="640" /></a></div>
<br />
<br />
All that's left is to render the proper html for the custom field to achieve the popup effect. You could actually use any modal popup or video player you wish. I find the <a href="http://www.jacklmoore.com/colorbox" target="_blank">ColorBox </a>plugin very lightweight and easy to implement, so it's one of my favorites. The markup it requires is minimal as long as it's integrated and initialized properly. For my example, I included the necessary resource files onto the same layout where the WFFM form placeholder sits, and I defined css classes to initialize the popup for the different types of links. Thus, in the custom field type I would need to override the DoRender() method to render a link with the respective class and attach it to the base 'help' field value:<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">protected override void DoRender(HtmlTextWriter writer)
{
if (!string.IsNullOrEmpty(this.LabelLinkText))
{
string help = string.Empty;
if (this.LabelType == LabelLinkTypes.ImagePopup)
{
help = string.Format("{0} <a class=\"popup\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
}
else if (this.LabelType == LabelLinkTypes.VideoPopup)
{
help = string.Format("{0} <a class=\"iframe\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
}
else if (this.LabelType == LabelLinkTypes.NewPage)
{
help = string.Format("{0} <a href=\"{1}\" target=\"_blank\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLink, this.LabelLinkText);
}
this.Information = help;
}
base.DoRender(writer);
}
//Sitecore.Data.ID of the chosen drop down type item
public ID LabelType
{
get
{
return new ID(LabelLinkType);
}
}
//It might be easier for an editor to input the path to a media item instead of its url, so we'll try for that
public string LabelLinkUrl
{
get
{
string url = string.Empty;
if (this.LabelType == LabelLinkTypes.ImagePopup)
{
//media item
Item item = StaticSettings.ContextDatabase.GetItem(LabelLink);
if (item != null)
{
MediaItem mi = (MediaItem)item;
url = MediaManager.GetMediaUrl(mi);
}
}
else
{
url = LabelLink;
}
return url;
}
}</span></pre>
</div>
<div>
<br />
And that was it! The links will now be appearing next to the 'help' message below the single-line text field.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br /></div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com1tag:blogger.com,1999:blog-5274075061622613925.post-34120451243418831072012-09-05T16:59:00.000-04:002012-09-25T12:11:01.923-04:00A custom auto-complete field type for external data in the Sitecore editorFor one of our recent projects, I had to implement a custom Sitecore field that would use a web service as a data source. <a href="http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2011/08/Use-a-Custom-Field-Type-to-Read-External-XML-into-a-DropDown-List-with-the-Sitecore-ASPNET-CMS.aspx" target="_blank">John West's post</a> on doing this as a DropDown field was extremely helpful. For a basic how-to on creating custom fields, visit <a href="http://sdn.sitecore.net/Articles/API/Creating%20a%20Composite%20Custom%20Field/Adding%20a%20Custom%20Field%20to%20Sitecore%20Client.aspx" target="_blank">this SDN link</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
In my scenario, the service could potentially return thousands of items, so a simple drop-down field could easily become painful to use. Using autocomplete became a requirement. I decided to go with jQuery's autocomplete plugin. Using jQuery within the Content Editor is a bit tricky, but definitely doable. An awesome example of custom fields that make use of jQuery is the <a href="http://trac.sitecore.net/FieldTypes/" target="_blank">FieldTypes shared source module</a>.<br />
<br />
The custom scripts need to be added to the content editor before anything else is rendered. Use the <span style="font-family: Courier New, Courier, monospace;"><renderContentEditor></span> pipeline to add a processor (or use a config patch file with a patch:before="*[1]" attribute). The processor should look something like this:<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">public void Process(PipelineArgs args)
{
if (!Context.ClientPage.IsEvent)
{
HttpContext current = HttpContext.Current;
if (current != null)
{
Page handler = current.Handler as Page;
if (handler != null) {
Assert.IsNotNull(handler.Header, "Content Editor <head> tag is missing runat='value'");
handler.Header.Controls.Add(new LiteralControl("<script type='text/javascript' language='javascript' src='/sitecore/shell/custom/autocomplete.js'></script>"));
}
}
}
}
</span></pre>
<br />
For the actual custom field class, I decided to go with inheriting from the <span style="font-family: Courier New, Courier, monospace;">Sitecore.Web.UI.HtmlControls.Control</span> and stick closely to what a regular DropList field would do. The data source of the custom field would contain three parameters: the url of the service to call, the field that would be used as a key, and the field that would be used as the display text of a service "item".<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">public Dictionary<string, string> ControlParameters = new Dictionary<string, string>()
{
{"serviceurl", string.Empty},
{"textfield", string.Empty},
{"keyfield", string.Empty}
};
</span></pre>
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">private void LoadControlParameters()
{
var parameters = Sitecore.StringUtil.ParseNameValueCollection(this.Source, '|', ':');
foreach (string p in parameters.AllKeys)
{
ControlParameters[p] = parameters[p];
}
}</span></pre>
<br />
The service I was calling used json by default, so I decided that the field would support json response and used <a href="http://msdn.microsoft.com/en-us/library/system.json.jsonvalue.parse(v=vs.95).aspx">System.Json.JasonValue</a> for parsing the data:<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">protected virtual Dictionary<string, string> GetItems()
{
LoadControlParameters();
Dictionary<string, string> items = new Dictionary<string, string>();
try
{
// Call the service
string serviceResult = Sitecore.Web.WebUtil.ExecuteWebPage(ControlParameters["serviceurl"]);
dynamic json = JsonValue.Parse(serviceResult);
foreach (dynamic item in json)
{
items.Add(item[ControlParameters["keyfield"]].Value.ToString(), item[ControlParameters["textfield"]].Value.ToString());
}
}
catch
{
// invalid endpoint
Sitecore.Diagnostics.Log.Error(string.Format("{0}: Service End-Point Not Found - {1}", this.ToString(), ControlParameters["serviceurl"]), this);
}
return items;
}</span></pre>
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">private Dictionary<string, string> _suggestions;
public Dictionary<string, string> Suggestions
{
get
{
if (_suggestions == null)
{
_suggestions = GetItems();
}
return _suggestions;
}
}</span><span style="color: black; font-family: arial; font-size: 12px; line-height: 20px;">
</span></pre>
<div>
<br />
Now that we have the key value pairs of suggestions for the field, all that's left is to override the DoRender method of the base Control.<br />
<br />
<pre style="background-attachment: scroll; background-color: #f0f0f0; background-image: none; background-position: 0% 0%; background-repeat: repeat repeat; border: 1px dashed rgb(204, 204, 204); height: auto; overflow: auto; padding: 0px; text-align: left; width: 99%;"><span style="font-size: 12px; line-height: 20px;">protected override void DoRender(HtmlTextWriter output)
{
string err = null;
//check if the data source of the field is empty first
if (string.IsNullOrEmpty(this.Source))
{
err = SC.Globalization.Translate.Text("Source is not defined for this field.");
}
else
{
//check if the suggestions contain a previously saved value
//we want to show the value even if it is not returned by the service anymore
bool found = Suggestions.ContainsKey(this.Value);
//add any custom css for the field
output.Write("<link rel='stylesheet' href='/sitecore/shell/custom/autofill.css' />");
List<string> items = Suggestions.Select(a => string.Format("{0}|{1}", a.Value.Replace("'", string.Empty), a.Key)).ToList();
//output the script for the autocomplete plugin
string scr = @"
<script>
$sc(function () {
var availableTags = [
'" + String.Join("', '", items.ToArray()) + @"'
];
$sc('#au_{ID}').autocomplete({
source: availableTags,
mustMatch: true,
focus: function(event, ui) {
$sc('#au_{ID}').val(ui.item.value.split('|')[0]);
return false;
},
select: function( event, ui ) {
$sc('#{ID}').val(ui.item.value.split('|')[1]);
return false;
}
});
});
</script>
<input type='text' class='scContentControl' id=au_{ID}".Replace("{ID}", this.ID) + @" value='{Value}'/>".Replace("{Value}", found ? Suggestions[this.Value] : this.Value);
output.Write(scr);
output.Write("<input type='hidden' value='" + this.Value + "' "+ this.GetControlAttributes()+ " />");
//give the user any information that may be important
if (Suggestions.Count() == 0)
{
err = Sitecore.Globalization.Translate.Text("The service did not return any options.");
}
else if (!found && !string.IsNullOrEmpty(this.Value))
{
err = Sitecore.Globalization.Translate.Text("Value not in the selection list.");
}
}
if (err != null)
{
output.Write("<div style=\"color:#999999;padding:2px 0px 0px 0px\">{0}</div>", err);
}
}</span>
</pre>
<div>
<br /></div>
The $sc variable is what I found Sitecore was overriding the $-function with. This was implemented for and tested on Sitecore 6.5.0.110818, and I can't be sure if the same variable will work with previous Sitecore versions. You can override it with your own variable by calling "jQuery.noConflict()" and adding that piece of javascript to the pipeline processor for injecting scripts.<br />
<br />
<div>
<br /></div>
</div>
Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-35689935523804239602012-08-31T16:33:00.001-04:002012-08-31T16:33:24.319-04:00Null Sitecore Root Item... What?<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwYAhrGeUVwpiOup79PmCouhESrE3ChPDTEU6JnJJkXs4WGwygwPtm17S_3RZstZd1xB6x0BGUN10j410_X1i7x1-hHI7_1Sbf1OvJ7-orfzN1wWX1NolrgaAhhyphenhyphenib4Um1416eBN_qhE80/s1600/tree.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwYAhrGeUVwpiOup79PmCouhESrE3ChPDTEU6JnJJkXs4WGwygwPtm17S_3RZstZd1xB6x0BGUN10j410_X1i7x1-hHI7_1Sbf1OvJ7-orfzN1wWX1NolrgaAhhyphenhyphenib4Um1416eBN_qhE80/s1600/tree.png" /></a>This issue drove our development team completely insane for about three days. Well, it drove me insane. I'm sure it at least mildly agitated everyone else who I bugged to help me solve it. The Sitecore project we were working on for a client had a basic multi-site setup with a broken 'Preview'.<br />
<br />
<br />
The error manifested itself in a few ways, the most common being an 'access-denied' thrown by the Sitecore API every time you would click on the 'preview' button.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLup12DyoexReu917orzwz99JLh-YwneGc67L83Sy9GSEc4Jmzm9GZjkPIkkc9sIIHwPIJ8C-TzAHUfkNqNLUU4k5-I2aUM1HPtR1UNvkMzrlNAdhHRXIVA5t5XwUTvoZbaNTi1BCEAtTX/s1600/access-denied.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="46" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLup12DyoexReu917orzwz99JLh-YwneGc67L83Sy9GSEc4Jmzm9GZjkPIkkc9sIIHwPIJ8C-TzAHUfkNqNLUU4k5-I2aUM1HPtR1UNvkMzrlNAdhHRXIVA5t5XwUTvoZbaNTi1BCEAtTX/s320/access-denied.png" width="320" /></a><br />
<br />
<br />
<br />
<br />
The context site and database would be resolved properly. The full path of the item, however, looked like this - [orphan]/content/home/page-1. The /sitecore item was null!<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
Sitecore.Context.Database.GetItem("/sitecore") would return null as well! Actually, any item requested by path would return null. Meanwhile, we could see the item, it was definitely in the database, and the whole tree was showing up in the editor - not something you would expect if the /sitecore item really was null.<br />
<br />
To make things harder to debug, the project had a lot of framework customizations, a couple of modules installed, and we were dealing with an upgraded database. In the end, we found that the issue was caused by the 'Hide version' field being checked on the /sitecore item. This is easily reproducible on a clean Sitecore (we tried 6.4.1.101221). Why you would change anything on the /sitecore item is a different topic.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjspeOeWMyLzAVdRPLgmtNFGJGWVCCKGAuTsiE2ApNFcqNUinlvYF7WXSmr-rWnNyLo_ovYzQ3bKgGhYLHQpI_viBaSuudWOTBfh-s6vSz__Rb8qvRz5h83YjJb1vDsqIz1IzJmAc-4o-mS/s1600/hide-version.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="355" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjspeOeWMyLzAVdRPLgmtNFGJGWVCCKGAuTsiE2ApNFcqNUinlvYF7WXSmr-rWnNyLo_ovYzQ3bKgGhYLHQpI_viBaSuudWOTBfh-s6vSz__Rb8qvRz5h83YjJb1vDsqIz1IzJmAc-4o-mS/s400/hide-version.png" width="400" /></a></div>
<br />
<br />
The approach we took in troubleshooting after we gave up debugging was fairly straight-forward. Install a clean Sitecore of the same version as the project and start adding things to it slowly to see where and when it will break. By doing so, we were able to determine that the problem was not caused by a bug in code and was hiding somewhere in the database. Using TDS (<a href="http://www.teamdevelopmentforsitecore.com/" target="_blank">Team Development for Sitecore</a>), we actually migrated all project items as well as all code to the clean instance, and preview was still working properly. Finally, by comparing the values of all fields (shared, versioned, and unversioned) for the /sitecore item in the clean master database vs. the problem master database, we found the difference for the 'Hide version' field. Sigh of relief.<br />
<br />
<br />Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-70835165693508266852012-07-23T15:23:00.000-04:002015-03-17T14:28:42.186-04:00Extracting data from the Sitecore Web Forms for Marketers ModuleThe Web Forms for Marketers module for Sitecore comes with an extensive reporting tool inside the Sitecore Desktop. Information about submissions and activity on forms can be viewed in the reports already provided by the module. Recently, I had to create a custom report on WFFM forms that would live outside of the Desktop, so I decided to put together the basics on accessing form items and entries.
<br />
You would need a reference to the Sitecore.Forms.Core.dll<br />
<br />
<div>
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">private void Example()
{
string formID = "{FCD67950-6473-4962-B090-B4821BDB2C80}";
ItemUri uri = new ItemUri(Sitecore.Data.ID.Parse(formID), Sitecore.Context.Database);
//1. Get Form
FormItem form = new FormItem(Database.GetItem(uri));
string name = form.FormName;
//2. Data Filters
List<GridFilter> filters = new List<GridFilter>();
// 2.a Form filter
filters.Add(new GridFilter(Sitecore.Form.Core.Configuration.Constants.DataKey, formID, GridFilter.FilterOperator.Contains));
// 2.b Get archived items
filters.Add(new GridFilter(Sitecore.Form.Core.Configuration.Constants.StorageName, Sitecore.Form.Core.Configuration.Constants.Archive, GridFilter.FilterOperator.Contains));
//3. Get all entries
IEnumerable<IForm> entries = Sitecore.Forms.Data.DataManager.GetForms().GetPage(new PageCriteria(0, 0x7ffffffe), null, filters);
// 3.a Apply custom filtering on the entries
entries = entries.Where(a => a.Timestamp.Date.CompareTo(startDate) >= 0 && a.Timestamp.Date.CompareTo(endDate) <= 0);
//4. Create a form packet
FormPacket packet = new FormPacket(entries);
CustomProcessor export = new CustomProcessor();
string result = export.Process(form, packet);
}
</code></pre>
</div>
<br />
1. Get the form<br />
Use the FormItem constructor, passing in the inner data item, which, like any other item, can be grabbed based on its ID from the Context.Database. The FormItem class will give you access to various form properties such as the form.Introduction, form.Footer, and form.FormName as well as all the form fields and save actions.<br />
<br /><br />
2. Data filters<br />
There are two filters that are <b>obligatory </b>in order to use the Sitecore.Forms.Data.DataProvider. To grab entries for a specific form, you will need to apply a GridFilter on the DataKey ("dataKey") field where the criteria would be the ID of the form. And the second filter specifies whether you are getting entries out of the archive or not. <br />
<br />
3. Get the entries<br />
Having defined the grid filters, you can now grab all entries using the DataManager.<br />
<br />
4. Create a form packet<br />
The FormPacket makes it easy to ship a packet of entries that you've already filtered out off to a processor. For example, this could be a processor for a specific file type.Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0tag:blogger.com,1999:blog-5274075061622613925.post-73552913237334267042012-07-06T15:39:00.000-04:002015-03-17T14:27:40.373-04:00Custom log files for the Sitecore CMSI realize that in most cases, the purpose of doing this would be to avoid the hassle of digging through tons of Sitecore log messages to find your own. In my case, I really wanted to separate the custom messages so as to avoid polluting the Sitecore logs. There's actually a lot of information out there on how to write your custom log messages into a separate log file, so I'm not going to go into that. There is an extensive post by <a href="http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2011/05/All-About-Logging-with-the-Sitecore-ASPNET-CMS.aspx" target="_blank">John West on Logging with Sitecore</a>, which can get you started on successfully separating log entries into a new file and to the point where I found myself. My custom messages were successfully written to a separate file, but they also continued to get appended to the regular Sitecore logs.<br /><br />
I had to do some digging to configure the regular logs to ignore my custom messages, so I'm writing this in the hopes that I might save someone else the digging.<br />
<br />
I ended up adding a filter to the LogFileAppender that would deny messages containing my custom string. The message would be denied on match.<br />
<br />
<div>
<pre style="background: none repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"><appender name="LogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
<file value="$(dataFolder)/logs/log.{date}.txt"/>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="YOUR_CUSTOM_STRING" />
<acceptOnMatch value="false" />
</filter>
<appendToFile value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n"/>
</layout>
</appender>
</code></pre>
</div>Elena Mosoffhttp://www.blogger.com/profile/01233400222347589829noreply@blogger.com0