=====================================
Event Subscriptions and Notifications
=====================================


:Revision:  $Id: subscriptions-draft.txt 30650 2005-12-14 22:15:26Z dkuhlman $

.. sectnum::    :depth: 4
.. contents::   :depth: 4


Typical Use Cases
=================

1. Notify the creator of a document that it is published.

2. Notify the submitter of a document that it is published.

3. Notify the section reviewer that a document is submitted.

4. Notify the forum reviewer that a post is pending approval.

5. Notify all the intermediate workflow validators of a document
   that it has reached "approved" status.

6. Notify all having "ResponsableCrise" roles that a subproblem has
   reached "resolved" status.

7. Notify workspace members that a comment has been posted into a
   container forum.

8. Notify the poster of a message that someone gave an answer.

9. Notify workspace members that a document has been copy/pasted
   into the workspace.

10. Notify workspace members that a new document version has been
    created (which means that a revision has been frozen). [Misill]

11. When certain workflow transitions are done, it must be possible
    for the user to specify a flag "notify_local_only" which will
    instruct the user resolution mechanism to lookup only locally
    and not infer merged local roles. Another flag "notify_no_local"
    will instruct to not send any notification based on the placeful
    resolution. [Misill]


Use case analysis
=================

The main use case is to "do something when something happens".


When?
-----

- Workflow transition (create, edit, submit, publish, accept,
  copy/cut/paste, )

- Forum related events (New post, Reply to post, New comment)

- Other


Do what?
--------

- Send emails

- Launch some script


Sending an email
----------------

Notify whom?

- Global list of users/groups

- Local roles in the context of the event

  - Direct local roles on the object

    ex: the Owner role to notify the creator that his document
    is published.

    ex: the Reader role to notify local readers assigned to
    the document. (?)

  - Direct local roles on a parent of the object with specific
      characteristics

    Example: The SectionManagers of the Forum above the current post.

  - Merged local roles

    Example: The SectionReviewers are all potential validators.

  For publishing (and others), there are two contexts: source
  and destination ...

- Local explicit list

  This is what we have currently with "MailingList".

- Configuration list in the parent of the object giving
  instructions

  Example: Notification of the poster of a message that someone
  responded to him. A configuration object at the forum level (or
  above) specifies that we lookup the Owner of the Post/Thread
  above the new post and notify him.

- Users having been implied by the workflow

  This is important be able to notify actors of some workflow
  transition. The workflow currently records actors in the
  history, so it is possible to extract them.

  Configuration examples:

  - those that did the transitions "edit"

  - those that did the LAST transition "edit"

  - those that did the transitions "validate" or "publish"

  - those that did any transition except "comment" and "view"

  In some workflows, we accumulate a stack of people that will
  be able to do some action later. We also want to be able to
  notify those. XXX this depends on how we store them.

  Note that there are two workflow history, the one local to
  the proxy and the one global to the document ID. Both may be
  useful depending on the context:

  - local: ex: people who have been acting on the proxy in a
      particular workspace

  - global: Example: (?)

Note that for publishing events, may want to check the source
context (to notify people from the workspace) or the destination
(to notify people from the section).

Send what message?

- message defined globally. i18n.

- message defined locally.

The message's content may have to be computed according to the
context.


Specification
=============

Concepts
--------

Subscriptions Tool
      portal_subcriptions is the central tool with the necessary
      methods to query the subscriptions and execute them.

      The subscriptions are looked up locally in a .cps_subscriptions
      folder in the object.

Subscription
      A Subscription object holds the information about what kind
      of events are treated, the parameters needed to find the
      final recipients (recipients rules), and the notifications
      sent to the recipients.

      The recipients rules are stored as sub-objects of the
      Subscription.

RecipientsRule
      They describe how to get some recipients. A final recipient
      for a subscription is either a member or an explicit email.
      Explicit emails only make sense for email subscriptions.

      If configured so, members or anonymous users can add or
      remove themselves from the subscription list.

Notification
      The something that is actually done. Usually it involves
      Recipients (sending email) but that's not mandatory
      (triggering an arbitrary script for instance).


Scenario
--------

An event is sent. The event has a context object, and additional
properties like the user flags specified in doActionFor for a
workflow transition, or the workflow transition parameters like
the source/destination container.

The event is passed to the portal_subcriptions tool.

The subscriptions tool does lookups from the context and up to
find the subscriptions. It then sends the notification to all
applicable subscriptions.

Each subscription computes a set of recipients according to its
recipient rules, and does its specific notification on the
recipients.


Implementation
--------------

Subscriptions Tool
..................

This tool has id 'portal_subscriptions'.

API:

- notify_event::
  
      def notify_event(self, event_type, object, infos):
      """Standard event hook.

      Get the applicable subscriptions. Sends the event to the
      subscriptions.

      For workflow events, infos must contain the additional
      keyword arguments passed to the transition.
      """

- getSubscriptionsFor::

      def getSubscriptionsFor(self, event_type, object, infos):
      """Get the subscriptions applicable for this event.

      Some of the parameters may be None to get all subscriptions.
      """

Also needed API

- To what local subscription lists can the user subscribe?


