diff --git a/message/output/airnotifier/db/access.php b/message/output/airnotifier/db/access.php
new file mode 100755
index 00000000000..e890372b3b5
--- /dev/null
+++ b/message/output/airnotifier/db/access.php
@@ -0,0 +1,37 @@
+.
+
+/**
+ * Capability definitions for airnotifier.
+ *
+ * @package message_airnotifier
+ * @category access
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$capabilities = array(
+
+ 'message/airnotifier:managedevice' => array(
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_USER,
+ 'archetypes' => array(
+ 'user' => CAP_ALLOW
+ )
+ )
+);
diff --git a/message/output/airnotifier/db/install.php b/message/output/airnotifier/db/install.php
new file mode 100755
index 00000000000..6e439ba4cdd
--- /dev/null
+++ b/message/output/airnotifier/db/install.php
@@ -0,0 +1,38 @@
+.
+
+/**
+ * Airnotifier message processor installation code
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Install the Airnotifier message processor
+ */
+function xmldb_message_airnotifier_install() {
+ global $DB;
+
+ $result = true;
+
+ $provider = new stdClass();
+ $provider->name = 'airnotifier';
+ $DB->insert_record('message_processors', $provider);
+ return $result;
+}
diff --git a/message/output/airnotifier/db/install.xml b/message/output/airnotifier/db/install.xml
new file mode 100755
index 00000000000..e4df629034a
--- /dev/null
+++ b/message/output/airnotifier/db/install.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/message/output/airnotifier/db/services.php b/message/output/airnotifier/db/services.php
new file mode 100755
index 00000000000..47c3c45aaca
--- /dev/null
+++ b/message/output/airnotifier/db/services.php
@@ -0,0 +1,44 @@
+.
+
+
+/**
+ * Airnotifier external functions and service definitions.
+ *
+ * @package message_airnotifier
+ * @category webservice
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$functions = array(
+ 'message_airnotifier_add_user_device' => array(
+ 'classname' => 'message_airnotifier_external',
+ 'methodname' => 'add_user_device',
+ 'classpath' => 'message/output/airnotifier/externallib.php',
+ 'description' => 'Add device to user device list',
+ 'type' => 'write',
+ ),
+
+ 'message_airnotifier_get_access_key' => array(
+ 'classname' => 'message_airnotifier_external',
+ 'methodname' => 'get_access_key',
+ 'classpath' => 'message/output/airnotifier/externallib.php',
+ 'description' => 'Get the mobile device access key with specified permissions',
+ 'type' => 'read',
+ ),
+);
+
diff --git a/message/output/airnotifier/db/upgrade.php b/message/output/airnotifier/db/upgrade.php
new file mode 100755
index 00000000000..acee7891e9a
--- /dev/null
+++ b/message/output/airnotifier/db/upgrade.php
@@ -0,0 +1,68 @@
+.
+
+/**
+ * Upgrade code for airnotifier message processor
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Upgrade code for the airnotifier message processor
+ *
+ * @param int $oldversion The version that we are upgrading from
+ */
+function xmldb_message_airnotifier_upgrade($oldversion) {
+ global $CFG, $DB;
+
+ $dbman = $DB->get_manager();
+
+
+ if ($oldversion < 2012070500.05) {
+
+ // Define table user_devices to be created
+ $table = new xmldb_table('airnotifier_user_devices');
+
+ // Adding fields to table user_devices
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('appname', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('devicename', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+ $table->add_field('devicetype', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('deviceos', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('deviceosversion', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('devicebrand', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('devicenotificationtoken', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+ $table->add_field('deviceuid', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+ $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('enable', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
+
+ // Adding keys to table user_devices
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ // Conditionally launch create table for user_devices
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+ // Main savepoint reached
+ upgrade_plugin_savepoint(true, 2012070500.05);
+ }
+
+ return true;
+}
+
diff --git a/message/output/airnotifier/externallib.php b/message/output/airnotifier/externallib.php
new file mode 100755
index 00000000000..6f52b8a2425
--- /dev/null
+++ b/message/output/airnotifier/externallib.php
@@ -0,0 +1,150 @@
+.
+
+/**
+ * external API for airnotifier web services
+ *
+ * @package message_airnotifier
+ * @category external
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+class message_airnotifier_external extends external_api {
+
+ /**
+ * Returns description of add_user_device() parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.4
+ */
+ public static function add_user_device_parameters() {
+ return new external_function_parameters(
+ array('device' => new external_single_structure(
+ array(
+ 'appname' => new external_value( PARAM_TEXT, 'the app name'),
+ 'devicename' => new external_value( PARAM_TEXT, 'Device name: "Jerome\'s iPhone"', VALUE_OPTIONAL),
+ 'devicetype' => new external_value( PARAM_TEXT, 'iPhone 3GS, Google Nexus S, ...', VALUE_OPTIONAL),
+ 'deviceos' => new external_value( PARAM_TEXT, 'iOS, Android, ...', VALUE_OPTIONAL),
+ 'deviceosversion' => new external_value( PARAM_TEXT, 'OS version number', VALUE_OPTIONAL),
+ 'devicebrand' => new external_value( PARAM_TEXT, 'the device brand (Apple, Samsung, ...)', VALUE_OPTIONAL),
+ 'devicenotificationtoken' => new external_value( PARAM_RAW, 'the device token used to send notification for the specified app'),
+ 'deviceuid' => new external_value( PARAM_RAW, 'the device unique device id if it exists', VALUE_OPTIONAL),
+ ), 'the device information - Important: type, os, osversion and brand will be saved in lowercase for fast searching'
+ )
+ ));
+ }
+
+ /**
+ * Add a device to the user device list
+ *
+ * @param array $device
+ * @return int device id
+ * @since Moodle 2.4
+ */
+ public static function add_user_device($device) {
+ global $USER, $CFG;
+
+ $params = self::validate_parameters(self::add_user_device_parameters(),
+ array('device'=>$device));
+
+ // Ensure the current user is allowed to run this function
+ $context = context_user::instance($USER->id);
+ self::validate_context($context);
+ require_capability('message/airnotifier:managedevice', $context);
+
+ $device['userid'] = $USER->id;
+
+ require_once($CFG->dirroot . "/message/output/airnotifier/lib.php");
+ $airnotifiermanager = new airnotifier_manager();
+ $device['id'] = $airnotifiermanager->add_user_device($device);
+
+ return $device['id'];
+ }
+
+ /**
+ * Returns description of add_user_device() result value
+ *
+ * @return external_single_structure
+ * @since Moodle 2.4
+ */
+ public static function add_user_device_returns() {
+ return new external_value( PARAM_INT, 'Device id in the Moodle database');
+ }
+
+ /**
+ * Returns description of get_access_key() parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.4
+ */
+ public static function get_access_key_parameters() {
+ return new external_function_parameters(
+ array('permissions' => new external_multiple_structure(
+ new external_value( PARAM_ALPHA, 'the permission'),
+ 'Allowed permissions: createtoken (not yet implemented: deletetoken, accessobjects,
+ sendnotification, sendbroadcast)',
+ VALUE_DEFAULT, array()
+ )
+ ));
+ }
+
+ /**
+ * Get access key with specified permissions
+ *
+ * @param array $permissions the permission that the access key should
+ * @return string access key
+ * @since Moodle 2.4
+ */
+ public static function get_access_key($permissions = array()) {
+ global $CFG;
+
+ $params = self::validate_parameters(self::get_access_key_parameters(),
+ array('permissions'=>$permissions));
+
+ // Check that user can use the requested permission.
+ foreach ($params['permissions'] as $perm) {
+ switch ($perm) {
+ case 'createtoken':
+ // Any mobile device / user should have this permission.
+ // No need to check anything for this permission.
+
+ break;
+
+ default:
+ throw new moodle_exception('permissionnotimplemented');
+ break;
+ }
+ }
+
+ // Look for access key that have exactly the same permissions.
+ // TODO: This mobile device access key should be retrieve by web service from
+ // moodle.org or airnotifer when the admin enables mobile on Moodle.
+ $accesskey = $CFG->airnotifierdeviceaccesskey;
+
+ return $accesskey;
+ }
+
+ /**
+ * Returns description of add_user_device() result value
+ *
+ * @return external_single_structure
+ * @since Moodle 2.4
+ */
+ public static function get_access_key_returns() {
+ return new external_value( PARAM_ALPHANUMEXT, 'access key');
+ }
+}
diff --git a/message/output/airnotifier/lang/en/message_airnotifier.php b/message/output/airnotifier/lang/en/message_airnotifier.php
new file mode 100755
index 00000000000..9f94a3599fe
--- /dev/null
+++ b/message/output/airnotifier/lang/en/message_airnotifier.php
@@ -0,0 +1,43 @@
+.
+
+/**
+ * Strings for component 'message_airnotifier', language 'en'
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['airnotifieraccesskey'] = 'Airnotifier access key';
+$string['airnotifierappname'] = 'Airnotifier app name';
+$string['airnotifierdeviceaccesskey'] = 'Airnotifier device access key';
+$string['airnotifierport'] = 'Airnotifier port';
+$string['airnotifierurl'] = 'Airnotifier URL';
+$string['configairnotifierurl'] = 'The server url to connect to to send push notifications.';
+$string['configairnotifierport'] = 'The port to use when connecting to the airnotifier server.';
+$string['configairnotifieraccesskey'] = 'The access key to use when connecting to the airnotifier server.';
+$string['configairnotifierappname'] = 'The app name where Airnotifier will send notification.';
+$string['configairnotifierdeviceaccesskey'] = 'The access key used by mobile devices when connecting to the airnotifier server.';
+$string['deletecheckdevicename'] = 'Delete your device: {$a->name}';
+$string['deletedevice'] = 'Delete the device. Note that it will not forbid an app to register it again. If the device keep reappearing, disable it.';
+$string['devicetoken'] = 'Device token';
+$string['nodevices'] = 'No registered devices. Devices are automatically registered when you allow a Moodle iOS app to receive push notifications.';
+$string['notconfigured'] = 'The Airnotifier server hasn\'t been configured so Airnotifier messages cannot be sent';
+$string['pluginname'] = 'iPhone/iPad';
+$string['showhide'] = 'Enable/disable the device.';
+$string['unknowndevice'] = 'Unknown device';
diff --git a/message/output/airnotifier/lib.php b/message/output/airnotifier/lib.php
new file mode 100755
index 00000000000..57c2d41348d
--- /dev/null
+++ b/message/output/airnotifier/lib.php
@@ -0,0 +1,139 @@
+.
+
+/**
+ * Airnotifier related functions
+ *
+ * @package message_airnotifier
+ * @category external
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+class airnotifier_manager {
+
+ /**
+ * Include the relevant javascript and language strings for the device
+ * toolbox YUI module
+ *
+ * @return bool
+ */
+ function include_device_ajax() {
+ global $PAGE, $CFG;
+
+ if (!$CFG->enableajax) {
+ return false;
+ }
+
+ $config = new stdClass();
+
+ // The URL to use for resource changes
+ if (!isset($config->resturl)) {
+ $config->resturl = '/message/output/airnotifier/rest.php';
+ }
+
+ // Any additional parameters which need to be included on page submission
+ if (!isset($config->pageparams)) {
+ $config->pageparams = array();
+ }
+
+ // Include toolboxes
+ $PAGE->requires->yui_module('moodle-message_airnotifier-toolboxes', 'M.message.init_device_toolbox', array(array(
+ 'ajaxurl' => $config->resturl,
+ 'config' => $config,
+ ))
+ );
+
+ // Required strings for the javascript
+ $PAGE->requires->strings_for_js(array('deletecheckdevicename'), 'message_airnotifier');
+ $PAGE->requires->strings_for_js(array('show','hide'), 'moodle');
+
+ return true;
+ }
+
+ /**
+ * Add device to the user device list.
+ *
+ * @param object $device
+ * @param int $userid if empty take the current user
+ * @return int device id (in Moodle database)
+ */
+ public function add_user_device($device, $userid = null) {
+ global $DB, $USER;
+
+ if (empty($user)) {
+ $userid = $USER->id;
+ }
+
+ $device = (object) $device;
+
+ // Check if the device token already exist - Note that we look for user in case several users use the same device
+ $existingdevice = $DB->get_record('airnotifier_user_devices', array('appname' => $device->appname,
+ 'devicenotificationtoken' => $device->devicenotificationtoken, 'userid' => $userid));
+
+ // trim the data (some field should be in lowercase to make the search on them quickly)
+ $attributstolower = array('devicetype', 'deviceos', 'deviceosversion', 'devicebrand');
+ if ($existingdevice) {
+ foreach ($device as $attributname => $value) {
+ if (in_array($attributname, $attributstolower)) {
+ $existingdevice->{$attributname} = strtolower($value);
+ } else {
+ $existingdevice->{$attributname} = $value;
+ }
+ }
+ } else {
+ foreach ($attributstolower as $attribut) {
+ if (!empty($device->{$attribut})) {
+ $device->{$attribut} = strtolower($device->{$attribut});
+ }
+ }
+ }
+
+ if ($existingdevice) {
+ $DB->update_record('airnotifier_user_devices', $existingdevice);
+ return $existingdevice->id;
+ } else {
+ return $DB->insert_record('airnotifier_user_devices', $device);
+ }
+ }
+
+ /**
+ * Return the user devices for a specific app.
+ *
+ * @param string $appname the app name .
+ * @param int $userid if empty take the current user.
+ * @param int $enabled if 1 returned only enabled devices, if 0 only disabled devices, if null all devices.
+ * @return array all the devices
+ */
+ public function get_user_devices($appname, $userid = null, $enabled = null) {
+ global $USER, $DB;
+
+ if (empty($userid)) {
+ $userid = $USER->id;
+ }
+
+ $params = array('appname' => $appname, 'userid' => $userid);
+ if ($enabled !== null) {
+ $params['enable'] = $enabled;
+ }
+
+ return $DB->get_records('airnotifier_user_devices', $params);
+ }
+
+}
+
+?>
diff --git a/message/output/airnotifier/message_output_airnotifier.php b/message/output/airnotifier/message_output_airnotifier.php
new file mode 100755
index 00000000000..202cfcaaf48
--- /dev/null
+++ b/message/output/airnotifier/message_output_airnotifier.php
@@ -0,0 +1,195 @@
+.
+
+require_once($CFG->dirroot . '/message/output/lib.php');
+require_once($CFG->dirroot . '/message/output/airnotifier/lib.php');
+
+/**
+ * Airnotifier message processor to send messages to the APNS provider: airnotfier.
+ * (https://github.com/dongsheng/airnotifier)
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class message_output_airnotifier extends message_output {
+
+ /**
+ * Processes the message and sends a notification via airnotifier
+ *
+ * @param stdClass $eventdata the event data submitted by the message sender plus $eventdata->savedmessageid
+ * @return true if ok, false if error
+ */
+ function send_message($eventdata) {
+ global $CFG;
+
+ if (!empty($CFG->noemailever)) {
+ // hidden setting for development sites, set in config.php if needed
+ debugging('$CFG->noemailever active, no airnotifier message sent.', DEBUG_MINIMAL);
+ return true;
+ }
+
+ // skip any messaging suspended and deleted users
+ if ($eventdata->userto->auth === 'nologin' or $eventdata->userto->suspended or
+ $eventdata->userto->deleted) {
+ return true;
+ }
+
+ // Mandatory notification data that need to be sent in the payload. They have variable length.
+ // We need to take them in consideration to calculate the maximum message size.
+ $notificationdata = array("urlparams" => $eventdata->urlparams, "id" => $eventdata->savedmessageid,
+ "type" => $eventdata->component . '_' . $eventdata->name, "userfrom" => fullname($eventdata->userfrom));
+
+ // Calculate the size of the message knowing Apple payload must be lower than 256 bytes.
+ // Airnotifier using few bytes of the payload, we must limit our message to even less characters.
+ $maxmsgsize = 205 - strlen(json_encode($notificationdata));
+ $message = $eventdata->smallmessage;
+ // If the message size is too big make it shorter
+ if (strlen($message) >= $maxmsgsize) {
+
+ // Cut the message to the maximum possible size. -4 for the the ending 3 dots (...).
+ $message = substr($message, 0 , $maxmsgsize - 4);
+
+ // We need to check when the message is "escaped" then the message is not too long.
+ $encodedmsgsize = strlen(json_encode($message));
+ if ($encodedmsgsize > $maxmsgsize) {
+ $totalescapedchar = $encodedmsgsize - strlen($message);
+ // Cut the message to the maximum possible size (taking the escaped character in account)
+ $message = substr($message, 0 , $maxmsgsize - 4 - $totalescapedchar);
+ }
+
+ $message = $message . '...';
+ }
+
+ // We are sending to message to all devices
+ $airnotifiermanager = new airnotifier_manager();
+ $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifierappname, $eventdata->userto->id, true);
+
+ foreach ($devicetokens as $devicetoken) {
+
+ // Sending the message to the device
+ $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/notification/';
+ $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname,
+ 'X-AN-APP-KEY: ' . $CFG->airnotifieraccesskey);
+ $curl = new curl;
+ $curl->setHeader($header);
+ $params = array('alert' => $message,
+ 'date' => $eventdata->timecreated,
+ 'urlparams' => json_encode($eventdata->urlparams),
+ 'type' => $eventdata->component . '_' . $eventdata->name,
+ 'userfrom' => fullname($eventdata->userfrom),
+ 'id' => $eventdata->savedmessageid,
+ 'token' => $devicetoken->devicenotificationtoken);
+ $resp = $curl->post($serverurl, $params);
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates necessary fields in the messaging config form.
+ *
+ * @param array $preferences An array of user preferences
+ */
+ function config_form($preferences) {
+ global $CFG, $OUTPUT, $USER, $PAGE;
+
+ if (!$this->is_system_configured()) {
+ return get_string('notconfigured', 'message_airnotifier');
+ } else {
+
+ $PAGE->requires->css('/message/output/airnotifier/style.css');
+
+ $airnotifiermanager = new airnotifier_manager();
+ $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifierappname, $USER->id);
+
+ if (!empty($devicetokens)) {
+ $output = '';
+
+ foreach ($devicetokens as $devicetoken) {
+
+ if ($devicetoken->enable) {
+ $hideshowiconname = 't/hide';
+ $dimmed = '';
+ } else {
+ $hideshowiconname = 't/show';
+ $dimmed = 'dimmed_text';
+ }
+
+ $hideshowicon = $OUTPUT->pix_icon($hideshowiconname, get_string('showhide', 'message_airnotifier'));
+ $deleteicon = $OUTPUT->pix_icon('t/delete', get_string('deletedevice', 'message_airnotifier'));
+ $devicename = empty($devicetoken->devicename) ? get_string('unknowndevice', 'message_airnotifier') : s($devicetoken->devicename);
+ $hideurl = new moodle_url('message/output/airnotifier/action.php',
+ array('hide' => !$devicetoken->enable, 'deviceid' => $devicetoken->id,
+ 'sesskey' => sesskey()));
+ $deleteurl = new moodle_url('message/output/airnotifier/action.php',
+ array('delete' => true, 'deviceid' => $devicetoken->id,
+ 'sesskey' => sesskey()));
+
+ $output .= html_writer::start_tag('li', array('id' => $devicetoken->id, 'class' => 'airnotifierdevice ' . $dimmed)) . "\n";
+ $output .= html_writer::label($devicename, 'deviceid-' . $devicetoken->id, array('class' => 'devicelabel ')) . ' ' .
+ html_writer::link($hideurl, $hideshowicon, array('class' => 'hidedevice', 'alt' => 'show/hide')) . '' .
+ html_writer::link($deleteurl, $deleteicon, array('class' => 'deletedevice', 'alt' => 'delete')) . "\n";
+ $output .= html_writer::end_tag('li') . "\n";
+ }
+
+ // Include the AJAX script to automatically trigger the action
+ $airnotifiermanager->include_device_ajax();
+
+ $output = html_writer::tag('ul', $output, array('id' => 'airnotifierdevices'));
+ } else {
+ $output = get_string('nodevices', 'message_airnotifier');
+ }
+ return $output;
+ }
+ }
+
+ /**
+ * Parses the submitted form data and saves it into preferences array.
+ *
+ * @param stdClass $form preferences form class
+ * @param array $preferences preferences array
+ */
+ function process_form($form, &$preferences) {
+ if (isset($form->airnotifier_devicetoken) && !empty($form->airnotifier_devicetoken)) {
+ $preferences['message_processor_airnotifier_devicetoken'] = $form->airnotifier_devicetoken;
+ }
+ }
+
+ /**
+ * Loads the config data from database to put on the form during initial form display
+ *
+ * @param array $preferences preferences array
+ * @param int $userid the user id
+ */
+ function load_data(&$preferences, $userid) {
+ $preferences->airnotifier_devicetoken =
+ get_user_preferences('message_processor_airnotifier_devicetoken', '', $userid);
+ }
+
+ /**
+ * Tests whether the airnotifier settings have been configured
+ * @return boolean true if airnotifier is configured
+ */
+ function is_system_configured() {
+ global $CFG;
+ return (!empty($CFG->airnotifierurl) && !empty($CFG->airnotifierport) &&
+ !empty($CFG->airnotifieraccesskey) && !empty($CFG->airnotifierdeviceaccesskey) &&
+ !empty($CFG->airnotifierappname));
+ }
+}
+
diff --git a/message/output/airnotifier/rest.php b/message/output/airnotifier/rest.php
new file mode 100755
index 00000000000..982830648f9
--- /dev/null
+++ b/message/output/airnotifier/rest.php
@@ -0,0 +1,72 @@
+.
+
+/**
+ * Provide interface for AJAX device actions
+ *
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package message_airnotifier
+ */
+
+if (!defined('AJAX_SCRIPT')) {
+ define('AJAX_SCRIPT', true);
+}
+require_once(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->dirroot.'/message/output/airnotifier/lib.php');
+
+// Initialise ALL the incoming parameters here, up front.
+$field = optional_param('field', '', PARAM_ALPHA);
+$id = required_param('id', PARAM_INT);
+$pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a DELETE command
+
+$usercontext = context_user::instance($USER->id);
+
+$PAGE->set_url('/message/output/airnotifier/rest.php');
+$PAGE->set_context($usercontext);
+require_login();
+require_sesskey();
+
+echo $OUTPUT->header(); // send headers
+
+// OK, now let's process the parameters and do stuff
+// MDL-10221 the DELETE method is not allowed on some web servers, so we simulate it with the action URL param
+$requestmethod = $_SERVER['REQUEST_METHOD'];
+if ($pageaction == 'DELETE') {
+ $requestmethod = 'DELETE';
+}
+
+$device = $DB->get_record('airnotifier_user_devices', array('id' => $id), '*', MUST_EXIST);
+
+$airnotifiermanager = new airnotifier_manager();
+
+switch($requestmethod) {
+ case 'POST':
+ switch ($field) {
+ case 'enable':
+ require_capability('message/airnotifier:managedevice', $usercontext);
+ $device->enable = required_param('enable', PARAM_BOOL);
+ $DB->update_record('airnotifier_user_devices', $device);
+ break;
+ }
+ break;
+
+ case 'DELETE':
+ require_capability('message/airnotifier:managedevice', $usercontext);
+ $DB->delete_records('airnotifier_user_devices', array('id' => $id));
+ break;
+}
diff --git a/message/output/airnotifier/settings.php b/message/output/airnotifier/settings.php
new file mode 100755
index 00000000000..f9a8c0191d5
--- /dev/null
+++ b/message/output/airnotifier/settings.php
@@ -0,0 +1,45 @@
+.
+
+/**
+ * Airnotifier configuration page
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+ //TODO: It will be better for these settings to be automatically retrieved by web service from moodle.org when enabling mobile.
+ // The processor should be enabled by the same enable mobile setting.
+ $settings->add(new admin_setting_configtext('airnotifierurl',
+ get_string('airnotifierurl', 'message_airnotifier'),
+ get_string('configairnotifierurl', 'message_airnotifier'), 'http://notify.moodle.net', PARAM_URL));
+ $settings->add(new admin_setting_configtext('airnotifierport',
+ get_string('airnotifierport', 'message_airnotifier'),
+ get_string('configairnotifierport', 'message_airnotifier'), '8801', PARAM_INT));
+ $settings->add(new admin_setting_configtext('airnotifieraccesskey',
+ get_string('airnotifieraccesskey', 'message_airnotifier'),
+ get_string('configairnotifieraccesskey', 'message_airnotifier'), '34220428981805c610cacc22aa6635fb', PARAM_ALPHANUMEXT));
+ $settings->add(new admin_setting_configtext('airnotifierdeviceaccesskey',
+ get_string('airnotifierdeviceaccesskey', 'message_airnotifier'),
+ get_string('configairnotifierdeviceaccesskey', 'message_airnotifier'), 'e6b15c1ac0606980c46f87c476c9f28e', PARAM_ALPHANUMEXT));
+ $settings->add(new admin_setting_configtext('airnotifierappname',
+ get_string('airnotifierappname', 'message_airnotifier'),
+ get_string('configairnotifierappname', 'message_airnotifier'), 'moodlehtml5app', PARAM_TEXT));
+}
diff --git a/message/output/airnotifier/style.css b/message/output/airnotifier/style.css
new file mode 100755
index 00000000000..cb030449778
--- /dev/null
+++ b/message/output/airnotifier/style.css
@@ -0,0 +1,29 @@
+/**
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+
+ Airnotifier (message output plugin) CSS
+
+ package message_airnotifier
+ category css
+ copyright 2012 Jerome Mouneyrac
+ license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+ul#airnotifierdevices {
+ list-style: none;
+ margin: 0em;
+}
\ No newline at end of file
diff --git a/message/output/airnotifier/tests/externallib_test.php b/message/output/airnotifier/tests/externallib_test.php
new file mode 100755
index 00000000000..5ced13822c3
--- /dev/null
+++ b/message/output/airnotifier/tests/externallib_test.php
@@ -0,0 +1,86 @@
+.
+
+/**
+ * External airnotifier functions unit tests
+ *
+ * @package message_airnotifier
+ * @category external
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+/**
+ * External airnotifier functions unit tests
+ *
+ * @package message_airnotifier
+ * @category external
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class message_airnotifier_external_testcase extends externallib_testcase {
+
+ /**
+ * Tests set up
+ */
+ protected function setUp() {
+ global $CFG;
+ require_once($CFG->dirroot . '/lib/externallib.php');
+ require_once($CFG->dirroot . '/webservice/externallib.php');
+ }
+
+ /**
+ * Test add_user_device
+ */
+ public function test_add_user_device() {
+
+ global $DB, $USER;
+
+ $this->resetAfterTest(true);
+
+ $user = self::getDataGenerator()->create_user();
+ self::setUser($user);
+
+ $device = array();
+ $device['appname'] = 'mymoodle';
+ $device['devicename'] = 'Jerome\'s Android';
+ $device['devicetype'] = 'galaxy nexus';
+ $device['deviceos'] = 'android';
+ $device['deviceosversion'] = '4.0.3';
+ $device['devicebrand'] = 'samsung';
+ $device['devicenotificationtoken'] = 'jhg576576sesgy98sd7g87sdg697sfg576df';
+ $device['deviceuid'] = 'is87fs64g2vuf84g378gbh378ehg98h875';
+
+ $deviceid = message_airnotifier_external::add_user_device($device);
+
+ $devicedb = $DB->get_record('user_devices', array('id' => $deviceid));
+ $this->assertEquals($devicedb->devicename, $device['devicename']);
+ $this->assertEquals($devicedb->devicetype, $device['devicetype']);
+ $this->assertEquals($devicedb->deviceos, $device['deviceos']);
+ $this->assertEquals($devicedb->deviceosversion, $device['deviceosversion']);
+ $this->assertEquals($devicedb->devicebrand, $device['devicebrand']);
+ $this->assertEquals($devicedb->devicenotificationtoken, $device['devicenotificationtoken']);
+ $this->assertEquals($devicedb->deviceuid, $device['deviceuid']);
+ $this->assertEquals($devicedb->userid, $USER->id);
+ }
+
+}
diff --git a/message/output/airnotifier/version.php b/message/output/airnotifier/version.php
new file mode 100755
index 00000000000..64545d1a2af
--- /dev/null
+++ b/message/output/airnotifier/version.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Airnotifier message processor version information
+ *
+ * @package message_airnotifier
+ * @copyright 2012 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2012070500.05; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2012110101.01; // Requires this Moodle version
+$plugin->component = 'message_airnotifier'; // Full name of the plugin (used for diagnostics)
diff --git a/message/output/airnotifier/yui/toolboxes/toolboxes.js b/message/output/airnotifier/yui/toolboxes/toolboxes.js
new file mode 100755
index 00000000000..8a6e824974c
--- /dev/null
+++ b/message/output/airnotifier/yui/toolboxes/toolboxes.js
@@ -0,0 +1,277 @@
+YUI.add('moodle-message_airnotifier-toolboxes', function(Y) {
+ WAITICON = {
+ 'pix':"i/loading_small",
+ 'component':'moodle'
+ };
+ // The CSS selectors we use
+ var CSS = {
+ AIRNOTIFIERCONTENT : 'fieldset#messageprocessor_airnotifier',
+ HIDEDEVICE : 'a.hidedevice',
+ DELETEDEVICE : 'a.deletedevice',
+ DEVICELI : 'li.airnotifierdevice',
+ DIMCLASS : 'dimmed',
+ DIMMEDTEXT : 'dimmed_text',
+ DEVICEIDPREFIX : 'deviceid-'
+ };
+
+ /**
+ * The toolbox classes
+ *
+ * TOOLBOX is a generic class which should never be directly instantiated
+ * DEVICETOOLBOX is a class extending TOOLBOX containing code specific to devices
+ */
+ var TOOLBOX = function() {
+ TOOLBOX.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(TOOLBOX, Y.Base, {
+ /**
+ * Replace the button click at the selector with the specified
+ * callback
+ *
+ * @param toolboxtarget The selector of the working area
+ * @param selector The 'button' to replace
+ * @param callback The callback to apply
+ * @param cursor An optional cursor style to apply
+ */
+ replace_button : function(toolboxtarget, selector, callback, cursor) {
+ if (!cursor) {
+ // Set the default cursor type to pointer to match the
+ // anchor
+ cursor = 'pointer';
+ }
+ var button = Y.one(toolboxtarget).all(selector)
+ .setStyle('cursor', cursor);
+
+ // on isn't chainable and will return an event
+ button.on('click', callback, this);
+
+ return button;
+ },
+ /**
+ * Toggle the visibility and availability for the specified
+ * device show/hide button
+ */
+ toggle_hide_device_ui : function(button) {
+
+ var element = button.ancestor(CSS.DEVICELI);
+ var hideicon = button.one('img');
+
+ var dimarea;
+ var toggle_class = CSS.DIMMEDTEXT;
+
+ var status = '';
+ var value;
+ if (element.hasClass(toggle_class)) {
+ status = 'hide';
+ value = 1;
+ } else {
+ status = 'show';
+ value = 0;
+ }
+
+ // Change the UI
+ element.toggleClass(toggle_class);
+ // We need to toggle dimming on the description too
+ // element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+ var newstring = M.util.get_string(status, 'moodle');
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'title' : newstring,
+ 'src' : M.util.image_url('t/' + status)
+ });
+ button.set('title', newstring);
+ button.set('className', 'editing_'+status);
+
+ return value;
+ },
+ /**
+ * Send a request using the REST API
+ *
+ * @param data The data to submit
+ * @param statusspinner (optional) A statusspinner which may contain a section loader
+ * @param optionalconfig (optional) Any additional configuration to submit
+ * @return response responseText field from responce
+ */
+ send_request : function(data, statusspinner, optionalconfig) {
+ // Default data structure
+ if (!data) {
+ data = {};
+ }
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ for (varname in pageparams) {
+ data[varname] = pageparams[varname];
+ }
+
+ data.sesskey = M.cfg.sesskey;
+
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ // Define the configuration to send with the request
+ var responsetext = [];
+ var config = {
+ method: 'POST',
+ data: data,
+ on: {
+ success: function(tid, response) {
+ try {
+ responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ } catch (e) {}
+ if (statusspinner) {
+ window.setTimeout(function(e) {
+ statusspinner.hide();
+ }, 400);
+ }
+ },
+ failure : function(tid, response) {
+ if (statusspinner) {
+ statusspinner.hide();
+ }
+ new M.core.ajaxException(response);
+ }
+ },
+ context: this,
+ sync: true
+ }
+
+ // Apply optional config
+ if (optionalconfig) {
+ for (varname in optionalconfig) {
+ config[varname] = optionalconfig[varname];
+ }
+ }
+
+ if (statusspinner) {
+ statusspinner.show();
+ }
+
+ // Send the request
+ Y.io(uri, config);
+ return responsetext;
+ },
+ /**
+ * Return the module ID for the specified element
+ *
+ * @param element The element to determine a module-id number for
+ * @return string The module ID
+ */
+ get_element_id : function(element) {
+ return element.get('id').replace(CSS.DEVICEIDPREFIX, '');
+ }
+ },
+ {
+ NAME : 'device-toolbox',
+ ATTRS : {
+ ajaxurl : {
+ 'value' : 0
+ },
+ config : {
+ 'value' : 0
+ }
+ }
+ }
+ );
+
+ var DEVICETOOLBOX = function() {
+ DEVICETOOLBOX.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(DEVICETOOLBOX, TOOLBOX, {
+
+ /**
+ * Initialize the device toolbox
+ *
+ * Updates all span.commands with relevant handlers and other required changes
+ */
+ initializer : function(config) {
+ this.setup_for_device();
+ },
+ /**
+ * Update any span.commands within the scope of the specified
+ * selector with AJAX equivelants
+ *
+ * @param baseselector The selector to limit scope to
+ * @return void
+ */
+ setup_for_device : function(baseselector) {
+ if (!baseselector) {
+ var baseselector = CSS.AIRNOTIFIERCONTENT;
+ }
+
+ Y.all(baseselector).each(this._setup_for_device, this);
+ },
+ _setup_for_device : function(toolboxtarget) {
+ // Delete
+ this.replace_button(toolboxtarget, CSS.DELETEDEVICE, this.delete_device);
+
+ // Show/Hide
+ var showhide = this.replace_button(toolboxtarget, CSS.HIDEDEVICE, this.toggle_hide_device);
+ },
+ delete_device : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.DEVICELI);
+
+ var confirmstring = '';
+ var plugindata = {
+ name : element.one('*').getHTML() //get the label
+ }
+ confirmstring = M.util.get_string('deletecheckdevicename', 'message_airnotifier', plugindata);
+
+ // Confirm element removal
+ if (!confirm(confirmstring)) {
+ return false;
+ }
+
+ // Actually remove the element
+ element.remove();
+ var data = {
+ 'class' : 'device',
+ 'action' : 'DELETE',
+ 'id' : this.get_element_id(element)
+ };
+ this.send_request(data);
+ },
+ toggle_hide_device : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.DEVICELI);
+
+ var button = e.target.ancestor('a', true);
+
+ var value = this.toggle_hide_device_ui(button);
+
+ // Send the request
+ var data = {
+ 'field' : 'enable',
+ 'enable' : value,
+ 'id' : this.get_element_id(element)
+ };
+ var spinner = M.util.add_spinner(Y, element);
+ this.send_request(data, spinner);
+ }
+ }, {
+ NAME : 'message-device-toolbox',
+ ATTRS : {
+ }
+ });
+
+ M.message = M.message || {};
+
+ M.message.init_device_toolbox = function(config) {
+ return new DEVICETOOLBOX(config);
+ };
+
+},
+'@VERSION@', {
+ requires : ['base', 'node', 'io']
+}
+);