How to trigger notification when programmatically creating nodes?

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
danielstrum's picture

Hi Folks,

I created an issue in the Issues Queue about this last week but perhaps it belongs here... as a discussion.

I'm wondering if anyone has any insight onto this question. I am programmatically creating discussion nodes through import feeds. Through commands I can set the "space", "section" and "author". I can use "rules" to select the conditions when a notification should be sent and even set the groups/users/teams to be notified (directly create new records in the "oa_notifications" table). The last thing I need to get this working is triggering the first notification when the node is created. Using "rules", I can execute arbitrary PHP code as an action when the rule meets the required conditions.

When nodes are created or edited through the interface, the notifications are triggered but this does not happen when nodes are created programmatically. I assume this is because the "oa_notifications_edit_form" is not being submitted simultaneously since that is done through Ajax. Perhaps there is a way to call the function that submits the form? I think I could use "rules" to submit that form but I can not find the values that would need to be set in the "form state". I would assume that I would need to have all the values used in the "oa_notifications" table:

source_type
source_id
target_type
target_id

Plus others like that this is a new node, not a reply or revision.

Any help with this would be highly appreciated. If I get this working, it would effectively make it possible for users to use OA discussion sections as if they were listserves, starting discussions via email and then replying with out ever having to visit the site.

Thanks.

Comments

How are you creating the

othermachines's picture

How are you creating the nodes? Some code would be useful.

I don't believe you need to emulate a form submission. Have a look at hook_entity_insert() implementation in rules/modules/events.inc (Rules module). This is where Rules is notified when a node is created. Perhaps there's a clue there for you.

A more complete explaination...

danielstrum's picture

Thanks othermachines. Here is a better explanation of the systems/flow I am using.

First I have a Group (Outreach Group), a Private Space (Outreach Space), and a Discussion Section (Outreach Discussions). The group is given access to the space > section via inheritance.

I then set it up using notifications and mailhandler as outlined in Mike Potter's blog post:
http://www.phase2technology.com/blog/using-email-with-open-atrium-2/
so that, when a discussion is created, the group gets a notification that they can reply to via email. This all works fine and is basically the functionality of a listserv except that discussions have to be started on the website, not via email.

I then set-up a separate email account (say "outreach-discussions@organization.org") and a Mailhandler Mailbox to retrieve emails from this account. I then set-up a Feeds Importer to create a Discussion node when an email is retrieved from "outreach-discussions@organization.org". This all works perfectly.

Since the email address, Mailhandler Mailbox, and Feeds Importer are used only to create a discussion within this discussion section (Outreach Discussions), I can specify "commands" in the Feeds Importer and map them to fields in the discussion node that will be created and place the discussion within the space and section. I can also check that the sender's email address is authorized to create these discussions and map that to be the new node's author. This also all works perfectly except no notification is sent as it would be if the node was created directly on the website.

Here is where I am trying to use Rules. I created a custom Yes/No field for the content type "Discussions" called "Via Email" that defaults to "No" and then created a "command" that sets it to "Yes" if the discussion is created by that specific Feed Importer.

I set the rule's event to be "After saving new content of type Discussion Post". Added conditions:

  1. Entity is new
  2. Data Comparison (that the custom field "Via Email" is "Yes")

This works and the rule fires correctly.

Now I can add the action "Execute custom PHP code". This is where I would like to trigger the notification. Though this code I can get the node ID and the ID of the group I want to notify:

$nid = ($node->nid);
$space_id = ($node->og_group_ref [und] [0] [target_id]);
$space_node = node_load($space_id);
$space_parent = ($space_node->oa_parent_space [und] [0] [target_id]);

My question is how could I then use these variables to trigger a notification. 2 ideas I had are:
1. Submit a notification form.
2. Call the right function in the notifications module.

Any insight into how I could implement this would be highly appreciated and I think could be used by others that might want the same functionality.

Thanks,
Daniel

Hi, Daniel. Not sure if I can

othermachines's picture

Hi, Daniel. Not sure if I can help - I don't have a ton of time - but let's see what happens when we keep things simple. :) You are attempting to implement a rule (using the Rules module) where the condition for the action is that a node is created (inserted into the database), correct?