Subscription
............

API:

- isInterestedInEvent::

      def isInterestedInEvent(self, event_type, object, infos):
      """Is the subscription interested in the given event."""

- sendEvent::

      def sendEvent(self, event_type, object, infos):
      """Send an event to the subscription."""

- getRecipientsRules::

      def getRecipientsRules(self, ...):
      """Get the recipients rules objects...

      XXX matching what ?
      """

- setters/getters for the properties.

A Subscription instance has the following properties:

- Event filtering:

  - filter_event_types -- The event types on which to react.

    Examples: workflow_create, workflow_in_publish

  - filter_object_types -- The types of the objects concerned by
    the subscription. The subscription is valid only if the
    context object's portal_type is in object_types.

- Recipient filtering:

  - recipient_emails_black_list -- The emails the subscription is
    blocking.

- Notifications:

  - notification_types -- What kinds of action are taken for the
    recipients:

    - 'tales': Call notification_expression.

    - 'email': Send an email to each recipient, according to
      notification_email_expression.

  - notification_expression -- A TALES expression called for each
    recipient. In the expression, the same namespace as in
    ComputedRecipient is available, with in addition:

    - email: The recipient's email.

    - member: The member if the recipient is a member, or None.

  - notification_email_expression -- A TALES expression returning
    the email headers and body as a string. No mail is sent if it
    returns a false value. In the expression, the same namespace
    as in action_expression is available.


Recipients
..........

There are different ways of getting recipients. The following
classes subclass RecipientsRule and implement different
strategies. Their properties are described below.

API:

- getRecipients::

      def getRecipients(self, event_type, object, infos):
      """Get the recipients.

      Returns a mapping with 'members' and 'emails' as keys.
      """

ComputedRecipientsRule:

expression -- A TALES expression returning a mapping with
recipients. In the expression, the following namespace is
available:

- portal: The portal object.

- context: The context object (proxy) where the triggering
  event occurred.

- proxy: Alias for context.

- doc: context.getContent().

- container: The context's container.

- ancestor: If 'ancestor_local' or 'ancestor_merged' was used for
  recipient_roles_origins, that object, else None.

- event_type: The triggering event type.

- triggering_user: The user who triggered the original
  action.

- DateTime: A DateTime constructor.

ExplicitRecipientsRule:

- members -- The ids of the members subscribed manually.

- members_allow_add -- Whether members are allowed to
  subscribe / unsubscribe manually to the Subscription (adding
  themselves to recipient_members).

- groups -- The ids of the groups subscribed manually.

- emails -- The emails subscribed manually.

- emails_allow_add -- Whether anonymous visitors are allowed to
  subscribe / unsubscribe manually to the Subscription (adding
  themselves to recipient_emails).

- emails_confirm -- Whether the emails have to be confirmed before
  being used. XXX this may be better as a global flag of
  portal_subcriptions instead..

- emails_pending_add -- The emails subscribed manually but not yet
  confirmed.

- emails_pending_delete -- The emails pending deletion from
  recipient_emails but not yet confirmed.

RoleRecipientsRule:

- roles -- The roles subscribed.

- origins -- A sequence describing how roles are looked up. It can
  contain the following keys:

  - 'local': Direct local roles from the context object are
    used.

  - 'merged': All merged local roles of the context object are
    used.

  - 'ancestor_local': Direct local roles found on an ancestor
    object of type in ancestor_object_types. Only the closest
    matching ancestor object is used.

  - 'ancestor_merged': Idem but with merged local roles.

- ancestor_object_types -- The portal types of the ancestor where
  a lookup of local roles is done if origins contains
  'ancestor_local' or 'ancestor_merged'.

WorkflowImpliedRecipientsRule:

- origins -- A sequence of keys used to determine which object's
  workflow is queried for recipients. It can contain the following
  keys:

  - 'context': The context object.

  - 'container': The container.

  - 'ancestor': Direct local roles found on an ancestor object of
    type in ancestor_object_types. Only the closest matching
    ancestor is used.

- ancestor_object_types -- The portal types of the ancestor where
  workflow is queried for recipients.

- history_lookup -- Determines what part of the history we lookup
  for the transition of type matching 'transitions':

  - 'last': The last transition of matching type is used.

  - 'all': All transitions of matching type are used.

- transitions -- The name of the transitions examined for
  variables. '*' means all transitions. If the first element of
  the list is '-', then all types except the ones following it are
  used.

- variables -- The workflow variables containing used recipients
  (string or list). (Will often be ['actor'].)


Open problems
=============

- We need a "Mailing" object that contains the body of an email
  and that can be reused easily.

- Where to place these mailing objects? Locally or globally?
  [JA:Globally (portal_subscriptions) and locally (Notification
  rules)

- We need a way for local administrators to add a new subscription
  from a fixed predetermined list, without having able to
  re-parameterize them. This may mean that RecipientsRules have to
  be kept non-locally.

- Notifications should be subclasses of NotificationRule and store
  also as sub-objects, like RecipientsRule.


.. Emacs
.. Local Variables:
.. mode: rst
.. End:
.. Vim
.. vim: set filetype=rst:

