How to avoid duplicate name while creating Sitecore item - sitecore

I'm facing a problem in my Sitecore project. As we are working in a team, we can't track all the item who has created and what name they have given. The problem is, people are creating the item with same name. This is causing some serious problem while moving the items to different environments.
What I want is, while creating the Sitecore item, pipeline method should execute and validate whether its immediate parent already has the same item name.
For example : Parent A has 3 subitems called Child1, Child2, Child3, when developer tried to create a item with name Child2 the popup/alert should display and not to allow him to create the item.
Please help me with this.

You can add your own handler to item:creating event and check if the parent already contains a child with proposed name.
Here is nice post describing how to prevent duplicates items in Sitecore. I've copied the following code from there:
<event name="item:creating">
<handler type="YourNameSpace.PreventDuplicates, YourAssembly" method="OnItemCreating" />
</event>
namespace YourNamespace
{
public class PreventDuplicates
{
public void OnItemCreating(object sender, EventArgs args)
{
using (new SecurityDisabler())
{
ItemCreatingEventArgs arg = Event.ExtractParameter(args, 0) as ItemCreatingEventArgs;
if ((arg != null) && (Sitecore.Context.Site.Name == "shell"))
{
foreach (Item currentItem in arg.Parent.GetChildren())
{
if ((arg.ItemName.Replace(' ', '-').ToLower() == currentItem.Name.ToLower())
&& (arg.ItemId != currentItem.ID))
{
((SitecoreEventArgs)args).Result.Cancel = true;
Sitecore.Context.ClientPage.ClientResponse.Alert
("Name " + currentItem.Name + " is already in use.Please use another name for the page.");
return;
}
}
}
}
}
}
}

I have a blog post out for this which uses the item create / save event and uses index search to identify duplicates. This was implemented and tested with Sitecore 7.2. Here's the config used:
<sitecore>
<events>
<event name="item:creating">
<handler type="MySite.Customizations.Customized_Sitecore.UniqueItemNameValidator, MySite" method="OnItemCreating" />
</event>
<event name="item:saving">
<handler type="MySite.Customizations.Customized_Sitecore.UniqueItemNameValidator, MySite" method="OnItemSaving" />
</event>
</events>
</sitecore>

Related

Sitecore update field just before publish

