How can Tridion 2011 Event System prevent a single page from publishing? - tridion

Event System handler code:
[TcmExtension("My Handler")]
public sealed class EventSystem : TcmExtension
{
public EventSystem()
{
EventSystem.Subscribe<Page, PublishEventArgs>((page, e, phases) => {
if (shouldTerminatePublishing(page))
{
throw new Exception(ex, page);
}
}, EventPhases.Initiated, EventSubscriptionOrder.Normal);
}
}
With the code above when multiple pages are being published and Event System is only about to block one of them (by throwing an exception), then all pages are effectively prevented from being published too. "Ignore Failures While Generating Publishable Content" check box does not affect this behavior.
How to prevent any given page from publishing but still allow all the rest to be published?
EDIT
Updated code as per Quirijn's suggestion:
public class MyResolver: IResolver
{
public void Resolve(
IdentifiableObject item,
ResolveInstruction instruction,
PublishContext context,
ISet<ResolvedItem> resolvedItems)
{
var page = item as Page;
if (null != page && instruction.Purpose == ResolvePurpose.Publish)
{
try
{
// Evaluate whether publishing is allowed
}
catch (Exception ex)
{
resolvedItems.Clear();
}
}
}
}
Some objections (or rather follow-up questions) so far:
There's no sensible way to provide explicit feedback to the user when item gets excluded (except advising to check "Show Items to Publish" option), is there?
Custom resolver must explicitly account for all items types, that is: not only for 'Page' but also 'StructureGroup' and 'Publication', must it not?
Given that evaluation code might be expensive (web service call), is there any way to cache it's result at least between preparing "Show Items to Publish" list and performing actual publishing? (In such case evaluation occurs at least twice).
EDIT 2
After looking into standard resolvers' implementation:
Is it necessary/preferably to implement IBulkResolver as well?

You shouldn't do this in the event system but in a custom resolver. This is a piece of code which gets executed to determine which pages / components should be effectively published when an item is put in the publish queue.
Here you can filter out any page or component which you do not want to be published.
See How to remove items from publishing using a Tridion Resolver?.

Related

How can I prevent default notifications when encountering an error when using an external datasource as my datastore?

