'm trying to customize sample workflow. I would like to override Sitecore.Workflows.Simple.AutoSubmitAction with my bussiness logic but I am not getting the steps from where I can customize. Basically I want workflow from which any item change I want to change last modified date of its parent. I know we can achieve this by event onsave but I want using workflow.
There is no point in overriding AutoSubmitAction.
Just create your custom action class and implement WorkflowPipelineArgs method:
public class CustomAutoAction
{
public void Process(WorkflowPipelineArgs args)
{
Item dataItem = args.DataItem;
if (dataItem != null && dataItem.Parent != null) {
dataItem.Parent ...
}
Then add it under /sitecore/system/Workflows/Sample Workflow/Draft/__OnSave:
Related
Is it possible to have more than one custom user profile and if it is how to set up web config file and how to manage custom profiles for two website under the same sitecore instance (same VS solution)?
We had one custom user profile and new requirement came about new website under the same sitecore instance but with the new custom user for the second website.
During development of second website we created second custom user profile and everything went fine, we change "inherits" attribute of system.web/profile node in the web.config file to point to second custom use profile and during development it was OK.
The problem now is that only one user profile can log in to the webistes:
if we set inherits attribute to "Namespace.Website.NamespaceA.CustomProfileA, Namespace.Website" only profileA will be able to log in to their domain and if we set it to "Namespace.Website.NamespaceB.CustomProfileB, Namespace.Website" only profileB will be able to login to its domain because the switcher will use this one.
All articles in the web describe how to set custom user profile, switcher and switchingProviders for just one custom user profile but there are no examples for my case.
Thanks,
Srdjan
Unfortunately, there does not seem to be a clean way to have multiple user profile classes created for you by the API. Typically, you will get the user profile via Sitecore.Context.User.Profile. The Context class is static and the methods that initialize the Profile property are private, so there's nowhere to insert your extra logic.
You could, however, create wrapper classes for the Profile. Start with a base class like this:
public abstract class CustomProfileBase
{
public CustomProfileBase(Sitecore.Security.UserProfile innerProfile)
{
Assert.ArgumentNotNull(innerProfile, nameof(innerProfile));
InnerProfile = innerProfile;
}
public Sitecore.Security.UserProfile InnerProfile { get; protected set; }
public virtual string GetCustomProperty(string propertyName)
{
return InnerProfile.GetCustomProperty(propertyName);
}
public virtual void SetCustomProperty(string propertyName, string value)
{
InnerProfile.SetCustomProperty(propertyName, value);
}
public virtual void Save()
{
InnerProfile.Save();
}
public virtual string Email
{
get { return InnerProfile.Email; }
set { InnerProfile.Email = value; }
}
// Other members omitted for brevity
}
This CustomProfileBase class would have a member that wraps each of the public members of Sitecore.Security.UserProfile. Then, you would create your site specific profile like this:
public class SiteOneProfile : CustomProfileBase
{
public SiteOneProfile(UserProfile innerProfile) : base(innerProfile)
{
}
public string CustomPropertyOne
{
get { return GetCustomProperty("CustomPropertyOne"); }
set { SetCustomProperty("CustomPropertyOne", value); }
}
}
Then you would use it from a controller or elsewhere like so:
var profile = new SiteOneProfile(Sitecore.Context.User.Profile);
model.property = profile.CustomPropertyOne;
Update
When using this approach, you would just leave the inherits attribute in the config with its default value. Also, the profile should not have an effect on the ability to login. If you are still having issues with that, please update your question with details of the error you get when logging in.
I am using Sitecore 7.2. I wanted to show or hide certain pages based on a condition on the user.
For example, Show/Give Read access to page A only to those users whose profile field name country = 'USA' just like we do for individual component in Sitecore item.
Is there a way to do this in Sitecore?
This sounds like the Rules Engine might be a good option for you. You could add a Rules field to your page template to drive the logic.
The rule conditions to check the profile fields already exist within Sitecore, you'd just need to create your own custom logic to deny access to the page
To evaluate the rules on your page you can put some code into a pipeline processor to check the rules apply for each page. Here's an example of evaluating the rules.
public bool EvaluateRule(string fieldName, Item item)
{
var ruleContext = new RuleContext();
foreach (Rule<RuleContext> rule in RuleFactory.GetRules<RuleContext>(new[] { item }, fieldName).Rules)
{
if (rule.Condition != null)
{
var stack = new RuleStack();
rule.Condition.Evaluate(ruleContext, stack);
if (ruleContext.IsAborted)
{
continue;
}
if ((stack.Count != 0) && ((bool)stack.Pop()))
{
return true;
}
}
}
return false;
}
You could combine this with this code to deny access to the page
public class RulesProcessor : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
if (!UserCanAccess())
{
Sitecore.Context.Item=null;
args.PermissionDenied = true;
}
}
private bool UserCanAccess()
{
EvaluateRulesLogic();
}
}
Why not using the role based permission that Sitecore supports?
So for example of each different Countries, you have the respective roles inside Sitecore and use that to define the permission to the items.
Next step would be to update the roles assigned to the user if the profile field country is updated.
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 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/
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.