Sitecore update field just before publish - sitecore

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.

Related

Change sitecore item without redirect

I have a custom pipeline processor inserted after ItemResolver in which I overwrite the current context item with a new item selected by the content editor from a droplink.
If I go to that dynamic item via a normal request through my website and through my processor and i change my context item, it will stil render the same item:
public override void Process(HttpRequestArgs args)
{
// some code
Context.Item = dropLink.TargetItem;
}
Strangely, if I issue a request via the item API, sitecore changes the item successfully
//api call
Context.Item = Context.Database.SelectSingleItem("fast:/sitecore/content/mysite/dynamicitem");
Here is my config file:
<pipelines>
<httpRequestBegin>
<processor patch:after="* #type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" type="MyDll.Web.Pipelines.LandingPageResolver,MyDll.Web" />
</httpRequestBegin>
</pipelines>
Since you are using MVC the item is resolved (again) using a different set of pipelines, so you need to patch into there instead.
The GetFromRouteUrl processor in the mvc.getPageItem pipeline sets args.Result to the item matching the requested URL which is then eventually set to Context.Item, so it essentially resets the item back to the "correct" item based on the URL and that overwrites the changes you made earlier.
You need to add a need processor to the mvc.getPageItem with some logic to check if the context item has already been resolved.
Update your code in the ItemResolver and store a boolean to indicate that you have already resolved using custom logic, this saves having to run the resolving logic twice:
public override void Process(HttpRequestArgs args)
{
// some code
Context.Item = dropLink.TargetItem;
Context.Items["custom::ItemResolved"] = true;
}
Create a new Class that checks if your custom logic has already resolved the item:
public class CheckItemResolved: GetPageItemProcessor
{
public override void Process(GetPageItemArgs args)
{
if (args.Result == null)
{
var resolved = Sitecore.Context.Items["custom::ItemResolved"];
if (MainUtil.GetBool(resolved, false))
{
// item has previously been resolved
args.Result = Sitecore.Context.Item;
}
}
return;
}
}
And then patch this in:
<pipelines>
<mvc.getPageItem>
<processor type="MyProject.Custom.Pipelines.CheckItemResolved, MyProject.Custom"
patch:before="*[#type='Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromRouteUrl, Sitecore.Mvc']" />
</mvc.getPageItem>
</pipelines>
The pipeline immediately after is GetFromFromUrl() which would normally set args.Result by re-resolving the Item. By setting it back to Context.Item that processor will break out early and leave your previous logic alone.
You can find more details about MVC and Pipelines in the documentation.

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.

How can I pass some information in to a Sitecore Webforms for Marketers custom message processor pipeline?

