See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 manager class 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 defined('MOODLE_INTERNAL') || die; 28 29 /** 30 * Airnotifier helper manager class 31 * 32 * @copyright 2012 Jerome Mouneyrac <jerome@moodle.com> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class message_airnotifier_manager { 36 37 /** @var string The Airnotifier public instance URL */ 38 const AIRNOTIFIER_PUBLICURL = 'https://messages.moodle.net'; 39 40 /** @var int Avoid sending notifications to devices not supporting encryption */ 41 const ENCRYPT_UNSUPPORTED_NOT_SEND = 0; 42 43 /** @var int Send notifications to devices not supporting encryption */ 44 const ENCRYPT_UNSUPPORTED_SEND = 1; 45 46 /** 47 * Include the relevant javascript and language strings for the device 48 * toolbox YUI module 49 * 50 * @return bool 51 */ 52 public function include_device_ajax() { 53 global $PAGE, $CFG; 54 55 $config = new stdClass(); 56 $config->resturl = '/message/output/airnotifier/rest.php'; 57 $config->pageparams = array(); 58 59 // Include toolboxes. 60 $PAGE->requires->yui_module('moodle-message_airnotifier-toolboxes', 'M.message.init_device_toolbox', array(array( 61 'ajaxurl' => $config->resturl, 62 'config' => $config, 63 )) 64 ); 65 66 // Required strings for the javascript. 67 $PAGE->requires->strings_for_js(array('deletecheckdevicename'), 'message_airnotifier'); 68 $PAGE->requires->strings_for_js(array('show', 'hide'), 'moodle'); 69 70 return true; 71 } 72 73 /** 74 * Return the user devices for a specific app. 75 * 76 * @param string $appname the app name . 77 * @param int $userid if empty take the current user. 78 * @return array all the devices 79 */ 80 public function get_user_devices($appname, $userid = null) { 81 global $USER, $DB; 82 83 if (empty($userid)) { 84 $userid = $USER->id; 85 } 86 87 $devices = array(); 88 89 $params = array('appid' => $appname, 'userid' => $userid); 90 91 // First, we look all the devices registered for this user in the Moodle core. 92 // We are going to allow only ios devices (since these are the ones that supports PUSH notifications). 93 $userdevices = $DB->get_records('user_devices', $params); 94 foreach ($userdevices as $device) { 95 if (core_text::strtolower($device->platform)) { 96 // Check if the device is known by airnotifier. 97 if (!$airnotifierdev = $DB->get_record('message_airnotifier_devices', 98 array('userdeviceid' => $device->id))) { 99 100 // We have to create the device token in airnotifier. 101 if (! $this->create_token($device->pushid, $device->platform)) { 102 continue; 103 } 104 105 $airnotifierdev = new stdClass; 106 $airnotifierdev->userdeviceid = $device->id; 107 $airnotifierdev->enable = 1; 108 $airnotifierdev->id = $DB->insert_record('message_airnotifier_devices', $airnotifierdev); 109 } 110 $device->id = $airnotifierdev->id; 111 $device->enable = $airnotifierdev->enable; 112 $devices[] = $device; 113 } 114 } 115 116 return $devices; 117 } 118 119 /** 120 * Request and access key to Airnotifier 121 * 122 * @return mixed The access key or false in case of error 123 */ 124 public function request_accesskey() { 125 global $CFG, $USER; 126 127 require_once($CFG->libdir . '/filelib.php'); 128 129 // Sending the request access key request to Airnotifier. 130 $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/accesskeys/'; 131 // We use an APP Key "none", it can be anything. 132 $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname, 133 'X-AN-APP-KEY: none'); 134 $curl = new curl(); 135 $curl->setHeader($header); 136 137 // Site ids are stored as secrets in md5 in the Moodle public hub. 138 $params = array( 139 'url' => $CFG->wwwroot, 140 'siteid' => md5($CFG->siteidentifier), 141 'contact' => $USER->email, 142 'description' => $CFG->wwwroot 143 ); 144 $resp = $curl->post($serverurl, $params); 145 146 if ($key = json_decode($resp, true)) { 147 if (!empty($key['accesskey'])) { 148 return $key['accesskey']; 149 } 150 } 151 debugging("Unexpected response from the Airnotifier server: $resp"); 152 return false; 153 } 154 155 /** 156 * Create a device token in the Airnotifier instance 157 * @param string $token The token to be created 158 * @param string $deviceplatform The device platform (Android, iOS, iOS-fcm, etc...) 159 * @return bool True if all was right 160 */ 161 private function create_token($token, $deviceplatform = '') { 162 global $CFG; 163 164 if (!$this->is_system_configured()) { 165 return false; 166 } 167 168 require_once($CFG->libdir . '/filelib.php'); 169 170 $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/tokens/' . $token; 171 $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname, 172 'X-AN-APP-KEY: ' . $CFG->airnotifieraccesskey); 173 $curl = new curl; 174 $curl->setHeader($header); 175 $params = []; 176 if (!empty($deviceplatform)) { 177 $params["device"] = $deviceplatform; 178 } 179 $resp = $curl->post($serverurl, $params); 180 181 if ($token = json_decode($resp, true)) { 182 if (!empty($token['status'])) { 183 return $token['status'] == 'ok' || $token['status'] == 'token exists'; 184 } 185 } 186 debugging("Unexpected response from the Airnotifier server: $resp"); 187 return false; 188 } 189 190 /** 191 * Tests whether the airnotifier settings have been configured 192 * @return boolean true if airnotifier is configured 193 */ 194 public function is_system_configured() { 195 global $CFG; 196 197 return (!empty($CFG->airnotifierurl) && !empty($CFG->airnotifierport) && 198 !empty($CFG->airnotifieraccesskey) && !empty($CFG->airnotifierappname) && 199 !empty($CFG->airnotifiermobileappname)); 200 } 201 202 /** 203 * Enables or disables a registered user device so it can receive Push notifications 204 * 205 * @param int $deviceid the device id 206 * @param bool $enable true to enable it, false to disable it 207 * @return bool true if the device was enabled, false in case of error 208 * @since Moodle 3.2 209 */ 210 public static function enable_device($deviceid, $enable) { 211 global $DB, $USER; 212 213 if (!$device = $DB->get_record('message_airnotifier_devices', array('id' => $deviceid), '*')) { 214 return false; 215 } 216 217 // Check that the device belongs to the current user. 218 if (!$userdevice = $DB->get_record('user_devices', array('id' => $device->userdeviceid, 'userid' => $USER->id), '*')) { 219 return false; 220 } 221 222 $device->enable = $enable; 223 return $DB->update_record('message_airnotifier_devices', $device); 224 } 225 226 /** 227 * Check the system configuration to detect possible issues. 228 * 229 * @return array result checks 230 */ 231 public function check_configuration(): array { 232 global $CFG, $DB; 233 234 $results = []; 235 // Check Mobile services enabled. 236 $summary = html_writer::link((new moodle_url('/admin/settings.php', ['section' => 'mobilesettings'])), 237 get_string('enablemobilewebservice', 'admin')); 238 if (empty($CFG->enablewebservices) || empty($CFG->enablemobilewebservice)) { 239 $results[] = new core\check\result(core\check\result::CRITICAL, $summary, get_string('enablewsdescription', 'webservice')); 240 } else { 241 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('enabled', 'admin')); 242 } 243 244 // Check message outputs are not disabled in config.php. 245 $summary = get_string('noemailevernotset', 'message_airnotifier'); 246 if (!empty($CFG->noemailever)) { 247 $results[] = new core\check\result(core\check\result::CRITICAL, $summary, 248 get_string('noemaileverset', 'message_airnotifier')); 249 } else { 250 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('disabled', 'admin')); 251 } 252 253 // Check Mobile notifications enabled. 254 require_once($CFG->dirroot . '/message/lib.php'); 255 $processors = get_message_processors(); 256 $enabled = false; 257 foreach ($processors as $processor => $status) { 258 if ($processor == 'airnotifier' && $status->enabled) { 259 $enabled = true; 260 } 261 } 262 263 $summary = html_writer::link((new moodle_url('/admin/message.php')), get_string('enableprocessor', 'message_airnotifier')); 264 if ($enabled) { 265 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('enabled', 'admin')); 266 } else { 267 $results[] = new core\check\result(core\check\result::CRITICAL, $summary, 268 get_string('mobilenotificationsdisabledwarning', 'tool_mobile')); 269 } 270 271 // Check Mobile notifications configuration is ok. 272 $summary = html_writer::link((new moodle_url('/admin/settings.php', ['section' => 'messagesettingairnotifier'])), 273 get_string('notificationsserverconfiguration', 'message_airnotifier')); 274 if ($this->is_system_configured()) { 275 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('configured', 'message_airnotifier')); 276 } else { 277 $results[] = new core\check\result(core\check\result::ERROR, $summary, get_string('notconfigured', 'message_airnotifier')); 278 } 279 280 // Check settings properly formatted. Only display in case of errors. 281 $settingstocheck = ['airnotifierappname', 'airnotifiermobileappname']; 282 if ($this->is_system_configured()) { 283 foreach ($settingstocheck as $setting) { 284 if ($CFG->$setting != trim($CFG->$setting)) { 285 $summary = html_writer::link((new moodle_url('/admin/settings.php', ['section' => 'messagesettingairnotifier'])), 286 get_string('notificationsserverconfiguration', 'message_airnotifier')); 287 288 $results[] = new core\check\result(core\check\result::ERROR, $summary, 289 get_string('airnotifierfielderror', 'message_airnotifier', get_string($setting, 'message_airnotifier'))); 290 } 291 } 292 } 293 294 // Check connectivity with Airnotifier. 295 $url = $CFG->airnotifierurl . ':' . $CFG->airnotifierport; 296 $curl = new \curl(); 297 $curl->setopt(['CURLOPT_TIMEOUT' => 5, 'CURLOPT_CONNECTTIMEOUT' => 5]); 298 $curl->get($url); 299 $info = $curl->get_info(); 300 301 $summary = html_writer::link($url, get_string('airnotifierurl', 'message_airnotifier')); 302 if (!empty($info['http_code']) && ($info['http_code'] == 200 || $info['http_code'] == 302)) { 303 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('online', 'message')); 304 } else { 305 $details = get_string('serverconnectivityerror', 'message_airnotifier', $url); 306 $curlerrno = $curl->get_errno(); 307 if (!empty($curlerrno)) { 308 $details .= ' CURL ERROR: ' . $curlerrno . ' - ' . $curl->error; 309 } 310 $results[] = new core\check\result(core\check\result::ERROR, $summary, $details); 311 } 312 313 // Check access key by trying to create an invalid token. 314 $settingsurl = new moodle_url('/admin/settings.php', ['section' => 'messagesettingairnotifier']); 315 $summary = html_writer::link($settingsurl, get_string('airnotifieraccesskey', 'message_airnotifier')); 316 if (!empty($CFG->airnotifieraccesskey)) { 317 $url = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/tokens/testtoken'; 318 $header = ['Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname, 319 'X-AN-APP-KEY: ' . $CFG->airnotifieraccesskey]; 320 $curl->setHeader($header); 321 $response = $curl->post($url); 322 $info = $curl->get_info(); 323 324 if ($curlerrno = $curl->get_errno()) { 325 $details = get_string('serverconnectivityerror', 'message_airnotifier', $url); 326 $details .= ' CURL ERROR: ' . $curlerrno . ' - ' . $curl->error; 327 $results[] = new core\check\result(core\check\result::ERROR, $summary, $details); 328 } else if (!empty($info['http_code']) && $info['http_code'] == 400 && $key = json_decode($response, true)) { 329 if ($key['error'] == 'Invalid access key') { 330 $results[] = new core\check\result(core\check\result::ERROR, $summary, $key['error']); 331 } else { 332 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('enabled', 'admin')); 333 } 334 } 335 } else { 336 $results[] = new core\check\result(core\check\result::ERROR, $summary, 337 get_string('requestaccesskey', 'message_airnotifier')); 338 } 339 340 // Check default preferences. 341 $preferences = (array) get_message_output_default_preferences(); 342 $providerscount = 0; 343 $providersconfigured = 0; 344 foreach ($preferences as $prefname => $prefval) { 345 if (strpos($prefname, 'message_provider') === 0) { 346 $providerscount++; 347 if (strpos($prefval, 'airnotifier') !== false) { 348 $providersconfigured++; 349 } 350 } 351 } 352 353 $summary = html_writer::link((new moodle_url('/admin/message.php')), get_string('managemessageoutputs', 'message')); 354 if ($providersconfigured == 0) { 355 $results[] = new core\check\result(core\check\result::ERROR, $summary, 356 get_string('messageprovidersempty', 'message_airnotifier')); 357 } else if ($providersconfigured / $providerscount < 0.25) { 358 // Less than a 25% of the providers are enabled by default for users. 359 $results[] = new core\check\result(core\check\result::WARNING, $summary, 360 get_string('messageproviderslow', 'message_airnotifier')); 361 } else { 362 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('configured', 'message_airnotifier')); 363 } 364 365 // Check user devices from last month. 366 $recentdevicescount = $DB->count_records_select('user_devices', 'appid = ? AND timemodified > ?', 367 [$CFG->airnotifiermobileappname, time() - (WEEKSECS * 4)]); 368 369 $summary = get_string('userdevices', 'message_airnotifier'); 370 if (!empty($recentdevicescount)) { 371 $results[] = new core\check\result(core\check\result::OK, $summary, get_string('configured', 'message_airnotifier')); 372 } else { 373 $results[] = new core\check\result(core\check\result::ERROR, $summary, get_string('nodevices', 'message_airnotifier')); 374 } 375 return $results; 376 } 377 378 /** 379 * Send a test notification to the given user. 380 * 381 * @param stdClass $user user object 382 */ 383 public function send_test_notification(stdClass $user): void { 384 global $CFG; 385 require_once($CFG->dirroot . '/message/output/airnotifier/message_output_airnotifier.php'); 386 387 $data = new stdClass; 388 $data->userto = clone $user; 389 $data->subject = 'Push Notification Test'; 390 $data->fullmessage = 'This is a test message send at: ' . userdate(time()); 391 $data->notification = 1; 392 393 // The send_message method always return true, so it does not make sense to return anything. 394 $airnotifier = new message_output_airnotifier(); 395 $airnotifier->send_message($data); 396 } 397 398 /** 399 * Check whether the given user has enabled devices or not for the given app. 400 * 401 * @param string $appname the app to check 402 * @param int $userid the user to check the devices for (empty for current user) 403 * @return bool true when the user has enabled devices, false otherwise 404 */ 405 public function has_enabled_devices(string $appname, int $userid = null): bool { 406 $enableddevices = false; 407 $devices = $this->get_user_devices($appname, $userid); 408 409 foreach ($devices as $device) { 410 if (!$device->enable) { 411 continue; 412 } 413 $enableddevices = true; 414 break; 415 } 416 return $enableddevices; 417 } 418 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body