Run code when Publishing Restriction is saved in Sitecore - sitecore

I need to run code when an author saves a publishing restriction for an item.
How would I go about doing that?

The time restrictions are stored in the "__Valid to" and "__Valid from" fields. Attach a new pipe like this:
<event name="item:saved">
<handler type="Test.ValidTest, Test" method="OnItemSaved" />
</event>
And then test if those fields changed and do your thing:
public class ValidTest
{
private static readonly ID __Validfrom = new ID("{C8F93AFE-BFD4-4E8F-9C61-152559854661}");
private static readonly ID __Validto = new ID("{4C346442-E859-4EFD-89B2-44AEDF467D21}");
public void OnItemSaved(object sender, EventArgs args)
{
Item obj = Event.ExtractParameter(args, 0) as Item;
if (obj == null)
return;
//if (!(obj.TemplateID == YourTemplateId)) //restrict this to a limited set of templates if possible
// return;
try
{
ItemChanges itemChanges = Event.ExtractParameter(args, 1) as ItemChanges;
if (itemChanges != null &&
(itemChanges.FieldChanges.Contains(__Validfrom) || itemChanges.FieldChanges.Contains(__Validto)))
{
//YOUR THING here
Log.Info("Changed!", (object)this);
}
}
catch (Exception ex)
{
Log.Error("failed", ex, (object)this);
}
}
}

Related

Sitecore Publishing Restriction Override

I have two publishing target - one is stage and one is production.The publishing on Production should adhere to the publishing restrictions, but publishing on Stage should not look at or discard the valid to and valid from dates and publish under any circumstance.
I have written a publishing pipeline (PipelinePublishProvider). I am not sure how could I manage to overwrite the field values temporarily so it publishes on to stage every-time.
public class StagePublishOverride : PipelinePublishProvider
{
public override PublishHelper CreatePublishHelper(PublishOptions options)
{
Assert.ArgumentNotNull(options, "options");
if (options.TargetDatabase.Name.ToLower() == "stage")
{
Item itemToBePublished = new Item(options.RootItem.ID, options.RootItem.InnerData, new Database("web"));
itemToBePublished.Editing.BeginEdit();
itemToBePublished.Publishing.ValidTo = DateTime.MaxValue;
itemToBePublished.Publishing.ValidFrom = DateTime.MinValue;
itemToBePublished.Editing.EndEdit();
options.RootItem = itemToBePublished;
}
if (options is ExtendedPublishOptions)
return new ExtendedPublishHelper(options as ExtendedPublishOptions);
return new PublishHelper(options);
}
}
public class ExtendedPublishHelper : PublishHelper
{
private readonly ExtendedPublishOptions _options;
public ExtendedPublishHelper(ExtendedPublishOptions options)
: base(options)
{
_options = options;
}
public override Item GetVersionToPublish(Item sourceItem)
{
Assert.ArgumentNotNull(sourceItem, "sourceItem");
if (Options is ExtendedPublishOptions)
{
return sourceItem.Publishing.GetValidVersion(Options.PublishDate, _options.RequireApproval);
}
return sourceItem.Publishing.GetValidVersion(Options.PublishDate, true);
}
}
public class ExtendedPublishOptions : PublishOptions
{
public ExtendedPublishOptions(Database sourceDatabase, Database targetDatabase, PublishMode mode, Language language, DateTime publishDate, bool requireApproval)
: base(sourceDatabase, targetDatabase, mode, language, publishDate)
{
RequireApproval = requireApproval;
}
public bool RequireApproval { get; set; }
}
}
I believe you'll be better off adding a processor to the publishItem pipeline. Here is some UNTESTED code, which I think will serve your purpose:
public class PublishOverride : PublishItemProcessor
{
public override void Process(PublishItemContext context)
{
Assert.ArgumentNotNull((object)context, "context");
if (context.Action != PublishAction.None)
return;
Item sourceItem = this.GetSourceItem(context);
if (sourceItem == null)
return;
var stagingDB = Factory.GetDatabase("Stage");
if (stagingDB != null && !sourceItem.Publishing.NeverPublish && context.PublishContext.PublishOptions.TargetDatabase == stagingDB)
{
context.Action = PublishAction.PublishVersion;
context.VersionToPublish = sourceItem;
}
}
private Item GetSourceItem(PublishItemContext context)
{
Assert.ArgumentNotNull((object)context, "context");
return context.PublishHelper.GetSourceItem(context.ItemId);
}
}
Make sure you patch it in before the DetermineAction processor in the default config. So your config patch would look like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<publishItem>
<processor patch:before="*[#type='Sitecore.Publishing.Pipelines.PublishItem.DetermineAction, Sitecore.Kernel']"
type="YourNamespace.PublishOverride, YourAssembly" />
</publishItem>
</pipelines>
</sitecore>
</configuration>

