Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Airnotifier message processor to send messages to the APNS provider: airnotfier. (https://github.com/dcai/airnotifier)
19
 *
20
 * @package    message_airnotifier
21
 * @category   external
22
 * @copyright  2012 Jerome Mouneyrac <jerome@moodle.com>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 * @since Moodle 2.7
25
 */
26
 
27
 
28
require_once($CFG->dirroot . '/message/output/lib.php');
29
 
30
/**
31
 * Message processor class
32
 *
33
 * @package   message_airnotifier
34
 * @copyright 2012 Jerome Mouneyrac
35
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class message_output_airnotifier extends message_output {
38
 
39
    /**
40
     * Processes the message and sends a notification via airnotifier
41
     *
42
     * @param stdClass $eventdata the event data submitted by the message sender plus $eventdata->savedmessageid
43
     * @return true if ok, false if error
44
     */
45
    public function send_message($eventdata) {
46
        global $CFG, $DB;
47
        require_once($CFG->libdir . '/filelib.php');
48
 
49
        if (!empty($CFG->noemailever)) {
50
            // Hidden setting for development sites, set in config.php if needed.
51
            debugging('$CFG->noemailever active, no airnotifier message sent.', DEBUG_MINIMAL);
52
            return true;
53
        }
54
 
55
        // Skip any messaging suspended and deleted users.
56
        if ($eventdata->userto->auth === 'nologin' or
57
            $eventdata->userto->suspended or
58
            $eventdata->userto->deleted) {
59
            return true;
60
        }
61
 
62
        // If username is empty we try to retrieve it, since it's required to generate the siteid.
63
        if (empty($eventdata->userto->username)) {
64
            $eventdata->userto->username = $DB->get_field('user', 'username', array('id' => $eventdata->userto->id));
65
        }
66
 
67
        // Site id, to map with Moodle Mobile stored sites.
68
        $siteid = md5($CFG->wwwroot . $eventdata->userto->username);
69
 
70
        // Airnotifier can handle custom requests using processors (that are Airnotifier plugins).
71
        // In the extra parameter we indicate which processor to use and also additional data to be handled by the processor.
72
        // Here we clone the eventdata object because will be deleting/adding attributes.
73
        $extra = clone $eventdata;
74
 
75
        // Delete attributes that may content private information.
76
        if (!empty($eventdata->userfrom)) {
77
            $extra->userfromid = $eventdata->userfrom->id;
78
            $extra->userfromfullname = fullname($eventdata->userfrom);
79
            unset($extra->userfrom);
80
        }
81
        $extra->usertoid = $eventdata->userto->id;
82
        unset($extra->userto);
83
 
84
        $extra->processor       = 'moodle';
85
        $extra->site            = $siteid;
86
        $extra->date            = (!empty($eventdata->timecreated)) ? $eventdata->timecreated : time();
87
        $extra->notification    = (!empty($eventdata->notification)) ? 1 : 0;
88
        $encryptnotifications = get_config('message_airnotifier', 'encryptnotifications') == 1;
89
        $encryptprocessing = get_config('message_airnotifier', 'encryptprocessing');
90
 
91
        // Site name.
92
        $site = get_site();
93
        $extra->sitefullname = clean_param(format_string($site->fullname), PARAM_NOTAGS);
94
        $extra->siteshortname = clean_param(format_string($site->shortname), PARAM_NOTAGS);
95
 
96
        // Clean HTML and ony allow data not to be ignored by Airnotifier to reduce the payload size.
97
        if (empty($extra->smallmessage)) {
98
            $extra->smallmessage = $extra->fullmessage;
99
        }
100
        $extra->smallmessage = clean_param($extra->smallmessage, PARAM_NOTAGS);
101
        unset($extra->fullmessage);
102
        unset($extra->fullmessagehtml);
103
        unset($extra->fullmessageformat);
104
        unset($extra->fullmessagetrust);
105
 
106
        // Send wwwroot to airnotifier.
107
        $extra->wwwroot = $CFG->wwwroot;
108
 
109
        // We are sending to message to all devices.
110
        $airnotifiermanager = new message_airnotifier_manager();
111
        $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifiermobileappname, $eventdata->userto->id);
112
 
113
        foreach ($devicetokens as $devicetoken) {
114
            if (!$devicetoken->enable) {
115
                continue;
116
            }
117
 
118
            // Check if we should skip sending the notification.
119
            if ($encryptnotifications && empty($devicetoken->publickey) &&
120
                    $encryptprocessing == message_airnotifier_manager::ENCRYPT_UNSUPPORTED_NOT_SEND) {
121
 
122
                continue;   // Avoid sending notifications to devices not supporting encryption.
123
            }
124
 
125
            // Sending the message to the device.
126
            $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/api/v2/push/';
127
            $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname,
128
                'X-AN-APP-KEY: ' . $CFG->airnotifieraccesskey);
129
            $curl = new curl;
130
            // Push notifications are supposed to be instant, do not wait to long blocking the execution.
131
            $curl->setopt(array('CURLOPT_TIMEOUT' => 2, 'CURLOPT_CONNECTTIMEOUT' => 2));
132
            $curl->setHeader($header);
133
 
134
            // Clone the data to avoid modifying the original.
135
            $deviceextra = clone $extra;
136
 
137
            $deviceextra->encrypted = $encryptnotifications;
138
            $deviceextra = $this->encrypt_payload($deviceextra, $devicetoken);
139
 
140
            // We use Firebase to deliver all Push Notifications, and for all device types.
141
            // Firebase has a 4KB payload limit.
142
            // https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages
143
            // If the message is over that limit we remove unneeded fields and replace the title with a simple message.
144
            if (\core_text::strlen(json_encode($deviceextra), '8bit') > 4000) {
145
                $deviceextra->smallmessage = get_string('view_notification', 'message_airnotifier');
146
            }
147
 
148
            $params = array(
149
                'device'    => $devicetoken->platform,
150
                'token'     => $devicetoken->pushid,
151
                'extra'     => $deviceextra
152
            );
153
            if ($deviceextra->encrypted) {
154
                // Setting alert to null makes air notifier send the notification as a data payload,
155
                // this forces Android phones to call the app onMessageReceived function to decrypt the notification.
156
                // Otherwise notifications are created by the Android system and will not be decrypted.
157
                $params['alert'] = null;
158
            }
159
 
160
            // JSON POST raw body request.
161
            $resp = $curl->post($serverurl, json_encode($params));
162
        }
