Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]

   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  }