Interceptors doesn't work in Unit tests

I am get Interceptors working on the application server. I have annotated EJB:
#Stateless
#Named("accountsEJB")
public class AccountsEJB {
#PersistenceContext(unitName = "weducationPU")
private EntityManager em;
// . . . other methods
#WithLog
#Restricted(allowedRoles = {}) // Allowed only for admin
public Account save(Account item) {
if (item.getId() == 0) {
em.persist(item);
return item;
} else {
return em.merge(item);
}
}
#WithLog
#Restricted(allowedRoles = {}) // Allowed only for admin
public void delete(final Account item) {
Account a = em.find(Account.class, item.getId());
if (null != a) {
em.remove(a);
}
}
}
Empty list of roles means, that It's allowed only for role admin.
Here the unit test file for this EJB
public class AccountsEJBTest {
private static EJBContainer container;
private static AccountsEJB ejb;
#BeforeClass
public static void setUpClass() {
try {
Map<String, Object> properties = new HashMap<>();
properties.put(EJBContainer.MODULES, new File("target/classes"));
properties.put("org.glassfish.ejb.embedded.glassfish.installation.root", "glassfish");
properties.put(EJBContainer.APP_NAME, "weducation");
container = EJBContainer.createEJBContainer(properties);
ejb = (AccountsEJB) container.getContext().lookup("java:global/weducation/classes/AccountsEJB");
System.out.println("AccountsEJBTest running...");
} catch (NamingException e) {
fail("Container init error: " + e.getMessage());
}
}
#AfterClass
public static void tearDownClass() {
if (null != container) {
container.close();
}
System.out.println("AccountsEJBTest finished");
}
private boolean equals(Account source, Account result) {
if (!source.getFullName().contentEquals(result.getFullName())) return false;
if (!source.getLogin().contentEquals(result.getLogin())) return false;
return source.getRole() == result.getRole();
}
#Test
public void testOperations() {
try {
System.out.println("-->testOperations()");
Account testAccount = new Account();
testAccount.setFullName("Test Account");
testAccount.setLogin("test");
testAccount.setPassword("test");
testAccount.setConfirm("test");
testAccount.updatePassword();
testAccount.setRole(AccountRole.DEPOT);
Account savedAccount = ejb.save(testAccount);
assertTrue(equals(testAccount, savedAccount));
savedAccount.setFullName("Still Test Account");
savedAccount.setLogin("test1");
testAccount = ejb.save(savedAccount);
assertTrue(equals(testAccount, savedAccount));
testAccount.setPassword("testpwd");
testAccount.setConfirm("testpwd");
testAccount.updatePassword();
savedAccount = ejb.save(testAccount);
assertTrue(equals(testAccount, savedAccount));
ejb.delete(savedAccount);
} catch (Exception e) {
fail("Exception class " + e.getClass().getName() + " with message " + e.getMessage());
}
}
}
And this test working. I think, that is not correct, because there is no user with admin role logged in. But why this behavior happing?
UPDATED.
#Restricted interface:
#Inherited
#InterceptorBinding
#Target({METHOD, TYPE})
#Retention(RUNTIME)
public #interface Restricted {
#Nonbinding
AccountRole[] allowedRoles();
}
SecurityInterceptor class
#Interceptor
#Restricted(allowedRoles = {})
public class SecurityInterceptor implements Serializable {
#Inject
private transient SessionMB session;
#AroundInvoke
public Object checkSecurity(InvocationContext context) throws Exception {
//System.out.println("Security checker started.");
if ((session == null) || (session.getUser() == null)) {
throw new SecurityException("Can't get user info");
}
// Allow all to admin
if (session.isAdmin()) {
//System.out.println("It's admin.");
return context.proceed();
}
// walk non administrator roles
for (AccountRole r : getAllowedRoles(context.getMethod())) {
// if match - accept method invocation
if (session.getUser().getRole() == r) {
//System.out.println("It's " + r.getDescription());
return context.proceed();
}
}
throw new SecurityException(session.getUser().getFullName()
+ " has no souch privilegies ");
}
private AccountRole[] getAllowedRoles(Method m) {
if (null == m) {
throw new IllegalArgumentException("Method is null!");
}
// Walk all method annotations
for (Annotation a : m.getAnnotations()) {
if (a instanceof Restricted) {
return ((Restricted) a).allowedRoles();
}
}
// Now - walk all class annotations
if (null != m.getDeclaringClass()) {
for (Annotation a : m.getDeclaringClass().getAnnotations()) {
if (a instanceof Restricted) {
return ((Restricted) a).allowedRoles();
}
}
}
// if no annotaion found
throw new RuntimeException("Annotation #Restricted not found at method "
+ m.getName() + " or it's class.");
}
}
The beans.xml is placed in WEB-INF folder and looks like
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="annotated">
<interceptors>
<class>ru.edu.pgtk.weducation.interceptors.LogInterceptor</class>
<class>ru.edu.pgtk.weducation.interceptors.SecurityInterceptor</class>
</interceptors>
</beans>
Can someone help me to know:
How to get Interceptors working in Unit tests?
How to start authorized session in Unit tests (log in as admin, for example)?
How to test such operations as creation and deleting account with the different tests (one test for creating, one for deleting)? Is it correct - to test all operations in one test?
Thank you for your time and your questions.