I'm building a custom content part that fetches its information from an external repository, mostly following the advice found # How to change Orchard record repository and using a custom handler to fetch the data.
Working with external data stores opens up the possibility of all sorts of network exceptions, etc., which would cause the underlying record not be saved. However, if there's an exception thrown in the ContentHandler, it's swallowed up by the Invoke<TEvents> method so that (unless it's a "fatal" exception) the user wouldn't know about the exception and would be notified by the AdminController that "Your {0} has been saved.", when in fact it hasn't been.
A workaround that's obvious to me is to intercept the error somehow and notify the content driver for my content part, which exposes the executing AdminController. At that point, I can hook into the controller's ModelState and introduce an error, which would then be caught and then I'd be notified of an error without any false positive notifications.
Are there any other extensibility points available in Orchard that would handle this kind of external access better than altering the controller's ModelState via a content driver?
The easiest way would be to implement you own, simple, per-request storage object for those errors, ie.
public interface IErrorLog : IDependency {
public void Add(string message){ ... }
public IEnumerable<string> List() { ... }
}
public class DefaultErrorLog : IErrorLog { ... }
Inject IErrorLog in both your controller and handler. In the handler, catch all errors you need to catch and add them to the collection with Add(...). Then, in the controller, call List() and add model error for each entry.
UPDATE
If you're not in control of a Controller that updates your content item, then you should use a driver. Catch the exception during item save (the second Editor method with 3 params) and push some error info using AddModelError of the provided IUpdateModel object. Way easier, but you'll be able to catch only those errors that happen when an item is saved.
UPDATE 2
If you're not in control of a Controller that updates your content item but would like to have a pure solution and gain control over the whole process, you can use your own controller for editing items. In order to do that:
create a new controller (or copy and alter the default AdminController found in Orchard.Core\Contents) first
now tell Orchard to use this controller instead of the default one for all types that contain your custom part. It can be done by putting something like this in your handler:
OnGetContentItemMetadata<MyCustomPart>((context, part) =>
{
context.Metadata.AdminRouteValues = new RouteValueDictionary
{
{ "Area", "My.Module" },
{ "Controller", "Admin" },
{ "Action", "Edit" },
{ "id", context.ContentItem.Id }
};
});

How to determine in a Custom Resolver if I am publishing or unpublishing? Tridion 2009 SP1

I'm trying to capture when a component is unpublished. I try some approaches but I don't have the result that I want. My attempts are:
In Event System. But this not works because there are a known bug in Windows about MSXML and COM+.
I try to build my own IResolver but there I cannot determine if it's a publishing or unpublishing action.
I try to build my own ITransportPackageHandler. There, I have a function called HandleResolvedItemForUnPublishing but I don't have any information about PublicationTarget and I don't know if it's unpublished from staging or live.
Can someone help me? I think that I can solve the problem if:
At the IResolver I can determine if the component is unpublishing.
At the ITransportPackageHandler I can access to the PublicationTarget info
If I can pass info from IResolver to the ITransportPackageHandler in a context variable or something similar.
thank you very much.
Gustavo.
You should be able to look at the ResolvePurpose of the ResolveInstruction that you get as one of the parameters in the custom resolver. Something along these lines:
public void Resolve(IdentifiableObject item, ResolveInstruction instruction, PublishContext context, ISet<ResolvedItem> resolvedItems)
{
if (instruction.Purpose == ResolvePurpose.Publish || instruction.Purpose == ResolvePurpose.RePublish)
{
// We are publishing
}
else if(instruction.Purpose == ResolvePurpose.UnPublish)
{
// We are unpublishing
}
// Don't know if this one exists in 2009, it exists in 2011 SP1
else if(instruction.Purpose == ResolvePurpose.UnknownByClient)
{
// The server is doing something that I don't understand (yet?)
}
}
EDIT
I refused to not find a way to make this work...
Indeed, in Tridion 2009 you don't have a Purpose on the resolve instruction. You do have an Action in the Publish Transaction, but this one is not exposed directly in the resolver. Here's how I found out if I'm publishing or unpublishing - your call if think it's overkill, but performance on my non-production VM was pretty good.
Find the current item we're resolving for
Load the list of PublishTransaction with a state of "In Progress"
Find the transaction for the current item
Determine the action by looking at the Action attribute
Filter filter = new Filter();
filter.Conditions["InfoType"] = 2; // Get transactions in Progress
foreach (XmlNode node in item.Session.GetList(typeof(PublishTransaction), filter))
{
if(node.Attributes["ItemID"].Value.Equals(item.Id.ToString()))
{
// we have a winner
string action;
if (node.Attributes["Action"].Value.Equals("0"))
action = "Publish";
if (node.Attributes["Action"].Value.Equals("1"))
action = "Unpublish";
}
}
Assuming you use 2011 you can bind an event handler to the Publish Transaction Save event and verify the State. Then, when the Component is unpublished you can perform the logic you needed.
public sealed class PublishedToEventHandler: TcmExtension
{
public PublishedToEventHandler()
{
EventSystem.SubscribeAsync<PublishTransaction, SaveEventArgs>(
(subject, args, phase) =>
{
if (!PublishStransactionStateIsSuccessfullyCompleted(subject))
return;
},
EventPhases.TransactionCommitted
);
}
static bool PublishStransactionStateIsSuccessfullyCompleted(PublishTransaction transaction)
{
return transaction.State == PublishTransactionState.Success ||
transaction.State == PublishTransactionState.Warning;
}
}
Before anything is handled in this event you can verify the Instruction.ResolveInstruction.Purpose property of the transaction to see whether or not you are publishing on unpublishing.
The transaction has a ProcessedItems collection, and each contains the Page or Component in the ResolvedItem.Item propery of the ProcessedItem object. When its a Page you do need to obtain the Components embedded on the Page to do anything with them.
Let me know if you have any more questions.

Event not working

I am new to Tridion Event System. I have written a small code.
[TcmExtension("MyEventHandlerExtension")]
public class EH : TcmExtension
{
public EH()
{
Subscribe();
}
public void Subscribe()
{
//EventSystem.Subscribe<Component, DeleteEventArgs>(HandlerForInitiated, EventPhases.Initiated);
EventSystem.Subscribe<Tridion.ContentManager.CommunicationManagement.Page, Tridion.ContentManager.Extensibility.Events.PublishOrUnPublishEventArgs>(HandlerForCommitted, EventPhases.All);
}
private void HandlerForCommitted(IdentifiableObject subject, PublishOrUnPublishEventArgs args, EventPhases phase)
{
TDSE obj = new TDSE();
Tridion.ContentManager.Interop.TDS.Publication pub = obj.GetPublication("tcm:0-150-1");
Tridion.ContentManager.Interop.TDS.Page pubPage = obj.GetPage("tcm:150-12374-64", pub);
pubPage.Publish("tcm:0-1-65538", false, true, false, default(DateTime), default(DateTime), default(DateTime));
}
}
using this code i wanted to publish a page everytime when a publish and unpublish event occur.
I build this code and register its path in tridion config file .
But its not working.Please Help
Ok, first of all remove all your TDSE code, you should use TOM.NET. You can get session as subject.Session
Then make sure you have registered this extension in Tridion.ContentManager.config and restarted your system
And finally - if something doesn't work, just add simple code that will create a file in your HandlerForCommitted whenever event occurs, this way you will be able to see if your extension get executed.
The 2011 Event System uses the TOM.NET API and not the TOM API. Please do not create new TDSE objects in the 2011 Event System. Even though you can reference the old Interop libraries, there is no reason to do so with 2011. Using the TOM.NET libraries you should see better performance and also the code is future-proof.
Mihai Cadariu has a nice example where he uses TOM.NET to Publish a page from a Tridion Template. Adjusting the code to check for previewmode or publish mode and setting your own user and priority (instead of reading it from the current transaction) should work well.
Below code from http://yatb.mitza.net/2012/05/publishing-from-template-code-using.html
public void Publish(Engine engine, String tcmUri, User user, PublishPriority priority)
{
Session session = new Session(user.Title);
PublishInstruction publishInstruction = new PublishInstruction(session);
RenderInstruction renderInstruction = new RenderInstruction(session);
renderInstruction.RenderMode = RenderMode.Publish; // work around. needs to be specified for binaries.
publishInstruction.RenderInstruction = renderInstruction;
List<IdentifiableObject> items = new List<IdentifiableObject>() { session.GetObject(tcmUri) };
List<PublicationTarget> targets = new List<PublicationTarget>() { engine.PublishingContext.PublicationTarget };
PublishEngine.Publish(items, publishInstruction, targets, priority);
session.Dispose();
}
// called with
PublishTransaction currentTransaction = TemplateUtils.GetPublishTransaction(engine);
TemplateUtils.Publish(engine, itemUri, currentTransaction.Creator, currentTransaction.Priority);
Your code seems to have the three things I "normally" forget:
the class is public
it extends TcmExtension
it has a TcmExtension attribute
If you've registered the class correctly in the configuration file, it should just be a matter of restarting the relevant module(s). In this case I'd expect those to be the Publisher and TcmServiceHost services.
After restarting those modules and triggering a publish action, you should see an event being logged (in the Windows event viewer) that your extension is being loaded.
If that even shows, it means your assembly is being loaded into the relevant Tridion process and the class is being recognized and instantiated.
If at this stage your handler doesn't fire you may have to consider listening to a different event. Whenever I want to interact with the publishing, I end up listening for the SaveEventArgs of a PublishTransaction, instead of the PublishOrUnPublishEventArgs on the Page.

ASP.NET MVC3 Role and Permission Management -> With Runtime Permission Assignment

ASP.NET MVC allows users the ability to assign permissions to functionality (i.e. Actions) at Design Time like so.
[Authorize(Roles = "Administrator,ContentEditor")]
public ActionResult Foo()
{
return View();
}
To actually check the permission, one might use the following statement in a (Razor) view:
#if (User.IsInRole("ContentEditor"))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
The problem with this approach is that all permissions must be set up and assigned as attributes at design time. (Attributes are compiled in with the DLL so I am presently aware of no mechanism to apply attributes (to allow additional permissions) such as [Authorize(Roles = "Administrator,ContentEditor")] at runtime.
In our use case, the client needs to be able to change what users have what permissions after deployment.
For example, the client may wish to allow a user in the ContentEditor role to edit some content of a particular type. Perhaps a user was not allowed to edit lookup table values, but now the client wants to allow this without granting the user all the permissions in the next higher role. Instead, the client simply wants to modify the permissions available to the user's current role.
What options are strategies are available to allow permissions on MVC Controllers/Views/Actions to be defined outside of attributes (as in a database) and evaluated and applied at runtime?
If possible, we would very much like to stick as closely as we can to the ASP.NET Membership and Role Provider functionality so that we can continue to leverage the other benefits it provides.
Thank you in advance for any ideas or insights.
What options are strategies are available to allow permissions on MVC
Controllers/Views/Actions to be defined outside of attributes (as in a
database) and evaluated and applied at runtime?
A custom Authorize attribute is one possibility to achieve this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
Roles = ... go ahead and fetch those roles dynamically from wherever they are stored
return base.AuthorizeCore(httpContext);
}
}
and then:
[MyAuthorize]
public ActionResult Foo()
{
return View();
}
As I'm lazy I couldn't be bothered rolling my own attribute and used FluentSecurity for this. In addition to the ability to apply rules at run time it allows a custom way to check role membership. In my case I have a configuration file setting for each role, and then I implement something like the following;
// Map application roles to configuration settings
private static readonly Dictionary<ApplicationRole, string>
RoleToConfigurationMapper = new Dictionary<ApplicationRole, string>
{
{ ApplicationRole.ExceptionLogViewer, "ExceptionLogViewerGroups" }
};
the application roles are then applied like so
SecurityConfigurator.Configure(
configuration =>
{
configuration.GetAuthenticationStatusFrom(() =>
HttpContext.Current.User.Identity.IsAuthenticated);
configuration.GetRolesFrom(() =>
GetApplicationRolesForPrincipal(HttpContext.Current.User));
configuration.ForAllControllers().DenyAnonymousAccess();
configuration.For<Areas.Administration.Controllers.LogViewerController>()
.RequireRole(ApplicationRole.ExceptionLogViewer);
});
filters.Add(new HandleSecurityAttribute());
and then the check is performed by
public static object[] GetApplicationRolesForPrincipal(IPrincipal principal)
{
if (principal == null)
{
return new object[0];
}
List<object> roles = new List<object>();
foreach (KeyValuePair<ApplicationRole, string> configurationMap in
RoleToConfigurationMapper)
{
string mappedRoles = (string)Properties.Settings.Default[configurationMap.Value];
if (string.IsNullOrEmpty(mappedRoles))
{
continue;
}
string[] individualRoles = mappedRoles.Split(',');
foreach (string indvidualRole in individualRoles)
{
if (!roles.Contains(configurationMap.Key) && principal.IsInRole(indvidualRole))
{
roles.Add(configurationMap.Key);
if (!roles.Contains(ApplicationRole.AnyAdministrationFunction))
{
roles.Add(ApplicationRole.AnyAdministrationFunction);
}
}
}
}
return roles.ToArray();
}
You could of course pull roles from a database. The nice thing about this is that I can apply different rules during development, plus someone has already done the hard work for me!
You could also consider doing task/activity based security and dynamically assign permission to perform those tasks to different groups
http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/
You would need to mangle the provider a little bit to work with this but it is possible to stay inline with the .net authorisation
http://www.lhotka.net/weblog/PermissionbasedAuthorizationVsRolebasedAuthorization.aspx
If you need to do Method or Controller based authorization (deny access to the whole method or controller) then you can override OnAuthorization in the controller base and do your ouwn authorization. You can then build a table to lookup what permissions are assigned to that controller/method and go from there.
You can also do a custom global filter, which is very similar.
Another option, using your second approach, is to say something like this:
#if (User.IsInRole(Model.MethodRoles))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
And then in your controller populate MethodRoles with the roles assigned to that method.

