diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 580265a2c9b..93032d4e791 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -1044,7 +1044,7 @@ class core_plugin_manager { ), 'message' => array( - 'email', 'jabber', 'popup' + 'airnotifier', 'email', 'jabber', 'popup' ), 'mnetservice' => array( diff --git a/message/output/airnotifier/classes/manager.php b/message/output/airnotifier/classes/manager.php new file mode 100755 index 00000000000..e08b5cfdef3 --- /dev/null +++ b/message/output/airnotifier/classes/manager.php @@ -0,0 +1,191 @@ +. + +/** + * Airnotifier manager class + * + * @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.7 + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Airnotifier helper manager class + * + * @copyright 2012 Jerome Mouneyrac + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class message_airnotifier_manager { + + /** + * Include the relevant javascript and language strings for the device + * toolbox YUI module + * + * @return bool + */ + public function include_device_ajax() { + global $PAGE, $CFG; + + if (!$CFG->enableajax) { + return false; + } + + $config = new stdClass(); + $config->resturl = '/message/output/airnotifier/rest.php'; + $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; + } + + /** + * Return the user devices for a specific app. + * + * @param string $appname the app name . + * @param int $userid if empty take the current user. + * @return array all the devices + */ + public function get_user_devices($appname, $userid = null) { + global $USER, $DB; + + if (empty($userid)) { + $userid = $USER->id; + } + + $devices = array(); + + $params = array('appid' => $appname, 'userid' => $userid); + + // First, we look all the devices registered for this user in the Moodle core. + // We are going to allow only ios devices (since these are the ones that supports PUSH notifications). + $userdevices = $DB->get_records('user_devices', $params); + foreach ($userdevices as $device) { + if (core_text::strtolower($device->platform)) { + // Check if the device is known by airnotifier. + if (!$airnotifierdev = $DB->get_record('message_airnotifier_devices', + array('userdeviceid' => $device->id))) { + + // We have to create the device token in airnotifier. + if (! $this->create_token($device->pushid)) { + continue; + } + + $airnotifierdev = new stdClass; + $airnotifierdev->userdeviceid = $device->id; + $airnotifierdev->enable = 1; + $airnotifierdev->id = $DB->insert_record('message_airnotifier_devices', $airnotifierdev); + } + $device->id = $airnotifierdev->id; + $device->enable = $airnotifierdev->enable; + $devices[] = $device; + } + } + + return $devices; + } + + /** + * Request and access key to Airnotifier + * + * @return mixed The access key or false in case of error + */ + public function request_accesskey() { + global $CFG, $USER; + + require_once($CFG->libdir . '/filelib.php'); + + // Sending the request access key request to Airnotifier. + $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/accesskeys/'; + // We use an APP Key "none", it can be anything. + $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname, + 'X-AN-APP-KEY: none'); + $curl = new curl(); + $curl->setHeader($header); + + // Site ids are stored as secrets in md5 in the Moodle public hub. + $params = array( + 'url' => $CFG->wwwroot, + 'siteid' => md5($CFG->siteidentifier), + 'contact' => $USER->email, + 'description' => $CFG->wwwroot + ); + $resp = $curl->post($serverurl, $params); + + if ($key = json_decode($resp, true)) { + if (!empty($key['accesskey'])) { + return $key['accesskey']; + } + } + return false; + } + + /** + * Create a device token in the Airnotifier instance + * @param string $token The token to be created + * @return bool True if all was right + */ + private function create_token($token) { + global $CFG; + + if (!$this->is_system_configured()) { + return false; + } + + require_once($CFG->libdir . '/filelib.php'); + + $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/tokens/' . $token; + $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(); + $resp = $curl->post($serverurl, $params); + + if ($resp = json_decode($resp, true)) { + if (!empty($resp['status'])) { + return $resp['status'] == 'ok' || $resp['status'] == 'token exists'; + } + } + return false; + } + + /** + * Tests whether the airnotifier settings have been configured + * @return boolean true if airnotifier is configured + */ + public function is_system_configured() { + global $CFG; + + return (!empty($CFG->airnotifierurl) && !empty($CFG->airnotifierport) && + !empty($CFG->airnotifieraccesskey) && !empty($CFG->airnotifierappname) && + !empty($CFG->airnotifiermobileappname)); + } + +} diff --git a/message/output/airnotifier/db/access.php b/message/output/airnotifier/db/access.php new file mode 100755 index 00000000000..ac0fc11a0ab --- /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_SYSTEM, + '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..81c0bf4d6d8 --- /dev/null +++ b/message/output/airnotifier/db/install.php @@ -0,0 +1,39 @@ +. + +/** + * 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 $CFG, $DB; + + $result = true; + + $provider = new stdClass(); + $provider->name = 'airnotifier'; + $provider->enabled = 0; + $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..ec529ff0924 --- /dev/null +++ b/message/output/airnotifier/db/install.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + +
+
+
\ 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..f7fa49ec9c1 --- /dev/null +++ b/message/output/airnotifier/db/services.php @@ -0,0 +1,43 @@ +. + + +/** + * 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_is_system_configured' => array( + 'classname' => 'message_airnotifier_external', + 'methodname' => 'is_system_configured', + 'classpath' => 'message/output/airnotifier/externallib.php', + 'description' => 'Check whether the airnotifier settings have been configured', + 'type' => 'read', + ), + + 'message_airnotifier_are_notification_preferences_configured' => array( + 'classname' => 'message_airnotifier_external', + 'methodname' => 'are_notification_preferences_configured', + 'classpath' => 'message/output/airnotifier/externallib.php', + 'description' => 'Check if the users have notification preferences configured yet', + 'type' => 'read', + ), +); diff --git a/message/output/airnotifier/externallib.php b/message/output/airnotifier/externallib.php new file mode 100755 index 00000000000..dbe89d59eb9 --- /dev/null +++ b/message/output/airnotifier/externallib.php @@ -0,0 +1,213 @@ +. + +/** + * External 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.7 + */ + +defined('MOODLE_INTERNAL') || die; + +require_once("$CFG->libdir/externallib.php"); + +/** + * 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.7 + */ +class message_airnotifier_external extends external_api { + + /** + * Returns description of method parameters + * + * @since Moodle 2.7 + */ + public static function is_system_configured_parameters() { + return new external_function_parameters( + array() + ); + } + + /** + * Tests whether the airnotifier settings have been configured + * + * @since Moodle 2.7 + */ + public static function is_system_configured() { + global $DB; + + // First, check if the plugin is disabled. + $processor = $DB->get_record('message_processors', array('name' => 'airnotifier'), '*', MUST_EXIST); + if (!$processor->enabled) { + return 0; + } + + // Then, check if the plugin is completly configured. + $manager = new message_airnotifier_manager(); + return (int) $manager->is_system_configured(); + } + + /** + * Returns description of method result value + * + * @return external_single_structure + * @since Moodle 2.7 + */ + public static function is_system_configured_returns() { + return new external_value( PARAM_INT, '0 if the system is not configured, 1 otherwise'); + } + + /** + * Returns description of method parameters + * + * @since Moodle 2.7 + */ + public static function are_notification_preferences_configured_parameters() { + return new external_function_parameters( + array( + 'userids' => new external_multiple_structure(new external_value(PARAM_INT, 'user ID')), + ) + ); + } + + /** + * Check if the users have notification preferences configured for the airnotifier plugin + * + * @param array $userids Array of user ids + * @since Moodle 2.7 + */ + public static function are_notification_preferences_configured($userids) { + global $CFG, $USER, $DB; + + require_once($CFG->dirroot . '/message/lib.php'); + $params = self::validate_parameters(self::are_notification_preferences_configured_parameters(), + array('userids' => $userids)); + + list($sqluserids, $params) = $DB->get_in_or_equal($params['userids'], SQL_PARAMS_NAMED); + $uselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); + $ujoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)"; + $params['contextlevel'] = CONTEXT_USER; + $usersql = "SELECT u.* $uselect + FROM {user} u $ujoin + WHERE u.id $sqluserids"; + $users = $DB->get_recordset_sql($usersql, $params); + + $result = array( + 'users' => array(), + 'warnings' => array() + ); + $hasuserupdatecap = has_capability('moodle/user:update', context_system::instance()); + foreach ($users as $user) { + + $currentuser = ($user->id == $USER->id); + + if ($currentuser or $hasuserupdatecap) { + + if (!empty($user->deleted)) { + $warning = array(); + $warning['item'] = 'user'; + $warning['itemid'] = $user->id; + $warning['warningcode'] = '1'; + $warning['message'] = "User $user->id was deleted"; + $result['warnings'][] = $warning; + continue; + } + + $preferences = array(); + $preferences['userid'] = $user->id; + $preferences['configured'] = 0; + + // Now we get for all the providers and all the states + // the user preferences to check if at least one is enabled for airnotifier plugin. + $providers = message_get_providers_for_user($user->id); + $configured = false; + + foreach ($providers as $provider) { + if ($configured) { + break; + } + + foreach (array('loggedin', 'loggedoff') as $state) { + if ($configured) { + break; + } + + $prefname = 'message_provider_'.$provider->component.'_'.$provider->name.'_'.$state; + $linepref = get_user_preferences($prefname, '', $user->id); + if ($linepref == '') { + continue; + } + $lineprefarray = explode(',', $linepref); + + foreach ($lineprefarray as $pref) { + if ($pref == 'airnotifier') { + $preferences['configured'] = 1; + $configured = true; + break; + } + } + } + } + + $result['users'][] = $preferences; + } else if (!$hasuserupdatecap) { + $warning = array(); + $warning['item'] = 'user'; + $warning['itemid'] = $user->id; + $warning['warningcode'] = '2'; + $warning['message'] = "You don't have permissions for view user $user->id preferences"; + $result['warnings'][] = $warning; + } + + } + $users->close(); + + return $result; + } + + /** + * Returns description of method result value + * + * @return external_single_structure + * @since Moodle 2.7 + */ + public static function are_notification_preferences_configured_returns() { + return new external_single_structure( + array( + 'users' => new external_multiple_structure( + new external_single_structure( + array ( + 'userid' => new external_value(PARAM_INT, 'userid id'), + 'configured' => new external_value(PARAM_INT, + '1 if the user preferences have been configured and 0 if not') + ) + ), + 'list of preferences by user'), + 'warnings' => new external_warnings() + ) + ); + } + +} \ No newline at end of file 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..4edc66d739c --- /dev/null +++ b/message/output/airnotifier/lang/en/message_airnotifier.php @@ -0,0 +1,48 @@ +. + +/** + * 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['airnotifiermobileappname'] = 'Mobile app name'; +$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 identifier in Airnotifier.'; +$string['configairnotifiermobileappname'] = 'The Mobile app unique identifier (usually something like com.moodle.moodlemobile).'; +$string['deletecheckdevicename'] = 'Delete your device: {$a->name}'; +$string['deletedevice'] = 'Delete the device. Note that an app can register the device again. If the device keeps reappearing, disable it.'; +$string['devicetoken'] = 'Device token'; +$string['errorretrievingkey'] = 'An error occurred while retrieving key. Your Moodle site must be registered with Moodle.org to use this service. You may need to re-register to update your details there.'; +$string['keyretrievedsuccessfully'] = 'Key retrieved successfully'; +$string['nodevices'] = 'No registered devices. Devices will automatically register after you allow a Moodle iOS app to receive push notifications.'; +$string['nopermissiontomanagedevices'] = 'You don\'t have permission to manage devices.'; +$string['notconfigured'] = 'The Airnotifier server hasn\'t been configured so Airnotifier messages cannot be sent'; +$string['pluginname'] = 'Mobile notifications'; +$string['sitemustberegistered'] = 'In order to use the public Airnotifier instance you must register your site with Moodle.org'; +$string['showhide'] = 'Enable/disable the device.'; +$string['requestaccesskey'] = 'Request access key'; +$string['unknowndevice'] = 'Unknown device'; +$string['airnotifier:managedevice'] = 'Mange devices'; diff --git a/message/output/airnotifier/message_output_airnotifier.php b/message/output/airnotifier/message_output_airnotifier.php new file mode 100755 index 00000000000..b1fe4cf5a6c --- /dev/null +++ b/message/output/airnotifier/message_output_airnotifier.php @@ -0,0 +1,213 @@ +. + +/** + * Airnotifier message processor to send messages to the APNS provider: airnotfier. (https://github.com/dongsheng/airnotifier) + * + * @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.7 + */ + + +require_once($CFG->dirroot . '/message/output/lib.php'); + +/** + * Message processor class + * + * @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 + */ + public 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; + } + + // Site id, to map with Moodle Mobile stored sites. + $siteid = md5($CFG->wwwroot . $eventdata->userto->username); + + // 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( + "site" => $siteid, + "type" => $eventdata->component . '_' . $eventdata->name, + "device" => "xxxxxxxxxx", // Since at this point we don't know the device, we use a 10 chars device platform. + "notif" => "x", // 1 or 0 wheter is a notification or not (it may be a private message). + "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 - core_text::strlen(json_encode($notificationdata)); + $message = s($eventdata->smallmessage); + // If the message size is too big make it shorter. + if (core_text::strlen($message) >= $maxmsgsize) { + + // Cut the message to the maximum possible size. -4 for the the ending 3 dots (...). + $message = core_text::substr($message, 0 , $maxmsgsize - 4); + + // We need to check when the message is "escaped" then the message is not too long. + $encodedmsgsize = core_text::strlen(json_encode($message)); + if ($encodedmsgsize > $maxmsgsize) { + $totalescapedchar = $encodedmsgsize - core_text::strlen($message); + // Cut the message to the maximum possible size (taking the escaped character in account). + $message = core_text::substr($message, 0 , $maxmsgsize - 4 - $totalescapedchar); + } + + $message = $message . '...'; + } + + // We are sending to message to all devices. + $airnotifiermanager = new message_airnotifier_manager(); + $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifiermobileappname, $eventdata->userto->id); + + foreach ($devicetokens as $devicetoken) { + + if (!$devicetoken->enable) { + continue; + } + + // 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, + 'site' => $siteid, + 'type' => $eventdata->component . '_' . $eventdata->name, + 'userfrom' => fullname($eventdata->userfrom), + 'device' => $devicetoken->platform, + 'notif' => (!empty($eventdata->notification)) ? '1' : '0', + 'token' => $devicetoken->pushid); + $resp = $curl->post($serverurl, $params); + } + + return true; + } + + /** + * Creates necessary fields in the messaging config form. + * + * @param array $preferences An array of user preferences + */ + public function config_form($preferences) { + global $CFG, $OUTPUT, $USER, $PAGE; + + $systemcontext = context_system::instance(); + if (!has_capability('message/airnotifier:managedevice', $systemcontext)) { + return get_string('nopermissiontomanagedevices', 'message_airnotifier'); + } + + if (!$this->is_system_configured()) { + return get_string('notconfigured', 'message_airnotifier'); + } else { + + $PAGE->requires->css('/message/output/airnotifier/style.css'); + + $airnotifiermanager = new message_airnotifier_manager(); + $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifiermobileappname, $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')); + $name = "{$devicetoken->name} {$devicetoken->model} {$devicetoken->platform} {$devicetoken->version}"; + $hideurl = new moodle_url('message/output/airnotifier/action.php', + array('hide' => !$devicetoken->enable, 'deviceid' => $devicetoken->id, + 'sesskey' => sesskey())); + + $output .= html_writer::start_tag('li', array('id' => $devicetoken->id, + 'class' => 'airnotifierdevice ' . $dimmed)) . "\n"; + $output .= html_writer::label($name, 'deviceid-' . $devicetoken->id, array('class' => 'devicelabel ')) . ' ' . + html_writer::link($hideurl, $hideshowicon, array('class' => 'hidedevice', 'alt' => 'show/hide')) . "\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 + */ + public function process_form($form, &$preferences) { + return true; + } + + /** + * 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 + */ + public function load_data(&$preferences, $userid) { + return true; + } + + /** + * Tests whether the airnotifier settings have been configured + * @return boolean true if airnotifier is configured + */ + public function is_system_configured() { + $airnotifiermanager = new message_airnotifier_manager(); + return $airnotifiermanager->is_system_configured(); + } +} + diff --git a/message/output/airnotifier/requestaccesskey.php b/message/output/airnotifier/requestaccesskey.php new file mode 100755 index 00000000000..457832d97cc --- /dev/null +++ b/message/output/airnotifier/requestaccesskey.php @@ -0,0 +1,78 @@ +. + +/** + * Request access key to AirNotifier + * + * @package message_airnotifier + * @copyright 2012 Jerome Mouneyrac + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require('../../../config.php'); +require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php'); + +define('AIRNOTIFIER_PUBLICURL', 'http://messages.moodle.net'); + +$PAGE->set_url(new moodle_url('/message/output/airnotifier/requestaccesskey.php')); +$PAGE->set_context(context_system::instance()); + +require_login(); +require_sesskey(); +require_capability('moodle/site:config', context_system::instance()); + +$strheading = get_string('requestaccesskey', 'message_airnotifier'); +$PAGE->navbar->add(get_string('administrationsite')); +$PAGE->navbar->add(get_string('plugins', 'admin')); +$PAGE->navbar->add(get_string('messageoutputs', 'message')); +$returl = new moodle_url('/admin/settings.php', array('section' => 'messagesettingairnotifier')); +$PAGE->navbar->add(get_string('pluginname', 'message_airnotifier'), $returl); +$PAGE->navbar->add($strheading); + +$PAGE->set_heading($strheading); +$PAGE->set_title($strheading); + +$msg = ""; + +// If we are requesting a key to the official message system, verify first that this site is registered. +// This check is also done in Airnotifier. +if (strpos($CFG->airnotifierurl, AIRNOTIFIER_PUBLICURL) !== false ) { + $registrationmanager = new registration_manager(); + if (!$registrationmanager->get_registeredhub(HUB_MOODLEORGHUBURL)) { + $msg = get_string('sitemustberegistered', 'message_airnotifier'); + $msg .= $OUTPUT->continue_button($returl); + + echo $OUTPUT->header(); + echo $OUTPUT->box($msg, 'generalbox'); + echo $OUTPUT->footer(); + die; + } +} + +$manager = new message_airnotifier_manager(); + +if ($key = $manager->request_accesskey()) { + set_config('airnotifieraccesskey', $key); + $msg = get_string('keyretrievedsuccessfully', 'message_airnotifier'); +} else { + $msg = get_string('errorretrievingkey', 'message_airnotifier'); +} + +$msg .= $OUTPUT->continue_button($returl); + +echo $OUTPUT->header(); +echo $OUTPUT->box($msg, 'generalbox '); +echo $OUTPUT->footer(); diff --git a/message/output/airnotifier/rest.php b/message/output/airnotifier/rest.php new file mode 100755 index 00000000000..bf3712e5fe9 --- /dev/null +++ b/message/output/airnotifier/rest.php @@ -0,0 +1,59 @@ +. + +/** + * 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 + */ + + +define('AJAX_SCRIPT', true); + +require_once(dirname(__FILE__) . '/../../../config.php'); + +// Initialise ALL the incoming parameters here, up front. +$id = required_param('id', PARAM_INT); +$enable = required_param('enable', PARAM_BOOL); + +require_login(); +require_sesskey(); + +$systemcontext = context_system::instance(); + +$PAGE->set_url('/message/output/airnotifier/rest.php'); +$PAGE->set_context($systemcontext); + +require_capability('message/airnotifier:managedevice', $systemcontext); + +echo $OUTPUT->header(); + +// Response class to be converted to json string. +$response = new stdClass(); + +$device = $DB->get_record('message_airnotifier_devices', array('id' => $id), '*', MUST_EXIST); + +// Check that the device belongs to the current user. +$userdevice = $DB->get_record('user_devices', array('id' => $device->userdeviceid, 'userid' => $USER->id), '*', MUST_EXIST); + +$device->enable = required_param('enable', PARAM_BOOL); +$DB->update_record('message_airnotifier_devices', $device); +$response->success = true; + +echo json_encode($response); +die; \ No newline at end of file diff --git a/message/output/airnotifier/settings.php b/message/output/airnotifier/settings.php new file mode 100755 index 00000000000..d788f75371c --- /dev/null +++ b/message/output/airnotifier/settings.php @@ -0,0 +1,47 @@ +. + +/** + * Airnotifier configuration page + * + * @package message_airnotifier + * @copyright 2012 Jerome Mouneyrac, 2014 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die; + +if ($ADMIN->fulltree) { + // 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://messages.moodle.net', PARAM_URL)); + $settings->add(new admin_setting_configtext('airnotifierport', + get_string('airnotifierport', 'message_airnotifier'), + get_string('configairnotifierport', 'message_airnotifier'), '80', PARAM_INT)); + $settings->add(new admin_setting_configtext('airnotifiermobileappname', + get_string('airnotifiermobileappname', 'message_airnotifier'), + get_string('configairnotifiermobileappname', 'message_airnotifier'), 'com.moodle.moodlemobile', PARAM_TEXT)); + $settings->add(new admin_setting_configtext('airnotifierappname', + get_string('airnotifierappname', 'message_airnotifier'), + get_string('configairnotifierappname', 'message_airnotifier'), 'commoodlemoodlemobile', PARAM_TEXT)); + $settings->add(new admin_setting_configtext('airnotifieraccesskey', + get_string('airnotifieraccesskey', 'message_airnotifier'), + get_string('configairnotifieraccesskey', 'message_airnotifier'), '', PARAM_ALPHANUMEXT)); + + $url = new moodle_url('/message/output/airnotifier/requestaccesskey.php', array('sesskey' => sesskey())); + $link = html_writer::link($url, get_string('requestaccesskey', 'message_airnotifier')); + $settings->add(new admin_setting_heading('requestaccesskey', '', $link)); +} diff --git a/message/output/airnotifier/style.css b/message/output/airnotifier/style.css new file mode 100755 index 00000000000..46b41d16a33 --- /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/2014 Jerome Mouneyrac / Juan Leyva + 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..906bc42a99c --- /dev/null +++ b/message/output/airnotifier/tests/externallib_test.php @@ -0,0 +1,127 @@ +. + +/** + * 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_advanced_testcase { + + /** + * Tests set up + */ + protected function setUp() { + global $CFG; + require_once($CFG->dirroot . '/message/output/airnotifier/externallib.php'); + } + + /** + * Test is_system_configured + */ + public function test_is_system_configured() { + + $this->resetAfterTest(true); + + $user = self::getDataGenerator()->create_user(); + self::setUser($user); + + // In a clean installation, it should be not configured. + $configured = message_airnotifier_external::is_system_configured(); + $this->assertEquals(0, $configured); + + // Fake configuration. + set_config('airnotifieraccesskey', random_string()); + $configured = message_airnotifier_external::is_system_configured(); + $this->assertEquals(1, $configured); + } + + /** + * Test are_notification_preferences_configured + */ + public function test_are_notification_preferences_configured() { + + $this->resetAfterTest(true); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + self::setUser($user1); + + set_user_preference('message_provider_moodle_instantmessage_loggedin', 'airnotifier', $user1); + set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'airnotifier', $user1); + set_user_preference('message_provider_moodle_instantmessage_loggedin', 'airnotifier', $user2); + set_user_preference('message_provider_moodle_instantmessage_loggedin', 'airnotifier', $user3); + + $params = array($user1->id, $user2->id, $user3->id); + + $preferences = message_airnotifier_external::are_notification_preferences_configured($params); + + $expected = array( + array( + 'userid' => $user1->id, + 'configured' => 1 + ) + ); + + $this->assertEquals(1, count($preferences['users'])); + $this->assertEquals($expected, $preferences['users']); + $this->assertEquals(2, count($preferences['warnings'])); + + // Now, remove one user. + delete_user($user2); + $preferences = message_airnotifier_external::are_notification_preferences_configured($params); + $this->assertEquals(1, count($preferences['users'])); + $this->assertEquals($expected, $preferences['users']); + $this->assertEquals(2, count($preferences['warnings'])); + + // Now, remove one user1 preference (the user still has one prefernce for airnotifier). + unset_user_preference('message_provider_moodle_instantmessage_loggedin', $user1); + $preferences = message_airnotifier_external::are_notification_preferences_configured($params); + $this->assertEquals($expected, $preferences['users']); + + // Delete the last user1 preference. + unset_user_preference('message_provider_moodle_instantmessage_loggedoff', $user1); + $preferences = message_airnotifier_external::are_notification_preferences_configured($params); + $expected = array( + array( + 'userid' => $user1->id, + 'configured' => 0 + ) + ); + $this->assertEquals($expected, $preferences['users']); + } + +} diff --git a/message/output/airnotifier/version.php b/message/output/airnotifier/version.php new file mode 100755 index 00000000000..ef1b91cc05a --- /dev/null +++ b/message/output/airnotifier/version.php @@ -0,0 +1,28 @@ +. + +/** + * 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 = 2014012800; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2013111800; // Requires this Moodle version. +$plugin->component = 'message_airnotifier'; // Full name of the plugin (used for diagnostics). diff --git a/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-debug.js b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-debug.js new file mode 100644 index 00000000000..acd18da77eb --- /dev/null +++ b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-debug.js @@ -0,0 +1,252 @@ +YUI.add('moodle-message_airnotifier-toolboxes', function (Y, NAME) { + +/** + * Provides a tool for enabling/disabling elements using AJAX/REST. + * + * @module moodle-message_airnotifier-toolboxes + */ + +WAITICON = { + 'pix':"i/loading_small", + 'component':'moodle' +}; +// The CSS selectors we use. +var CSS = { + AIRNOTIFIERCONTENT : 'fieldset#messageprocessor_airnotifier', + HIDEDEVICE : 'a.hidedevice', + 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 toggle_class = CSS.DIMMEDTEXT; + + var status = ''; + if (element.hasClass(toggle_class)) { + status = 'hide'; + } else { + status = 'show'; + } + + // 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); + }, + /** + * 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 callbacksuccess Call back on success + * @return response responseText field from responce + */ + send_request : function(data, statusspinner, callbacksuccess) { + // 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]; + } + + if (statusspinner) { + statusspinner.show(); + } + + 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) { + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(responsetext).show(); + }); + } else if (responsetext.success) { + callbacksuccess(); + } + } catch (e) {} + if (statusspinner) { + statusspinner.hide(); + } + }, + failure : function(tid, response) { + if (statusspinner) { + statusspinner.hide(); + } + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(response).show(); + }); + } + }, + context: this, + sync: false + }; + + // 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) { + + // Show/Hide. + var showhide = this.replace_button(toolboxtarget, CSS.HIDEDEVICE, this.toggle_hide_device); + }, + 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; + // Enable the device in case the CSS is dimmed. + if (element.hasClass(CSS.DIMMEDTEXT)) { + value = 1; + } else { + value = 0; + } + + // Send the request. + var data = { + 'field' : 'enable', + 'enable' : value, + 'id' : this.get_element_id(element) + }; + var spinner = M.util.add_spinner(Y, element); + + var context = this; + var callback = function() { + context.toggle_hide_device_ui(button); + }; + this.send_request(data, spinner, callback); + } +}, { + 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"]}); diff --git a/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-min.js b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-min.js new file mode 100644 index 00000000000..63e59ca7052 --- /dev/null +++ b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-min.js @@ -0,0 +1 @@ +YUI.add("moodle-message_airnotifier-toolboxes",function(e,t){WAITICON={pix:"i/loading_small",component:"moodle"};var n={AIRNOTIFIERCONTENT:"fieldset#messageprocessor_airnotifier",HIDEDEVICE:"a.hidedevice",DEVICELI:"li.airnotifierdevice",DIMCLASS:"dimmed",DIMMEDTEXT:"dimmed_text",DEVICEIDPREFIX:"deviceid-"},r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{replace_button:function(t,n,r,i){i||(i="pointer");var s=e.one(t).all(n).setStyle("cursor",i);return s.on("click",r,this),s},toggle_hide_device_ui:function(e){var t=e.ancestor(n.DEVICELI),r=e.one("img"),i=n.DIMMEDTEXT,s="";t.hasClass(i)?s="hide":s="show",t.toggleClass(i);var o=M.util.get_string(s,"moodle");r.setAttrs({alt:o,title:o,src:M.util.image_url("t/"+s)}),e.set("title",o),e.set("className","editing_"+s)},send_request:function(t,n,r){t||(t={});var i=this.get("config").pageparams;for(varname in i)t[varname]=i[varname];n&&n.show(),t.sesskey=M.cfg.sesskey;var s=M.cfg.wwwroot+this.get("ajaxurl"),o=[],u={method:"POST",data:t,on:{success:function(t,i){try{o=e.JSON.parse(i.responseText),o.error?e.use("moodle-core-notification-ajaxexception",function(){return(new M.core.ajaxException(o)).show()}):o.success&&r()}catch(s){}n&&n.hide()},failure:function(t,r){n&&n.hide(),e.use("moodle-core-notification-ajaxexception",function(){return(new M.core.ajaxException(r)).show()})}},context:this,sync:!1};return e.io(s,u),o},get_element_id:function(e){return e.get("id").replace(n.DEVICEIDPREFIX,"")}},{NAME:"device-toolbox",ATTRS:{ajaxurl:{value:0},config:{value:0}}});var i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,r,{initializer:function(e){this.setup_for_device()},setup_for_device:function(t){if(!t)var t=n.AIRNOTIFIERCONTENT;e.all(t).each(this._setup_for_device,this)},_setup_for_device:function(e){var t=this.replace_button(e,n.HIDEDEVICE,this.toggle_hide_device)},toggle_hide_device:function(t){t.preventDefault();var r=t.target.ancestor(n.DEVICELI),i=t.target.ancestor("a",!0),s;r.hasClass(n.DIMMEDTEXT)?s=1:s=0;var o={field:"enable",enable:s,id:this.get_element_id(r)},u=M.util.add_spinner(e,r),a=this,f=function(){a.toggle_hide_device_ui(i)};this.send_request(o,u,f)}},{NAME:"message-device-toolbox",ATTRS:{}}),M.message=M.message||{},M.message.init_device_toolbox=function(e){return new i(e)}},"@VERSION@",{requires:["base","node","io"]}); diff --git a/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes.js b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes.js new file mode 100644 index 00000000000..acd18da77eb --- /dev/null +++ b/message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes.js @@ -0,0 +1,252 @@ +YUI.add('moodle-message_airnotifier-toolboxes', function (Y, NAME) { + +/** + * Provides a tool for enabling/disabling elements using AJAX/REST. + * + * @module moodle-message_airnotifier-toolboxes + */ + +WAITICON = { + 'pix':"i/loading_small", + 'component':'moodle' +}; +// The CSS selectors we use. +var CSS = { + AIRNOTIFIERCONTENT : 'fieldset#messageprocessor_airnotifier', + HIDEDEVICE : 'a.hidedevice', + 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 toggle_class = CSS.DIMMEDTEXT; + + var status = ''; + if (element.hasClass(toggle_class)) { + status = 'hide'; + } else { + status = 'show'; + } + + // 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); + }, + /** + * 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 callbacksuccess Call back on success + * @return response responseText field from responce + */ + send_request : function(data, statusspinner, callbacksuccess) { + // 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]; + } + + if (statusspinner) { + statusspinner.show(); + } + + 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) { + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(responsetext).show(); + }); + } else if (responsetext.success) { + callbacksuccess(); + } + } catch (e) {} + if (statusspinner) { + statusspinner.hide(); + } + }, + failure : function(tid, response) { + if (statusspinner) { + statusspinner.hide(); + } + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(response).show(); + }); + } + }, + context: this, + sync: false + }; + + // 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) { + + // Show/Hide. + var showhide = this.replace_button(toolboxtarget, CSS.HIDEDEVICE, this.toggle_hide_device); + }, + 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; + // Enable the device in case the CSS is dimmed. + if (element.hasClass(CSS.DIMMEDTEXT)) { + value = 1; + } else { + value = 0; + } + + // Send the request. + var data = { + 'field' : 'enable', + 'enable' : value, + 'id' : this.get_element_id(element) + }; + var spinner = M.util.add_spinner(Y, element); + + var context = this; + var callback = function() { + context.toggle_hide_device_ui(button); + }; + this.send_request(data, spinner, callback); + } +}, { + 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"]}); diff --git a/message/output/airnotifier/yui/src/toolboxes/build.json b/message/output/airnotifier/yui/src/toolboxes/build.json new file mode 100644 index 00000000000..6a1b51af7a3 --- /dev/null +++ b/message/output/airnotifier/yui/src/toolboxes/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-message_airnotifier-toolboxes", + "builds": { + "moodle-message_airnotifier-toolboxes": { + "jsfiles": [ + "toolboxes.js" + ] + } + } +} diff --git a/message/output/airnotifier/yui/src/toolboxes/js/toolboxes.js b/message/output/airnotifier/yui/src/toolboxes/js/toolboxes.js new file mode 100755 index 00000000000..fcb362a5c45 --- /dev/null +++ b/message/output/airnotifier/yui/src/toolboxes/js/toolboxes.js @@ -0,0 +1,247 @@ +/** + * Provides a tool for enabling/disabling elements using AJAX/REST. + * + * @module moodle-message_airnotifier-toolboxes + */ + +WAITICON = { + 'pix':"i/loading_small", + 'component':'moodle' +}; +// The CSS selectors we use. +var CSS = { + AIRNOTIFIERCONTENT : 'fieldset#messageprocessor_airnotifier', + HIDEDEVICE : 'a.hidedevice', + 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 toggle_class = CSS.DIMMEDTEXT; + + var status = ''; + if (element.hasClass(toggle_class)) { + status = 'hide'; + } else { + status = 'show'; + } + + // 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); + }, + /** + * 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 callbacksuccess Call back on success + * @return response responseText field from responce + */ + send_request : function(data, statusspinner, callbacksuccess) { + // 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]; + } + + if (statusspinner) { + statusspinner.show(); + } + + 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) { + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(responsetext).show(); + }); + } else if (responsetext.success) { + callbacksuccess(); + } + } catch (e) {} + if (statusspinner) { + statusspinner.hide(); + } + }, + failure : function(tid, response) { + if (statusspinner) { + statusspinner.hide(); + } + Y.use('moodle-core-notification-ajaxexception', function() { + return new M.core.ajaxException(response).show(); + }); + } + }, + context: this, + sync: false + }; + + // 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) { + + // Show/Hide. + var showhide = this.replace_button(toolboxtarget, CSS.HIDEDEVICE, this.toggle_hide_device); + }, + 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; + // Enable the device in case the CSS is dimmed. + if (element.hasClass(CSS.DIMMEDTEXT)) { + value = 1; + } else { + value = 0; + } + + // Send the request. + var data = { + 'field' : 'enable', + 'enable' : value, + 'id' : this.get_element_id(element) + }; + var spinner = M.util.add_spinner(Y, element); + + var context = this; + var callback = function() { + context.toggle_hide_device_ui(button); + }; + this.send_request(data, spinner, callback); + } +}, { + NAME : 'message-device-toolbox', + ATTRS : { +} +}); + +M.message = M.message || {}; + +M.message.init_device_toolbox = function(config) { + return new DEVICETOOLBOX(config); +}; + diff --git a/message/output/airnotifier/yui/src/toolboxes/meta/toolboxes.json b/message/output/airnotifier/yui/src/toolboxes/meta/toolboxes.json new file mode 100644 index 00000000000..402d910c089 --- /dev/null +++ b/message/output/airnotifier/yui/src/toolboxes/meta/toolboxes.json @@ -0,0 +1,9 @@ +{ + "moodle-message_airnotifier-toolboxes": { + "requires": [ + "base", + "node", + "io" + ] + } +}