Are Sitecore events handled synchronously?

I want to hook the item:renamed event to do some processing. It may take a few minutes though. Are event handlers executed asynchronously or synchronously with normal pipeline execution? Is there a standard Sitecore way to kick this off asynchronously if I need to do that myself?
The only time this handler needs to execute is when an item is renamed in Content Editor.
Sitecore events are executed synchronously. There is a Sitecore Development Toolkit module on Sitecore Marketplace which contains a code for firing events asynchronously which you can easily reuse in your solution Sitecore Development Toolkit.
Here is a part of their code which fires methods asynchronously when the event is fired:
public void OnItemRenamed(object sender, EventArgs args)
{
if (args != null)
{
var item = Event.ExtractParameter(args, 0) as Item;
Assert.IsNotNull(item, "No item in parameters");
var name = Event.ExtractParameter(args, 1) as string;
Assert.IsNotNullOrEmpty(name, "No name in parameters");
DoAsync(() => OnItemRenameAsync(item, name));
}
}
private void OnItemRenameAsync(Item item, string name)
{
var itemRef = new ItemReference(item.Parent);
var itemRefText = itemRef.ToString();
// do some work here
}
Sitecore events are synchronous. You can kick off your long running task as a job. First create a class to handle the event:
namespace MyNamespace
{
public class MyClass
{
public void ItemRenamed (object sender, EventArgs args)
{
Run("LongRenameTask");
}
protected void Run(string methodName, EventArgs args)
{
var item = Event.ExtractParameter(args, 0) as Item;
var name = Event.ExtractParameter(args, 1) as string;
RunJob(methodName, item, name);
}
protected Handle RunJob(string methodName, Item item, string name)
{
var options = new JobOptions(
"Preparing rename job '{0}' for '{1}'".FormatWith(
methodName,
item.ID.ToString()),
"item:renamed",
"shell",
new ItemRenamedManager(item, name),
methodName)
{
WriteToLog = true,
AtomicExecution = true,
};
var job = new Job(options);
JobManager.Start(job);
return job.Handle;
}
}
}
Then create a class to do your work (this will be called on a background thread by Sitecore):
namespace MyNamespace
{
public class ItemRenamedManager
{
protected Item RenamedItem { get; set; }
protected string Name { get; set; }
public ItemRenamedManager(Item item, string name)
{
RenamedItem = item;
Name = name;
}
public void LongRenameTask()
{
// Do your long running task here.
// The property 'RenamedItem' will give you the item
}
}
}
Then patch your event handler in:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:renamed">
<handler type="MyNamespace.MyClass" method="ItemRenamed" />
</event>
</events>
</sitecore>
</configuration>
The above code is cribbed a bit from memory and needs some error handling, but should be pretty close, but this way, your long running task won't block the Content Editor UI.