Some info that might be helpful:

  • A printout of the exported rule
  • Where are the nodes being created/inserted? If not with custom code, can you tell us the module that is doing the creating?
  • Is the action not being triggered at all, or is it just that nothing happens? I'm assuming you've used the debugger?

If an action is not being triggered on node creation, the logical first step (I think) - after making sure that what you think is happening is actually happening, of course - is to look at the place where it is being created and go from there.

More clarification.

danielstrum's picture

Hi...

Yes, using the rules module on the event "content is created". The content is being created by a Feeds Importer and the rule is firing correctly. The issue I am trying to address is the "action" the rule will perform. I want it to trigger a notification exactly as if the node (discussion) was created through the site's interface. I can use the action "Execute custom PHP code" but can not figure out what that code would be.

I hope that helps... again, everything is working fine up to the point where I want to "Execute custom PHP code" that will trigger a notification.

ds

I don't mean to be obtuse,

othermachines's picture

I don't mean to be obtuse, but when you say this:

(danielstrum) "When nodes are created or edited through the interface, the notifications are triggered but this does not happen when nodes are created programmatically."

... it sounds like the rule isn't firing on the condition. You are saying that the rule is firing (content IS created) but no notification is sent UNLESS the content is created via the UI?

If I could see a Rules export I wouldn't have to ask these cumbersome questions. :)

I don't mean to be obtuse either...

danielstrum's picture

The rule has nothing to do with creating content... the content is being created by Feeds Import. This is the Event that triggers the rule. All I want the rule to do is send the notification. Here is the export of the rule:

{ "rules_discusion_notification" : {
    "LABEL" : "Discusion Notification",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules", "php" ],
    "ON" : { "node_insert--oa_discussion_post" : { "bundle" : "oa_discussion_post" } },
    "IF" : [
      { "entity_is_new" : { "entity" : [ "node" ] } },
      { "data_is" : { "data" : [ "node:field-primary-from-email" ], "value" : 1 } }
    ],
    "DO" : [
      { "drupal_message" : { "message" : "Rule Fired" } },
      { "php_eval" : { "code" : "$nid = ($node-\u003Enid);\r\n$space_id = ($node-\u003Eog_group_ref [und] [0] [target_id]);\r\n$space_node = node_load($space_id);\r\n$space_parent = ($space_node-\u003Eoa_parent_space [und] [0] [target_id]);\r\n\r\n$form_state = array(\r\n  \u0027source_type\u0027 =\u003E \u0027node\u0027,\r\n  \u0027source_id\u0027 =\u003E $nid,\r\n  \u0027target_type\u0027 =\u003E \u0027group\u0027,\r\n  \u0027target_id\u0027 =\u003E $space_parent,\r\n);\r\n\r\n  if (module_exists(\u0027oa_notifications\u0027) \u0026\u0026 function_exists(\u0027oa_notifications_edit_form_submit\u0027)) {\r\n    module_name_function_name();\r\n  }\r\n\r\n" } }
    ]
  }
}

"The rule has nothing to do

othermachines's picture

"The rule has nothing to do with creating content" - I understand that. But how the content is created becomes important when one method triggers an action and another doesn't. I (mis)understood your first post as describing a working rule that was contingent on the node being created through the UI, which would make the problem relatively easy to track down. This appears to be something entirely different.

I've never used any functionality provided by feed_import, but I'll take a stab at it.

Looking at the functions in oa_notifications (oa_notifications.module), below, I can see which values the module expects when a new node is created (looks like 'source_type' and 'source_entity'). My first thought was that Feed Import was bypassing node_save(), which would explain the behaviour, but that isn't the case. (This is why I asked for more info on where/how the content was being created.)

Now I don't know how the Feed Import module maps imported content to entity fields, but I'm guessing you should be able to inform Feed Import of these additional values before it saves the node? This would allow oa_notifications to do the handling, which would be ideal. Failing that, it looks like you could maybe add these values to your $node object in your "Execute PHP" action (as I think you were already preparing to do) and then call oa_notifications_save_notifications() as in hook_node_insert(), below.

This is all obviously untested, but it's my best guess. Good luck!