I have a need to update a date time field in Sitecore just before the item is published. This will act like a "publish date time" when the item is actually published. I have successfully implemented this in the workflow and that works fine for items in the workflow by adding a custom action.
For items not in workflow and that should be picked up by the Publish agent, I tapped into the pipeline and added a processor just before the PerformAction processor. The field gets updated fine in the master database but it's never published by the publish agent to the web database. The item with all other values before the field update goes through fine.
I have tried to debug the issue and feel like it's happening because the updated item is not reflected as part of the publishqueue. Is there a way I can force the update of the date time field also published in the same process instead of having to force it to publish?
Any suggestions are welcome.
You are right updated item is not part of the publish queue. You need to put your code into publish:itemProcessing event.
You need to follow next steps:
Add a handler class into
<event name="publish:itemProcessing" help="Receives an argument of type ItemProcessingEventArgs (namespace: Sitecore.Publishing.Pipelines.PublishItem)"/>
Your publish:itemProcessing event will look like
<event name="publish:itemProcessing" help="Receives an argument of type ItemProcessingEventArgs (namespace: Sitecore.Publishing.Pipelines.PublishItem)">
<handler type="YourNameSpace.SetPublishDate, YourAssembly" method="UpdatePublishingDate"/>
</event>
Create your own class for processing items on publish:
public class SetPublishDate
{
/// <summary>
/// Gets from date.
/// </summary>
/// <value>From date.</value>
public DateTime FromDate { get; set; }
/// <summary>
/// Gets to date.
/// </summary>
/// <value>To date.</value>
public DateTime ToDate { get; set; }
public void UpdatePublishingDate(object sender, EventArgs args)
{
var arguments = args as Sitecore.Publishing.Pipelines.PublishItem.ItemProcessingEventArgs;
var db = Sitecore.Configuration.Factory.GetDatabase("master");
Item item = db.GetItem(arguments.Context.ItemId);
if (item != null)
{
using (new Sitecore.Data.Events.EventDisabler())
{
using (new EditContext(item))
{
//PublishDateFieldName must be datetime field
item["PublishDateFieldName"] = DateTime.Now;
}
}
}
}
Depending on what you're going to use this date for, perhaps a slight different approach might be better. The previous answer is valid and will probably work just fine. But updating the master database on publish, may let the publishing engine think that the master item has changed and needs re-publishing. (EventDisabler etc will prevent this, as well as trigger a re-index and so on... Things may get very tricky)
Alternatively, you can write the publish date on the item in the web database instead.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<publishItem>
<processor type="Sitecore.Publishing.Pipelines.PublishItem.PerformAction, Sitecore.Kernel">
<patch:attribute name="type">Your.Namespace.PerformAction, Your.Assembly</patch:attribute>
</processor>
</publishItem>
</pipelines>
</sitecore>
</configuration>
And an implementation similar to this:
public class PerformAction : Sitecore.Publishing.Pipelines.PublishItem.PerformAction
{
public override void Process(PublishItemContext context)
{
base.Process(context);
if (context.Aborted || context.VersionToPublish == null || context.VersionToPublish.Source == null)
return;
var target = context.PublishOptions.TargetDatabase.GetItem(context.VersionToPublish.ID, context.VersionToPublish.Language);
if (target == null)
return;
using (new EditContext(target, false /*updateStatistics*/, true /*silent*/))
{
DateField lastPublished = target.Fields["LastPublished"]
lastPublished.Value = Sitecore.DateUtil.IsoNo;
}
}
}
John West have a blog post about this here:
http://www.sitecore.net/learn/blogs/technical-blogs/john-west-sitecore-blog/posts/2011/08/intercept-item-publishing-with-the-sitecore-aspnet-cms.aspx
Having the publish date stored in the web database, you can either read it from there instead, or create a computed index field for the master db, containing the date from the web db instead.
This can perhaps be a more robust solution, but again, it depends on what you're using the field for and if you're in control of the code reading the value.

How to configure EXM to create new messages with a custom language version, as default

I am trying to configure the EXM root for an Austrian website to create the new messages with a de-AT language version and to have the de-AT language selected as default.
My question is: How can I configure EXM to automatically create a language version for de-AT when a new message is created?
What I've done so far ..
I managed to achieve having the de-AT selected automatically by playing around with the Language - Select the target language field from the Message Context section of the Standard fields - but the actual message item that is created does not contain the de-AT version - and I am getting an error when trying to save the message.
Error message: Edited language version 'German (Austria)' could not be found. It may have been deleted by another user.
As it can be seen in this screnshot, when I open EXM and I create a new message, the de-AT language version is automatically selected. The problem is that the message has no de-AT language version assigned, so it won't allow to save the item.
I think you missed to add language version to standard values of your message template.
Templates in EXM works in a same way as anywhere in Sitecore. You should have language versions for your emails under:
/sitecore/templates/Email Campaign/Messages
/sitecore/templates/Branches/Email Campaign/Messages
None of the 'tricks' worked to automatically add a new language version when you create a new message inside EXM, therefore I've added a new OnItemSave event which checks if the Item is derived from the base Message template and creates a new language version - based on the own business logic.
Config:
<configuration xmlns:x="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:added">
<handler type="ABC.SitecoreExtensions.Handlers.EmailExperienceExtensions, ABC" method="OnItemAdded" />
</event>
</events>
</sitecore>
Code
namespace ABC.SitecoreExtensions.Handlers
{
class EmailExperienceExtensions
{
readonly Sitecore.Data.Database masterDb = Sitecore.Configuration.Factory.GetDatabase("master");
private const string EXM_BASE_EMAIL_TEMPLATE_ID = "{A0EA9681-5C86-43AB-80F7-C522DADF6F12}";
public void OnItemAdded(object sender, EventArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
Item obj1 = Event.ExtractParameter(args, 0) as Item;
if (obj1 == null)
return;
if (obj1.IsDerived(new Sitecore.Data.ID(EXM_BASE_EMAIL_TEMPLATE_ID )))
{
//logic to determine the context site and to pickup the language
....
if (rootItem == null)
{
return;
}
var siteContext = SiteContext.GetSite(rootItem.Name);
var lang = LanguageManager.GetLanguage(siteContext.Language);
Item ca = masterDb.GetItem(obj1.Paths.FullPath, lang);
using (new Sitecore.SecurityModel.SecurityDisabler())
{
try
{
if (0 == ca.Versions.Count)
{
ca.Versions.AddVersion();
}
}
catch (Exception ex)
{
// catch exception
}
}
}
}
}
}

Sitecore 8: Sync a bucket item on save

I have seen how we can provide default conditions and default actions to newly created bucket items. I also know that we can create a custom rule for building path based on custom date field.
But, how can we set the item path when the date field is and the is saved.
Consider an example. We have a bucket item template say "News" which has a date field say "Release Date". We have the settings where on item creation, the item path has the creation date like "/News/2015/09/16/item1". Now, we need to have some logic where we can change the path when the "release date" of "item1" is updated and the item is Saved.
How can we update the item path when item's release date is updated and item is Saved !! do i need to implement some logic in OnItemSaved() method ?
I already went through posts on GeekHive
The simplest way to do this would be to hook into the item:saved event and sync the bucket in there. The following code is untested:
public class ItemSavedEventHandler
{
public void Execute(object sender, ItemSavedEventArgs args)
{
Assert.IsNotNull(sender, "sender is null");
Assert.IsNotNull(args, "args is null");
// WARNING: using the events disabler is not recommended in this case.
// If you do this, the path of the item in question will not be updated and will cause issues when you edit the page and try to specify rendering data sources (the locations won't resolve)
using (new EventsDisabler())
{
var parameter = args.Item;
if (!BucketManager.IsItemContainedWithinBucket(paremeter))
{
return;
}
var bucketItem = parameter.GetParentBucketItemOrParent();
if (!bucketItem.IsABucket())
{
return;
}
BucketManager.Sync(bucketItem);
}
}
}
On a bucket with a lot of items, this will considerably slow down the save process tho.
If I understood you right, you want your bucket item path to be based on date updated rather than created? Am I right with that?
If yes, that it is not going to be a straightforward thing to do. I see the following approach to implement that.
Configure your bucket to be organised by update date, not created (you mentioned you already know how to configure that behavior). Every Sitecore item derived from Standard Template should have Statistics section where is __Updated field (with two underscores a the beginning) that automatically updates on each item save by corresponding event. You should use that field.
Once done, sync all existing items to apply that bucketing items paths.
Handle item:saved event
Within item:saved event handler: unbucket that particular item and re-bucket that item again (with item:unbucket and item:bucket commands)
Your that particular item will be bucketed implementing your bucketing path rule.
Hope that helps!
Building on some of the answers, here's the most readable / performant solution for most use cases:
using Sitecore.Buckets.Extensions;
using Sitecore.Buckets.Managers;
using Sitecore.Data.Events;
using Sitecore.Diagnostics;
using System;
using Sitecore.Data.Items;
using Sitecore.Events;
namespace XXXX.Project.Web.Infrastructure.Pipelines
{
public class MoveItemIntoBucketOnSave
{
public void OnItemSaved(object sender, EventArgs args)
{
Assert.IsNotNull(sender, "sender is null");
Assert.IsNotNull(args, "args is null");
var savedItem = Event.ExtractParameter(args, 0) as Item;
if (savedItem == null || savedItem.Database.Name.ToLower() != "master" || !savedItem.IsItemBucketable())
{
return;
}
// WARNING: see update below about EventDisabler
using (new EventDisabler())
{
if (!BucketManager.IsItemContainedWithinBucket(savedItem))
{
return;
}
var bucketItem = savedItem.GetParentBucketItemOrParent();
if (!bucketItem.IsABucket())
{
return;
}
// If you want to sync the entire bucket
// BucketManager.Sync(bucketItem);
BucketManager.MoveItemIntoBucket(savedItem, bucketItem);
}
}
}
}
I'm not worried about there being any empty bucket folders after this operation since they will be cleaned up during a full bucket sync, and content authors would not typically be traversing the bucket tree as they should be using search.
Here's the config:
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:saved">
<handler type="XXXX.Project.Web.Infrastructure.Pipelines.MoveItemIntoBucketOnSave, XXXX.Project.Web" method="OnItemSaved" />
</event>
</events>
</sitecore>
</configuration>
UPDATE: I do not recommend using the EventDisabler. If you add a new page and then try to add a rendering to the page and specify a datasource for it, the datasource locations won't resolve because Sitecore still thinks the path of newly created item is a direct child of the bucket item, rather than wherever the item was moved to within the bucket. See this question for more information.
UPDATE 2:
Note that this method will get called twice when a new bucketed item is created. You should think very carefully about what this means for you, and if you should add any other checks prior to calling any other code within this method.
You can achieve this by programmatically moving the bucketed item to the root of the bucket with the BucketManager. Doing this will force it to reevaluate the bucket rules and reorganize it:
BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);
Note that this is different from BucketManager.Sync(bucketItem) because it does not sync the whole bucket, but instead handles just the single item that was changed.
In our solutions, we typically create an item:saved event handler to do this automatically:
using Sitecore.Buckets.Managers;
using Sitecore.Buckets.Util;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Events;
using System;
using System.Text.RegularExpressions;
namespace Custom.Events.ItemSaved
{
public class ReorganizeBucketedItemInBucket
{
public void OnItemSaved(object sender, EventArgs args)
{
var bucketedItem = Event.ExtractParameter(args, 0) as Item;
// If we don't have an item or we're not saving in the master DB, ignore this save
if (bucketedItem == null || !"master".Equals(bucketedItem.Database?.Name, StringComparison.OrdinalIgnoreCase))
return;
if (!bucketedItem.TemplateID.Equals(new ID("{bucketed-item-template-id}"))) return;
var itemChanges = Event.ExtractParameter(args, 1) as ItemChanges;
// If there were no changes or the changes didn't include the date field, ignore this save
if (itemChanges == null || !itemChanges.HasFieldsChanged || !itemChanges.IsFieldModified(new ID("{field-id-of-date-field}")))
return;
Item bucketItem = bucketedItem.Axes.SelectSingleItem($"{EscapePath(bucketedItem.Paths.FullPath)}/ancestor-or-self::*[##templateid = '{{bucket-container-template-id}}']");
// If this item isn't in a bucket (or is in a bucket of another, unexpected type), ignore it
if (bucketItem == null) return;
Item parent = bucketedItem.Parent;
BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);
// Delete empty ancestor bucket folders
while (parent != null && !parent.HasChildren && parent.TemplateID == BucketConfigurationSettings.BucketTemplateId)
{
Item tempParent = parent.Parent;
parent.Delete();
parent = tempParent;
}
}
/// <summary>
/// Wraps each segment of a sitecore path with "#"'s
/// </summary>
public string EscapePath(string path)
{
return Regex.Replace(path, #"([^/]+)", "#$1#").Replace("#*#", "*");
}
}
}
And don't forget your patch config, of course:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:saved">
<handler type="Custom.Events.ItemSaved.ReorganizeBucketedItemInBucket, Custom.Events" method="OnItemSaved"></handler>
</event>
</events>
</sitecore>
</configuration>
You'll need to implement a pipeline processor.
You can do this by adding the following into a .config file in your App_Code/Include folder.
<processors>
<saveUI>
<processor mode="on" type="Namespace.ClassName, Your.Assembly" patch:after="processor[last()]" />
</saveUI>
</processor
You'll also need to implement that class - there's nothing special about it except that it must have a public Process method with a Sitecore.Pipelines.Save.SaveArgs parameter.
namespace CustomFunctions
{
public class SaveAction
{
public void Process(SaveArgs args)
{
// There's a collection of items
// I'm not sure what the situation where there's more than one item is though.
var items = args.SavedItems;
var bucket = SomeFunctionToGetParent(items);
BucketManager.Sync(items);
}
}
}
I've never actually implemented this, but I think my code should give you an idea of how to get started - though this pipeline processor would be called every time an item is saved, so you need efficient checking to make sure that the item needs to have your bucket syncing processor used.