How can I get my XSLT transformation to work with large XML?

I have written code to filter out new XML elements that have been introduced as part of web service enhancements. The transformation seems to work with small XML but as soon as I increase the size its stops working. Using SoapUI, it simply returns a blank response.
Can anyone help? Do i need to alter the code below to handle large XML?
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.PreRequestHandlerExecute += new EventHandler (context_PreRequestHandlerExecute);
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
State state = (State)app.Context.Items["VersionManagerState"];
if (state.ProcessRequest)
{
Stream xslres = Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Concat("TestService.VersionManager.",state.Version, ".xslt"));
if (xslres != null)
{
state.Xsl = new XslCompiledTransform();
state.Xsl.Load(XmlReader.Create(xslres));
app.Context.Response.Filter = new OutputFilterStream(state, app.Context.Response.Filter);
}
}
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
State state = new State();
app.Context.Items["VersionManagerState"] = state;
if (app.Context.Request.Url.AbsolutePath.EndsWith(".asmx") && app.Request.RequestType == "POST")
{
state.ProcessRequest = true;
app.Context.Request.Filter = new InputFilterStream(state, app.Context.Request.Filter);
}
}

Track Sitecore item history

Sitecore tracks the item changes with last updated by, Created by information.
Is it possible to track changes made to "fields" in an item against the person who changed them? And retrive the list of changes made to fields of an item.
You can create a custom handler and add it to item:saving event in Sitecore events/event configuration:
<sitecore>
<events>
<event name="item:saving">
<handler type="My.Assembly.Namespace.CreateHistoryEntryHandler, My.Assembly" method="OnItemSaving" />
</event>
</events>
</sitecore>
The class below saves the information to the Workflow History Store so you can see it using History menu from ribbon (see screenshot), but you can save it to any other place
namespace My.Assembly.Namespace
{
public class CreateHistoryEntryHandler
{
protected void OnItemSaving(object sender, EventArgs args)
{
Item newItem = Event.ExtractParameter(args, 0) as Item;
if (newItem == null || newItem.Database.DataManager.GetWorkflowInfo(newItem) == null)
{
return;
}
Item originalItem = newItem.Database.GetItem(newItem.ID, newItem.Language, newItem.Version);
newItem.Fields.ReadAll();
IEnumerable<string> fieldNames = newItem.Fields.Select(f => f.Name);
IEnumerable<string> differences = fieldNames.Where(fieldName => newItem[fieldName] != originalItem[fieldName]).ToList();
if (differences.Any())
{
string message = String.Format("Item content changed [{0}]", differences.Aggregate((s1, s2) => s1 + ", " + s2));
AddHistoryEntry(newItem, message);
}
}
public static void AddHistoryEntry(Item item, string text)
{
WorkflowProvider workflowProvider = (item.Database.WorkflowProvider as WorkflowProvider);
if (workflowProvider != null && workflowProvider.HistoryStore != null)
{
string workflowState = GetWorkflowState(item);
workflowProvider.HistoryStore.AddHistory(item, workflowState, workflowState, text);
}
}
private static string GetWorkflowState(Item item)
{
WorkflowInfo info = item.Database.DataManager.GetWorkflowInfo(item);
return (info != null) ? info.StateID : String.Empty;
}
}
}