/**
* Implements hook_node_insert().
*/
function oa_notifications_node_insert($node) {
  if (oa_notifications_is_notification_type($node)) {
    $node->oa_notifications['source_entity'] = $node;
    oa_notifications_save_notifications($node->oa_notifications);
  }
}

/**
* Saves notifications.
*
* @param array $values
*   The values to save.
*/
function oa_notifications_save_notifications($values) {
  if (isset($values['notifications']['skip_notify'])) {
    $_SESSION[SKIP_FLAG] = $values['notifications']['skip_notify'];
  }
  if (isset($values['source_entity']) && isset($values['source_type'])) {
    drupal_alter('oa_notifications_save', $values);
    $source_type = $values['source_type'];
    $source_id = current(entity_extract_ids($source_type, $values['source_entity']));
    $notifications = array();

    if (isset($values['override'])) {
      oa_notifications_save_override($source_type, $source_id, $values['override']);
    }
    if (!isset($values['override']) || $values['override']) {
      foreach (array('group', 'team', 'user') as $type) {
        foreach ($values['notifications'][$type] as $id) {
          $n = new stdClass();
          $n->source_id = $source_id;
          $n->source_type = $source_type;
          $n->target_id = $id;
          $n->target_type = $type;
          $notifications[] = $n;
        }
      }
    }

    oa_notifications_save_for_source($source_id, $source_type, $notifications);
  }
}

Thanks for the "stab at it".

danielstrum's picture

Yes, I can map these values to fields during the import and that is working. I have gotten the rule to call the function oa_notifications_save_notifications and pass the needed variables to it but that still does not trigger notifications. It does create a record in the oa_notifications table setting the default for notifications when the node is edited or a reply is created. I was already able to do this but this is a much more elegant way...

I still however have the same problem of how to trigger a notification when a discussion node is created through Feeds Import. Here is the rule as it stands now:

{ "rules_discusion_notification" : {
    "LABEL" : "Discusion Notification",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules", "php" ],
    "ON" : { "node_insert--oa_discussion_post" : { "bundle" : "oa_discussion_post" } },
    "IF" : [
      { "entity_is_new" : { "entity" : [ "node" ] } },
      { "data_is" : { "data" : [ "node:field-primary-from-email" ], "value" : 1 } }
    ],
    "DO" : [
      { "drupal_message" : { "message" : "Rule Fired" } },
      { "php_eval" : { "code" : "$nid = ($node-\u003Enid);\r\n$space_id = ($node-\u003Eog_group_ref [\u0027und\u0027] [0] [\u0027target_id\u0027]);\r\n$space_node = node_load($space_id);\r\n$space_parent = ($space_node-\u003Eoa_parent_space [\u0027und\u0027] [0] [\u0027target_id\u0027]);\r\n$values = array(\r\n  \u0027source_type\u0027 =\u003E \u0027node\u0027,\r\n  \u0027source_entity\u0027 =\u003E $node,\r\n  \u0027notifications\u0027 =\u003E array(\r\n    \u0027skip_notify\u0027 =\u003E 0,\r\n    \u0027team\u0027 =\u003E array(),\r\n    \u0027user\u0027 =\u003E array(),\r\n    \u0027group\u0027 =\u003E array(\r\n      \u0027\u0027 =\u003E $space_parent,)\r\n)\r\n);\r\n\r\n  if (module_exists(\u0027oa_notifications\u0027) \u0026\u0026 function_exists(\u0027oa_notifications_save_notifications\u0027)) {\r\n    oa_notifications_save_notifications($values);\r\n  }\r\n" } }
    ]
  }
}

Again... thanks,
ds

Tricky.

othermachines's picture

I think I might have an idea. Could you paste the PHP in separately, from the field? The export makes a bit of a mess of it.

Be nice if the oa_ code was a bit better documented. Took a bit of searching to find where the notification is actually being sent.

Tricky... yes.

danielstrum's picture

Here is the PHP...

$nid = ($node->nid);
$space_id = ($node->og_group_ref ['und'] [0] ['target_id']);
$space_node = node_load($space_id);
$space_parent = ($space_node->oa_parent_space ['und'] [0] ['target_id']);
$values = array(
  'source_type' => 'node',
  'source_entity' => $node,
  'notifications' => array(
    'skip_notify' => 0,
    'team' => array(),
    'user' => array(),
    'group' => array(
      '' => $space_parent,
      )
  )
);