SitecoreEventArgs Result.Cancel = true not Canceling the event

In the sitecore Content Editor I want to cancel the adding of an item.So I used the Event Handler item:addedand wrote a method to cancel the event.. But its now working, I have spending so much effort but no clue.
Here is the code:
public void MoreThenOneAddressAllowed(object sender, EventArgs args)
{
var item = Event.ExtractParameter<Item>(args, 0);
if (item.TemplateID.ToString() == Settings.GetSetting("AddressEntryTemplateID"))
{
if (item.Parent.Fields["More than one address allowed"] != null && item.Parent.Fields["More than one address allowed"].Value != "1" && item.Parent.Children.Count >= 1)
{
SitecoreEventArgs evt = args as SitecoreEventArgs;
evt.Result.Cancel = true;
Sitecore.Context.ClientPage.ClientResponse.Alert("More than one address not allowed under this item!!");
}
}
}
Here the entry in the configuration file:
<event name="item:added">
<handler type="EventHandlers.CompanyEventHandler" method="MoreThenOneAddressAllowed" />
</event>
I can see the message in the content editor. But the item is also added, some how I want to stop the adding of item.
Not sure why, exactly. But you should maybe consider implementing this rule as an Insert Rule instead. Only give editors the "Insert Address" option dynamically on elements that do not have one already.
For an example on how that could be set up, see here: http://www.newguid.net/sitecore/2011/sitecore-rules-engine-how-to-create-an-insert-option-rule/
You should use the item:creating event as this will occur earlier.
item:added will be too late!