Dynamic list constraint on Alfresco

I'm trying to follow the examples provided in this post, to create a dynamic list constraint in Alfresco 3.3.
So, I've created my own class extending ListOfValuesConstraint:
public class MyConstraint extends ListOfValuesConstraint {
private static ServiceRegistry registry;
#Override
public void initialize() {
loadData();
}
#Override
public List getAllowedValues() {
//loadData();
return super.getAllowedValues();
}
#Override
public void setAllowedValues(List allowedValues) {
}
protected void loadData() {
List<String> values = new LinkedList<String>();
String query = "+TYPE:\"cm:category\" +#cm\\:description:\"" + tipo + "\"";
StoreRef storeRef = new StoreRef("workspace://SpacesStore");
ResultSet resultSet = registry.getSearchService().query(storeRef, SearchService.LANGUAGE_LUCENE, query);
// ... values.add(data obtained using searchService and nodeService) ...
if (values.isEmpty()) {
values.add("-");
}
super.setAllowedValues(values);
}
}
ServiceRegistry reference is injected by Spring, and it's working fine. If I only call loadData() from initialize(), it executes the Lucene query, gets the data, and the dropdown displays it correctly. Only that it's not dynamic: data doesn't get refreshed unless I restart the Alfresco server.
getAllowedValues() is called each time the UI has to display a property having this constraint. The idea on the referred post is to call loadData() from getAllowedValues() too, so the values will be actually dynamic. But when I do this, I don't get any data. The Lucene query is the same, but returns 0 results, so my dropdown only displays -.
BTW, the query I'm doing is: +TYPE:"cm:category" +#cm\:description:"something here", and it's the same on each case. It works from initialize, but doesn't from getAllowedValues.
Any ideas on why is this happening, or how can I solve it?
Thanks
Edit: we upgraded to Alfresco 3.3.0g Community yesterday, but we're still having the same issues.
This dynamic-list-of-values-constraint is a bad idea and I tell you why:
The Alfresco repository should be in a valid state all the time. Your (dynamic) list of constraints will change (that's why you want it to be dynamic). Adding items would not be a problem, but editing and removing items are. If you would remove an item from your option-list, the nodes in the repository with this property value will be invalid.
You will not be able to fix this easily. The standard UI will fail on invalid-state-nodes. Simply editing this value and setting it to something valid will not work. You have been warned.
Because the default UI widget for a ListConstraint is a dropdown, not every dropdown should be a ListConstraint. ListConstraints are designed for something like a Status property: { Draft, Waiting Approval, Approved }. Not for a list of customer-names.
I have seen this same topic come up again and again over the last few years. What you actually want is let the user choose a value from a dynamic list of options (combo box). This is a UI problem, not a dictionary-model-issue. You should setup something like this with the web-config-context.xml (Alfresco web UI) or in Alfresco Share. The last one is more flexible and I would recommend taking that path.

Resources