I have a site built using Sitecore 7.5 and Webforms for Marketers 2.5. I am trying to create a custom email message processor pipeline command that will change the TO field for the email before it goes out. But the proper email address needs to come from a Session variable.
Here is my class:
public class CustomEmailMessageProcessor
{
public void Process(ProcessMessageArgs args)
{
//Change the TO address for the email based on the selection in the Subject field
var subjectField = args.Fields.GetEntryByName("Subject");
if (subjectField == null)
{
return;
}
//The value of the selected item will be the ID of a Subject Option
var selectedSubjectOptionItem = Sitecore.Context.Database.GetItem(new ID(subjectField.Value));
if (selectedSubjectOptionItem == null)
{
return;
}
var selectedSubjectOption = selectedSubjectOptionItem.GlassCast<Contact_Us_Subject_Option>();
//Based on the currently selected Region in the Session variable, get the proper
//child of selectedSubjectOption to populate the TO field
???
if (args.To.Length != 0)
{
args.To.Append(",");
}
args.To.Append(proper email address goes here);
}
}
And here is my associated config:
<processMessage>
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="ExpandLinks" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="ExpandTokens" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="AddHostToItemLink" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="AddHostToMediaItem" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="AddAttachments" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="BuildToFromRecipient" />
<!-- Custom setting -->
<processor type="myProject.CustomEmailMessageProcessor, myProject" method="Process" />
<processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" method="SendEmail" />
</processMessage>
The user will select a Subject from a dropdown list on the form. The value of each Subject will be the Guid of a corresponding item. That subject item will have children. Each child will have an email address and will correspond to a Region item in the system. I will then find the child item that matches the Region ID that is stored in Session. Then I will have the correct email address to send the email to.
However I have no idea how to access the Session variable from within the pipeline (or if it is even possible). It doesn't have to be Session. I am happy to pass in the currently selected Region in some other fashion. I just need some way to pass information in that can be accessed from the pipeline code.
Thanks,
Corey
When using the Sitecore WFFM webservice, the save action is run on the CMS, shell site. The save action don't know anything about the user session.
A different more standard solution is create a (custom) Field hidden, and set there the value you can use to find the needed email address.
Create a custom save action and do there your e-mail sending.
Note: If you do not have a good identifier to find the email address watch out with putting an email address or to simple identifier in a (hidden) form value, it can be abused.
Example of a hidden field, based on a <input type="text" .....> for Webforms
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sitecore.Form.Web.UI.Controls;
namespace StockpickSitecore.Controls.WFFM.CustomFields
{
public class HiddenEmailfield : SingleLineText
{
private static readonly string baseCssClassName = "scfSingleLineTextBorder";
private string CssClassName { get; set; }
public int MaxLength
{
get
{
return this.textbox.MaxLength;
}
set
{
this.textbox.MaxLength = value;
}
}
public int MinLength { get; set; }
public new string CssClass
{
get
{
return base.CssClass;
}
set
{
base.CssClass = value;
}
}
public HiddenEmailfield()
{
this.Text = "info#yourdomein.com";
}
protected override void OnInit(EventArgs e)
{
this.Attributes.Add("style", "display: none;");
this.textbox.CssClass = "scfSingleLineTextBox";
this.help.CssClass = "scfSingleLineTextUsefulInfo";
this.generalPanel.CssClass = "scfSingleLineGeneralPanel";
this.title.CssClass = "scfSingleLineTextLabel";
this.textbox.TextMode = TextBoxMode.SingleLine;
this.Controls.AddAt(0, (Control) this.generalPanel);
this.Controls.AddAt(0, (Control) this.title);
this.generalPanel.Controls.AddAt(0, (Control) this.help);
this.generalPanel.Controls.AddAt(0, (Control) this.textbox);
}
}
}
Change the constructor HiddenEmailfield and set the this.Text with the Session value.
Creat a item in Sitecore below /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Custom/
based on Template: /sitecore/templates/Web Forms for Marketers/Field Type
Fill in the Assembly : StockpickSitecore for Example
Fill in the Class: StockpickSitecore.Controls.WFFM.CustomFields.HiddenEmailfield
Now you can use HiddenEmailfield in you webform als field type

Set queryable source on Rendering Parameter Template field