Implementing Sitecore Multisite Robots.txt files

How to implement to have different robots.txt files for each website hosting on the same Sitecore solution. I want to read dinamically robots.txt from sitecore items.
you need to follow next steps:
1) Create and implement your custom generic (.ashx) handler.
2) In the web.config file add the following line to the section
3) Navigate to the section and add here
4) On home item you will have "Robots" field (memo, or multi line field, not richText field)
Your custom generic handler will look like :
public class Robots : IHttpHandler
{
public virtual void ProcessRequest(HttpContext context)
{
private string defaultRobots = "your default robots.txt content ";
string robotsTxt = defaultRobots;
if ((Sitecore.Context.Site == null) || (Sitecore.Context.Database == null))
{
robotsTxt = defaultRobots;
}
Item itmHomeNode = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.StartPath);
if (itmHomeNode != null)
{
if ((itmHomeNode.Fields["Robots"] != null) && (itmHomeNode.Fields["Robots"].Value != ""))
{
robotsTxt = itmHomeNode.Fields["Robots"].Value;
}
}
context.Response.ContentType = "text/plain";
context.Response.Write(robotsTxt);
}
We had similar problems especially in the multi site environment, so we used the handlers for implementing robots.txt
Create a new class inheriting from IHTTPHandler and implement the logic within the process method. Write the XML ouput to the context object.
context.Response.ContentType = "text/plain";
context.Response.Output.Write({XML DATA});
Add the custom handler and trigger.
<handler trigger="~/Handlers/" handler="robots.txt"/>
<add name="{Name}" path="robots.txt" verb="*" type="{Assembly Name and Type}" />
It seems that if you want to access Sitecore Context, and any items, you need to wait untill this stuff is resolved. The aboce method will always give you a null in the Site definition, as this isnt resolved when the filehandler kicks in.
It seems that to get the Sitecore.Context, you should implement a HttpRequestProcessor in Sitecore, that renderes the robots.txt, example on this website:
http://darjimaulik.wordpress.com/2013/03/06/how-to-create-handler-in-sitecore/
You can refer to this blog post for step-by-step explanation on how to do it with a custom HttpRequestProcessor and a custom robots settings template : http://nsgocev.wordpress.com/2014/07/30/handling-sitecore-multi-site-instance-robots-txt/