Abusing Plone Content Rules to allow Site Admin customizations of sent emails
This would be a sort of tutorial on how to implement a new plone.app.contentrules Trigger Event, how to write a new plone.stringinterp variable substitutor and how to trigger the event from a browser page (or z3c.form).
This allows configuring a new trigger event from the Content Rules Plone control panel configlet and to assign a new Email action, with customizable body template.
Let's say our task is to implement a contact form:
from plone.directives import form from plone.api.portal import show_message from zope.event import notify class IContactForm(form.Schema): name = schema.TextLine(title=u"Your Name", required=True) email = Email(title=u"Contact eMail:", required=True) class ContactInformation(Implicit): """ A container to be passed to plone.app.contentrules for contact info """ def __init__(self, name=None, email=None): self.name = name self.email = email class ContactForm(form.SchemaForm): schema = IContactForm ignoreContext = True label = u"Contact Form" fields = field.Fields(IContactForm) @button.buttonAndHandler(u"Submit") def handleApply(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return name = data.get('name') email = data.get('email') obj = ContactInformation(name=name, email=email) obj = obj.__of__(self.context) notify(ContactEvent(obj)) show_message(message="Message sent", request=self.request, type='info')
The ContactInformation object is needed to pass information from the form to the content rules machinery. It needs to be acquisition aware, as plone.app.contentrules will try to trigger events up its chain of acquisition. This can also be replaced by setting some annotation on the request.
We need a custom event with an event handler that triggers the content rules execution:
from zope.interface import implements from zope.component.interfaces import ObjectEvent, IObjectEvent from plone.app.contentrules.handlers import execute_rules class IContactEvent(IObjectEvent): """ """ class ContactEvent(ObjectEvent): implements(IContactEvent) def trigger_contentrules(event): execute_rules(event)
Now, a bit of zcml to register the event with plone.app.contentrules and setup the event handler:
<interface interface=".events.IContactEvent" type="plone.contentrules.rule.interfaces.IRuleEventType" name="Contact form triggered" /> <subscriber for=".events.IContactEvent" handler=".events.trigger_contentrules" />
This will make the "Contact form triggered" event available for the Plone Content Rules and, thanks to the notify() call in the form handler, trigger the content rules execution.
One last thing that we need is to access the information submitted in the form. The ContactInformation object serves as a container for that information, so we'll need special variables for the Email action:
class contact_email(BaseSubstitution): description = u"Contact email" category = 'ContactForm' adapts(Interface) def safe_call(self): return self.context.email class contact_name(BaseSubstitution): description = u"Contact name" category = 'ContactForm' adapts(Interface) def safe_call(self): return self.context.name
<adapter for="*" provides="plone.stringinterp.interfaces.IStringSubstitution" factory=".stringinterp.contact_email" name="contact_email" /> <adapter for="*" provides="plone.stringinterp.interfaces.IStringSubstitution" factory=".stringinterp.contact_name" name="contact_name" />