Actions

Developer Area/Notification Plugins & Activities

From Mahara Wiki

< Developer Area

Mahara has a notification system for sending messages to users. On the implementation side, the system is divided into two parts:

  • Activities are the actions that trigger messages, such as posting to a group forum, sharing a page, joining an institution, sending a friend request, or direct messaging another user.
  • Notifications are the plugins that deliver those messages to the user. Mahara by default includes three notifications: Inbox, Email, and Email Digest.

If a Mahara user clicks on "Settings", there is a "Notifications" tab that will show up below the main nav. Clicking this takes you to that user's notification settings page. This page lists all of the different Activity types Mahara is currently aware of, and lets the user specify which Notification method they want to handle each of these (or none). It's worth noting, if the user picks a notification method other than "Inbox", they will still receive a notification in their Inbox, but it will be automatically marked "read".

The notification life-cycle

  1. A piece of Mahara code calls the method activity_occurred($activitytype, $data, $plugintype=null, $pluginname=null, $delay=null)
    1. If the $delay field is true, or if the activity is marked for delay in the activity_type table in the database, then the activity is inserted into the activity_queue table to be handled by the cron job.
    2. Otherwise, the activity is handled immediately.
  2. When the activity is handled, its ActivityType subclass is instantiated, and its notify_users() method is called. This in turn calls the singular notify_user() once for each user.
  3. The notify_user() method then looks up the user's preference for which Notification type should be used for this Activity. It calls the appropriate PluginNotification subclass's static notify_user() method. The PluginNotification subclass then sends the message to the user in some way.
  4. To ensure that notifications also show up in the user's inbox, the ActivityType also calls PluginNotificationInternal::notify_user(). If their notification preference was for inbox, it marks the message "unread". Otherwise, it marks it "read".

Sending a notification

As a developer, if you wish to send a notification for an existing activity type, all you need to do is call the activity_occurred() method.

// Core activity
activity_occurred('activitytype', $data);

// Plugin activity
activity_occurred('activitytype', $data, $plugintype, $pluginname);

// Force the notification to be queued to the cron.
activity_occurred('activitytype', $data, null, null, true);

You do not need to specify the Notification type, because that will be determined by the user's preferences.

The $data to include will depend upon the ActivityType's get_required_params() method. However, all fields of the $data object will be written to fields of the underyling ActivityType class instance, and some methods of ActivityType will use these as a sort of API. The following fields will be looked for by the default implementation of ActivityType, and they will be used if present in $data, and if the ActivityType in question has not overridden them.

  • strings: A list of string identifiers to generate the notification subject, message, and URL label. See the "Fill in your message" section below for more details.
  • subject: A string to use for the notification subject. (If you use this, make sure to use the method get_string_for_language() rather than get_string() when fetching the language string, so that you can use the recipient's language rather than the sender's!)
  • message: A string to use for the notification message body.
  • url: (Optional) A URL to put on the bottom of the notification.
  • urltext: (Optional) A label to use for the URL
  • fromuser: (Optional) The id of the user to show in the "from" column for this notification. (Or a hard-coded lang string, if the "from" is not a Mahara user.)

If you want to create a new activity type, you'll need to implement an ActivityType subclass, as described in the next section.

Activities

Activities are the things users can do that generate messages. Looked at another way, they're the thing you call to generate the appropriate message for the appropriate audience when a user takes a particular action.

Activities are an API, but not a full-fledged plugin. Each Activity is defined by a class that extends ActivityType. Core activities are defined in lib/activity.php.

Plugins can also define their own activity types, in their lib.php file. Plugin activities should follow the naming pattern "ActivityType{$plugintype}{$pluginname}{$activityname}". For example, the "feedback" activity of the "comment" Artefact is called ActivityTypeArtefactCommentFeedback.

The bare minimum to implement

1. Populate the list of users.

It's poorly documented, but an ActivityType must populate its "$this->users" attribute during construction. This must be an array of user records (such as those retrieved from the "usr" table, or from the "get_user($userid)" function). There are two ways this can be done.

1a. Provide a 'users' argument (containing an array of user records) in the $data object passed to activity_occurred. (Any parameters of $data get written as fields of the activity object during the default constructor.)