if (module_exists('oa_notifications') && function_exists('oa_notifications_save_notifications')) {
  oa_notifications_save_notifications($values);
}

Possible solutions...

othermachines's picture

The reason why your notifications aren't being sent is because the oa_notifications module updates the database on hook_node_insert(), then it is oa_messages (oa_core submodule) that sends the messages on hook_entity_insert(). These are two separate chains.

From oa_core/modules/oa_messages/oa_messages.module:

function oa_messages_entity_insert($entity, $type) {
  $types = oa_core_list_content_types(TRUE);
  if ($type == 'node' && array_key_exists($entity->type, $types)) {
    $message = oa_messages_create('oa_create', $entity, $type, '', NULL, $entity->uid);
  }
}

Now take a look at this snippet from oa_messages_create():

      $subscribe_options = oa_messages_build_subscribe_options($entity, $message);
      message_subscribe_send_message('node', $entity, $message, $notify_options, $subscribe_options);

Now a snippet from oa_messages_build_subscribe_options():

  if (module_exists('oa_notifications')) {
    $users = oa_notifications_get_notifications($entity);
    foreach ($users as $uid => $user) {
      // Disable certain notifiers based on preferences.
      $notifiers = oa_messages_determine_user_notifiers($user, $entity, $message);
      $options['uids'][$uid] = array(
        'entity' => $user,
        'notifiers' => $notifiers,
      );
    }
  }

By the time the PHP inside your rule is called, all of this has already happened.

The only way to get your rule to work on "After saving new content" would be to duplicate what is happening in both chains. I wouldn't recommend this because not only is it ridiculously complex, the module code could change at any time and your rule would cease to work. What you need to do is create the right conditions before the node is saved/inserted and let the modules do the work.

Three possible solutions:

  1. Set up your fields and values in Feed Import field settings. I have no idea if you can do this. You don't need to set 'source_entity' but you will need to somehow find your group IDs (tokens?). The structure is important, since oa_notifications will look for these values in $node->oa_notifications.

  2. Create your own module and alter the node entity in a hook_entity_presave().

  3. Set up these values in a rule on the event "Before saving content". You will be altering the entity object. I'm a bit fuzzy on how this is done, but I think you need to first check "Entity has field" then use action "Set a data value". Anyway, if your entity object is prepared correctly on presave, the oa_notifications module (and then oa_messages) should take it from there.

Let me know if that helps.

Suggesting #2...

othermachines's picture

Are you planning on packaging your work using Features, anyway? If so, I would go with #2 and implement hook_entity_presave() in the .module file rather than fuss with Rules. In fact, I would go with #2 regardless.

/**
* Implements hook_entity_presave().
*/
function MODULENAME_entity_presave($entity, $type) {
  if ( // Check values of $type, $entity->type and $entity->field_primary_from_email
    ) {
    // Set up your new values
    $entity->oa_notifications = array(
      // .. etc
    );
  }
}

That's it.

API: https://api.drupal.org/api/drupal/modules%21system%21system.api.php/func...

Time for me to go get a life. :-)

I got this working!

danielstrum's picture

Hi othermachines...

I just want to thank you again for your help on this. I did get it working (still though "Rules") and I will post more info detailing how I did it soon (just in case it can help others). Now I have another really odd issue that I am going to post separately.

Again, Thanks..
ds

Latest Update

danielstrum's picture

As noted above, I got this working using the "Rules" module but there are still issues that keep it from functioning as we would like. The way we would like this to work is that for each "Discussion Section" within a "Space", there would be a dedicated email address used to start a discussion via email and for each, when a discussion is started, notifications are sent to the "Groups" that are "Parents" of the "Space".

Each of these addresses would have a Mailhandler Mailbox and a Feed Importer. On each cron run, the emails would be checked and imported. Using Feed Importer "Commands" the "Space" and "Section" for each address would be statically set, corresponding to the specific Space/Section we want the discussion posted to.