I have a Rendering Parameter template applied to a sublayout. It has a single Droptree field on it, and I want to set the Source of that field to a Sitecore query so I can limit the options available for that field.
Source can be:
query:./*
or
query:./ancestor-or-self::*[##templatename='MyTemplate']/
The query just needs to grab items relative to the content item that we're on. This normally works with Droptree fields in the content editor.
However I'm finding that the query isn't working here because we're in the rendering parameters, so it's not using the content item as it's context.
The query fails and I just get the full Sitecore tree.
I found this can be fixed up for the Datasource field with 'Queryable Datasource Locations' at this link:-
http://www.cognifide.com/blogs/sitecore/reduce-multisite-chaos-with-sitecore-queries/
However I don't know where to start to get this working for other rendering parameter fields.
Any ideas? (I'm using Sitecore 6.6 Update 5)
Unfortunately, the pipeline mentioned in Adam Najmanowicz's answer works for some other types, like Droplink and Multilist, but the pipeline isn't run for Droptree fields.
After looking into this deeper I found that the Source of a Droptree field IS using the wrong context item, as Adam mentioned, but the code comes from the Droptree field itself:-
Sitecore.Shell.Applications.ContentEditor.Tree, Sitecore.Kernel
Utilising the query string code from Adam's answer, we can create a 'fixed' Droptree custom field, that is almost the same as the regular Droptree but will use the correct context item instead.
The code will inherit from the normal Tree control, and only change the way that the Source property is set.
public class QueryableTree : Sitecore.Shell.Applications.ContentEditor.Tree
{
// override the Source property from the base class
public new string Source
{
get
{
return StringUtil.GetString(new string[]
{
base.Source // slightly altered from the original
});
}
set
{
Assert.ArgumentNotNull(value, "value");
if (!value.StartsWith("query:", StringComparison.InvariantCulture))
{
base.Source = value; // slightly altered from the original
return;
}
Item item = Client.ContentDatabase.GetItem(this.ItemID);
// Added code that figures out if we're looking at rendering parameters,
// and if so, figures out what the context item actually is.
string url = WebUtil.GetQueryString();
if (!string.IsNullOrWhiteSpace(url) && url.Contains("hdl"))
{
FieldEditorParameters parameters = FieldEditorOptions.Parse(new UrlString(url)).Parameters;
var currentItemId = parameters["contentitem"];
if (!string.IsNullOrEmpty(currentItemId))
{
Sitecore.Data.ItemUri contentItemUri = new Sitecore.Data.ItemUri(currentItemId);
item = Sitecore.Data.Database.GetItem(contentItemUri);
}
}
if (item == null)
{
return;
}
Item item2 = item.Axes.SelectSingleItem(value.Substring("query:".Length));
if (item2 == null)
{
return;
}
base.Source = item2.ID.ToString(); // slightly altered from the original
}
}
The above code is pretty much the same as the Source property on the base Tree field, except that we figure out the proper context item from the URL if we've detected that we're in the rendering parameters dialog.
To create the custom field, you just need to edit the Web.Config file as described here. Then add the custom field to the core database as described here.
This means that parameters can now have queries for their source, allowing us to limit the available items to the content editor. (Useful for multi-site solutions).
The key here would be to set the Field Editor's context to be relative to the item you are editing instead of the Rendering parameters (that I think it has by default).
So you could have processor:
public class ResolveRelativeQuerySource
{
public void Process(GetLookupSourceItemsArgs args)
{
Assert.IsNotNull(args, "args");
if (!args.Source.StartsWith("query:"))
return;
Item contextItem = null;
string url = WebUtil.GetQueryString();
if (!string.IsNullOrWhiteSpace(url) && url.Contains("hdl"))
{
FieldEditorParameters parameters = FieldEditorOptions.Parse(new UrlString(url)).Parameters;
var currentItemId = parameters["contentitem"];
if (!string.IsNullOrEmpty(currentItemId))
{
Sitecore.Data.ItemUri contentItemUri = new Sitecore.Data.ItemUri(currentItemId);
contextItem = Sitecore.Data.Database.GetItem(contentItemUri);
}
}
else
{
contextItem = args.Item;
}
}
}
hooked as:
<sitecore>
<pipelines>
<getLookupSourceItems>
<processor patch:before="*[#type='Sitecore.Pipelines.GetLookupSourceItems.ProcessQuerySource, Sitecore.Kernel']"
type="Cognifide.SiteCore.Logic.Processors.ResolveRelativeQuerySource, Cognifide.SiteCore" />
</getLookupSourceItems>
</pipelines>
</sitecore>
Together with ResolveQueryableDatasources from Przemek's blog this should solve your problem.

Where To Intercept Item Locking/Editing In Sitecore

Is there a pipeline I can add to and/or event to intercept that would allow me to set the lock on an item only if a lock in the foreign system is possible? [Mainly interested with this functionality in the Media Library]
We are currently looking to provide syncing functionality with Sitecore items and I'm running through a checklist of use cases to determine feasibility. Locking an item currently needs to lock an item in the foreign system.
I read John West's blog post explaining the interception of Item Updates.
If you need to be able to block the save operation, consider the saveUI pipeline, the item:saving event, or field validators. If you need to prevent the visual effect in the user interface that indicates that Sitecore has saved the selected item, consider the saveUI pipeline or field validators.
This is useful to me later when I need to possibly block the saving of an item because of an error in the foreign system, but it does not state anything as the locking/editing begins.
I also noted on John's Important Pipelines blog post that there is no "uiEditItem" or anything that seems to note a pipeline with item editing.
You could propably place your own processor before "Sitecore.Pipelines.Save.CheckItemLock" in the saveUI section.
In your processor you can abort the saving of an item by calling the AbortPipeline method.
If you want to check if the saved item gets locked, you can check if the lock field has changed.
Here is some example code:
public class CheckForeignLock {
protected bool UserIsTryingToLockItem(SaveArgs args) {
var lockfield = args.Fields.SingleOrDefault(x => x.ID == FieldIDs.Lock);
return lockfield != null && lockfield.OriginalValue != lockfield.Value;
}
protected bool CanLock() {
// your code
}
public void Process(SaveArgs args) {
if(UserIsTryingToLockItem(args) && !CanLock()) {
args.AbortPipeline();
}
}
}
And here the place where you could register your processor:
<processor mode="on" type="Sitecore.Pipelines.Save.ParseXml, Sitecore.Kernel" />
<processor mode="on" type="YourAssembly.CheckForeignLock, YourAssembly" />
<processor mode="on" type="Sitecore.Pipelines.Save.CheckItemLock, Sitecore.Kernel" />