163
 
164
        return true;
165
    }
166
 
167
    /**
168
     * Encrypt the notification payload.
169
     *
170
     * @param stdClass $payload The notification payload.
171
     * @param stdClass $devicetoken The device token record
172
     * @return stdClass
173
     */
174
    protected function encrypt_payload(stdClass $payload, stdClass $devicetoken): stdClass {
175
        if (empty($payload->encrypted)) {
176
            return $payload;
177
        }
178
 
179
        if (empty($devicetoken->publickey)) {
180
            $payload->encrypted = false;
181
            return $payload;
182
        }
183
 
184
        $publickey = sodium_base642bin($devicetoken->publickey, SODIUM_BASE64_VARIANT_ORIGINAL);
185
        $fields = [
186
            'userfromfullname',
187
            'userfromid',
188
            'sitefullname',
189
            'smallmessage',
190
            'subject',
191
            'contexturl',
192
        ];
193
        foreach ($fields as $field) {
194
            if (!isset($payload->$field)) {
195
                continue;
196
            }
197
            $payload->$field = sodium_bin2base64(sodium_crypto_box_seal(
198
                $payload->$field,
199
                $publickey
200
            ), SODIUM_BASE64_VARIANT_ORIGINAL);
201
        }
202
 
203
        // Remove extra fields which may contain personal data.
204
        // They cannot be encrypted otherwise we would go over the 4KB payload size limit.
205
        unset($payload->usertoid);
206
        unset($payload->replyto);
207
        unset($payload->replytoname);
208
        unset($payload->siteshortname);
209
        unset($payload->customdata);
210
        unset($payload->contexturlname);
211
        unset($payload->replytoname);
212
        unset($payload->attachment);
213
        unset($payload->attachname);
214
 
215
        return $payload;
216
    }
217
 
218
    /**
219
     * Creates necessary fields in the messaging config form.
220
     *
221
     * @param array $preferences An array of user preferences
222
     */
223
    public function config_form($preferences) {
224
        global $CFG, $OUTPUT, $USER, $PAGE;
225
 
226
        $systemcontext = context_system::instance();
227
        if (!has_capability('message/airnotifier:managedevice', $systemcontext)) {
228
            return get_string('nopermissiontomanagedevices', 'message_airnotifier');
229
        }
230
 
231
        if (!$this->is_system_configured()) {
232
            return get_string('notconfigured', 'message_airnotifier');
233
        } else {
234
 
235
            $airnotifiermanager = new message_airnotifier_manager();
236
            $devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifiermobileappname, $USER->id);
237
 
238
            if (!empty($devicetokens)) {
239
                $output = '';
240
 
241
                foreach ($devicetokens as $devicetoken) {
242
 
243
                    if ($devicetoken->enable) {
244
                        $hideshowiconname = 't/hide';
245
                        $dimmed = '';
246
                    } else {
247
                        $hideshowiconname = 't/show';
248
                        $dimmed = 'dimmed_text';
249
                    }
250
 
251
                    $hideshowicon = $OUTPUT->pix_icon($hideshowiconname, get_string('showhide', 'message_airnotifier'));
252
                    $name = "{$devicetoken->name} {$devicetoken->model} {$devicetoken->platform} {$devicetoken->version}";
253
 
254
                    $output .= html_writer::start_tag('li', array('id' => $devicetoken->id,
255
                                                                    'class' => 'airnotifierdevice ' . $dimmed)) . "\n";
256
                    $output .= html_writer::label($name, 'deviceid-' . $devicetoken->id, array('class' => 'devicelabel ')) . ' ' .
257
                        html_writer::link('#', $hideshowicon, array('class' => 'hidedevice', 'alt' => 'show/hide')) . "\n";
258
                    $output .= html_writer::end_tag('li') . "\n";
259
                }
260
 
261
                // Include the AJAX script to automatically trigger the action.
262
                $airnotifiermanager->include_device_ajax();
263
 
264
                $output = html_writer::tag('ul', $output, array('class' => 'list-unstyled unstyled',
265
                    'id' => 'airnotifierdevices'));
266
            } else {
267
                $output = get_string('nodevices', 'message_airnotifier');
268
            }
269
            return $output;
270
        }
271
    }
272
 
273
    /**
274
     * Parses the submitted form data and saves it into preferences array.
275
     *
276
     * @param stdClass $form preferences form class
277
     * @param array $preferences preferences array
278
     */
279
    public function process_form($form, &$preferences) {
280
        return true;
281
    }
282
 
283
    /**
284
     * Loads the config data from database to put on the form during initial form display
285
     *
286
     * @param array $preferences preferences array
287
     * @param int $userid the user id
288
     */
289
    public function load_data(&$preferences, $userid) {
290
        return true;
291
    }
292
 
293
    /**
294
     * Tests whether the airnotifier settings have been configured
295
     * @return boolean true if airnotifier is configured
296
     */
297
    public function is_system_configured() {
298
        $airnotifiermanager = new message_airnotifier_manager();
299
        return $airnotifiermanager->is_system_configured();
300
    }
301
}