$msg = (object) array(
    'users'     => array($id),
    'url'       => profile_url($USER, false)
);
activity_occurred('maharamessage', $msg);

1b. Override the default constructor so that it calls the parent constructor, and then populates $this->users with a list of user records which should receive the message. This approach can be useful if your activity may be invoked many places, and you want to centralize the recipient logic.

public function __construct($data, $cron = false) {
    parent::__construct($data, $cron);
    $this->users = $this->get_my_users();
}

2. get_required_parameters()

This method should return an array which names the fields that must be present in $data. It should include all necessary data to determine the recipient(s) of the notification, as well as its content. (Any fields you list here should also be listed as fields of your activity class.)

You should not override the notify_users() and notify_user() methods, as they specify important details of how the class interacts with the PluginType classes. We should probably mark those "final".

Fill in your message

There are two ways to specify your message. Use whichever works better for you. Whichever you do, though, be sure to use get_string_for_language() rather than the more typical get_string() when fetching the message text, so that the message will use the proper language for the recipient!

1. Provide a "strings" object in the $data object that you pass to "activity_occurred()". This method is often simpler to implement, but it can get unwieldy if your activity is invoked at many different places in the code. The strings object should have fields called subject and message, each of which should be another object, with fields named key, section, and args, which will then get passed to lang_strings(). You can also pass a third field called urltext, to specify the label of the URL for the notification (assuming your notification has a URL).

activity_occurred('groupmessage', array(
    'group'         => $group->id,
    'deletedgroup'  => true,
    'strings'       => (object) array(
        'subject' => (object) array(
            'key'     => 'deletegroupnotificationsubject',
            'section' => 'group',
            'args'    => array(hsc($group->name)),
        ),
        'message' => (object) array(
            'key'     => 'deletegroupnotificationmessage',
            'section' => 'group',
            'args'    => array(hsc($group->name), get_config('sitename')),
        ),
    ),
));

2. Override get_message($user), get_subject($user), and (optionally) get_urltext($user) in your activity subclass. The fields of the "$data" object that you passed to "activity_occurred()" will be accessible as fields of "$this". If you do this, make sure to use get_string_from_language instead of get_string, so that the message will be in the language of the recipient!

    // Example (from the Watchlist activity)
    public function get_subject($user) {
        return get_string_from_language($user->lang, 'newwatchlistmessage', 'activity');
    }

    public function get_message($user) {
        return get_string_from_language($user->lang, 'newwatchlistmessageview1', 'activity',
                                        $this->viewinfo->get('title'), $this->ownerinfo);
    }

The activity_type table

All activities that Mahara knows about are listed in the activity_type table in the database. Core activities are inserted here by one of Mahara's post-installation methods. Plugin activities are installed by the plugin_upgrade() method at install or upgrade time.

The one interesting thing about this table is that it includes a delay column, which can be "0" or "1". If it's 1, then notifications for this activity will always be queued and handled by the cron job, regardless of whether the user specifies $delay=true when calling activity_occurred().

Notifications

Notifications are the methods by which users receive messages (email, email digest, SMS text message, etc).

Notifications are a full-fledged Plugin type in Mahara, with their own lang files, cron table, config table, etc, living under the "/notification" folder, and implemented by subclassing PluginNotification. The "emaildigest" notification type, for instance, uses its cron job to gather each day's emails into digest form.

The PluginNotification subclasses are never instantiated and contain only static methods. The most important method is notify_user($user, $data). This method is called once for each user who should receive a message, and it should send the message to them by whatever means the notification is meant to represent.

The plugin can specify additional data that it needs from the notification, by providing a static $userdata field, which should be an array. For each item in the array, a corresponding "get_" method will be looked for and called if present in the ActivityType method, and the result will be added to $userdata. For instance, the "email" notification adds "htmlmessage" and "emailmessage", and some ActivityTypes implement "get_htmlmessage()" and "get_emailmessage()" to provide alternate forms of the message for HTML email and standard email.

It may also be worth pointing out that "notify_user()" does not actually have to notify the user immediately. The "email digest" notification's notify_user() method inserts the message into a digest queue table, and then its cron job reads the messages out of that queue table and assembles them into digests.

See also