First... this is how I configured the "rule":

  1. The event that triggers the rule is "Before saving content of type Discussion Post"
  2. First condition is "Entity is new", data selector is "node".
  3. Second condition is "Execute custom PHP code" and the code I am using is
    if ((empty($node -> oa_notifications)) && (!array_key_exists('oa_parent', $node))) {
       return 1;
    }
    else {
       return 0;
      }

    This confirms that the discussion is being started via this email system, is not a reply, and that the oa_notification variables must be set.
  4. The action is "Execute custom PHP code" and the code I am using is (I know this could be done more elegantly but, I am not a coder and this works for now):
    $space_id = ($node->og_group_ref ['und'] [0] ['target_id']);
    $space_node = node_load($space_id);
    $space_parent1 = ($space_node->oa_parent_space ['und'] [0] ['target_id']);
    $space_parent2 = ($space_node->oa_parent_space ['und'] [1] ['target_id']);
    $space_parent3 = ($space_node->oa_parent_space ['und'] [2] ['target_id']);
    $space_parent4 = ($space_node->oa_parent_space ['und'] [3] ['target_id']);
    $space_parent5 = ($space_node->oa_parent_space ['und'] [4] ['target_id']);
    $groups = array($space_parent1, $space_parent2, $space_parent3, $space_parent4, $space_parent5);
    $values = array(
      'source_type' => 'node',
      'source_entity' => $node,
      'notifications' => array(
        'skip_notify' => 0,
        'team' => array(),
        'user' => array(),
        'group' => $groups,
    )
    );
    $node->oa_notifications = $values;

This all works. When I send an email to the dedicated email addresss from an authorized (permissioned) email address and manually trigger cron, the email is fetched, imported, posted to the right space/section, and notifications are sent.

The first and most troubling issue I am encountering is that for ANY EMAILS (even basic discussion replies) using the setup described in this blog post:
http://www.phase2technology.com/blog/using-email-with-open-atrium-2/
notifications are not sent unless cron is manually triggered. Please see this issue: https://drupal.org/node/2279833

The second problem I believe has to do with this issue:
https://drupal.org/node/2194169
This means that administrators must add each individual user to both the "Space" and the "Group" in order for them to get notifications. In our case, we would like them to just be added to the Group, and then have notifications follow the same inheritance as permissions. One of our groups will be very dynamic and have thousands of members. Having to add and remove them from both the Group and the Space would be very difficult.

The third problem has to do with posting these discussions directly on the website. Before I updated to 7.18 (I think from 7.16), when a user would create a new discussion node on the site, the notifications widget would auto populate with the groups that were included in the notifications widget when the discussion section was created (or updated). After the update this no longer happens and the widget is always empty when creating a new discussion.

potassiumchloride's picture

I am creating tickets in our system via email, but I am having a lot of trouble getting my feeds importer command to work. I have a list (integer) field that should be set to 1 when my mailhandler feeds importer imports nodes. All of my other mappings are working, but this one is not.

Command is:
email: 1

Mapping is:
email: 1 (source) to field_ticket_created_by_email (target)

Everything else about Mailhandler is working just fine, nodes are created, other fields are set, an e-mail is sent to the submitter, etc., but this is the only mapping from a command. I simply can't figure out what is wrong with the command or how to troubleshoot, and all of the Drupal and Google searching is not getting me any closer. If anyone has thoughts or can point me in a direction to look, I would be grateful. Thanks!

Configuration error

potassiumchloride's picture

I have fixed the problem, and I am posting here so that others can learn from my mistake.

It had been a few weeks between setting up the Mailhandler source and the feeds importer, and I ran into difficulty because I was trying to set the default commands in the feeds importer instead of in the Mailhandler source node.

The solution: edit the Mailhandler source to add default commands in the form of key: label. Then, edit the feeds importer to add the command key to the "available commands" of the parser settings. Last, map the key to the correct field in the Mapping for Node Processor.

Now I have a working method for users to submit tickets via email and to receive a reply. I am using a "created by email" field that gets set to "Yes" when the nodes are created, and I use Rules to send an email to the submitter. A condition of the rule is that the "created by email" is set "Yes" so that creating a ticket manually on the site does not send this email.

Open Atrium

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: