You are here

firebase.module in Firebase Push Notification (FCM) 7

Same filename and directory in other branches
  1. 8 firebase.module
  2. 3.0.x firebase.module

File

firebase.module
View source
<?php

/**
 * Implements hook_help().
 */
function firebase_help($path, $arg) {
  switch ($path) {
    case 'admin/help#firebase':
      return '<p>' . t('Enables sending push notifications.') . '</p>';
  }
}

/**
 * Implements hook_menu().
 */
function firebase_menu() {
  $items['admin/config/system/firebase'] = array(
    'title' => 'Firebase Push Notification Configuration',
    'description' => 'Configure Firebase Notification',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'firebase_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Form callback: Firebase settings.
 *
 * @see firebase_menu()
 */
function firebase_form($form, &$form_state) {
  $form = array();
  $form['firebase_server_key'] = array(
    '#type' => 'textarea',
    '#title' => t('Firebase Server Key'),
    '#description' => t('This is the server key. <em>Do not confuse with API Key</em>'),
    '#default_value' => variable_get('firebase_server_key', ''),
    '#required' => TRUE,
  );
  $form['firebase_endpoint'] = array(
    '#type' => 'textfield',
    '#title' => t('Firebase endpoint'),
    '#description' => t('Google Firebase Cloud Messaging endpoint.'),
    '#default_value' => variable_get('firebase_endpoint', 'https://fcm.googleapis.com/fcm/send'),
    '#required' => TRUE,
  );
  return system_settings_form($form);
}

/**
 * Sends the push notification.
 *
 * @param string $token
 *   Firebase token that identify each device.
 * @param array $param
 *   Parameters for payload. Expected values are:
 *   - $param['title']
 *     Title of push message
 *   - $param['body']
 *     Body of push message
 *   Optional values are:
 *   - $param['icon']
 *     Icon to be displayed. If none is given, the App's icon will be used.
 *   - $param['sound']
 *     Sound to play. If none is given, the App's default will be used.
 *   - $param['priority']
 *     Set message priority.
 *   - $param['click_action']
 *     The action associated with a user click on the notification.
 *   - $param['content_available']
 *     If sending silent pushes for iOS, this must be equal to TRUE.
 *   - $param['data']
 *     Send extra information to device. Not displayed to users.
 *   - $param['badge']
 *     Badge number on App icon.
 *
 * @return bool
 *   TRUE if the push was sent successfully, and FALSE if not.
 */
function firebase_send($token, $param) {

  // We absolutely need the token. If it was not provided, return early.
  if (empty($token)) {
    return FALSE;
  }
  if (!($response = _firebase_sendPushNotification($token, $param))) {

    // Error connecting to Firebase API. For instance, timeout.
    return FALSE;
  }
  if ($response['body']->success === 1 && $response['body']->failure === 0) {
    return TRUE;
  }

  // Something went wrong. We didn't sent the push notification.
  // Common errors:
  // - Authentication Error
  //   The Server Key is invalid.
  // - Invalid Registration Token
  //   The token (generated by app) is not recognized by Firebase.
  // @see https://firebase.google.com/docs/cloud-messaging/http-server-ref#error-codes
  $error_message = reset($response['body']->results)->error;
  watchdog('firebase', 'Message failure: !error', array(
    '!error' => $error_message,
  ));
  return FALSE;
}

/**
 * Execute the push notification.
 *
 * @param string $appToken
 *   Device token.
 * @param array $param
 *   Parameters for payload.
 *
 * @return object
 *   Firebase's response.
 */
function _firebase_sendPushNotification($appToken, array $param) {
  $headers = _firebase_buildHeaders();

  // We receive our prepared payload from buildMessage.
  // Contains the token and notification array.
  $message = _firebase_buildMessage($appToken, $param);
  $ch = curl_init(variable_get('firebase_endpoint', 'https://fcm.googleapis.com/fcm/send'));

  // Setup curl with our headers and message.
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

  // Send the request to Firebase.
  $response['body'] = curl_exec($ch);
  $response['body'] = json_decode($response['body']);
  curl_close($ch);
  return $response;
}

/**
 * Builds the push notification header.
 */
function _firebase_buildHeaders() {
  return array(
    'Content-Type: application/json',
    'Authorization: key=' . variable_get('firebase_server_key', ''),
  );
}

/**
 * Builds the push notification body.
 *
 * @param string $appToken
 *   Device token.
 * @param array $param
 *   Parameters for payload.
 *
 * @return array
 *   Prepared payload for push notification.
 */
function _firebase_buildMessage($appToken, array $param) {

  // Parameters will be okay if we have at least the title and body.
  // If we do NOT have minimum fields, we assume it is a silent push.
  // Silent pushes need parameter data. So we check for $param['data'].
  // If these conditions are not met, we set a default value, just to go
  // through the push notification.
  if (!_firebase_isParamValid($param)) {
    return FALSE;
  }
  $mandatory = _firebase_addMandatoryFields($appToken);
  $optional = _firebase_addOptionalFields($param);
  $message = $mandatory + $optional;
  return json_encode($message);
}

/**
 * Adds mandatory fields to payload.
 *
 * @param string $token
 *   Device token.
 *
 * @return array
 *   Mandatory payload.
 */
function _firebase_addMandatoryFields($token) {

  // This is the core notification body.
  $message['to'] = $token;
  $message['priority'] = 'high';
  return $message;
}

/**
 * Adds optional fields to payload.
 *
 * @param array $param
 *   Data for payload.
 *
 * @return array
 *   Optional payload.
 */
function _firebase_addOptionalFields($param) {
  $message = [];
  if (!empty($param['priority'])) {
    $message['priority'] = $param['priority'];
  }
  if (!empty($param['title']) && !empty($param['body'])) {
    $message['notification'] = [
      'title' => $param['title'],
      'body' => $param['body'],
    ];
  }

  // If an icon, sound or click_action are available,
  // add them to notification body.
  if (!empty($param['icon'])) {
    $message['notification']['icon'] = $param['icon'];
  }
  if (!empty($param['sound'])) {
    $message['notification']['sound'] = $param['sound'];
  }
  if (!empty($param['click_action'])) {
    $message['notification']['click_action'] = $param['click_action'];
  }
  if (!empty($param['content_available'])) {
    $message['content_available'] = $param['content_available'];
  }
  if (isset($param['badge'])) {
    $message['notification']['badge'] = $param['badge'];
  }

  // Data is not displayed to app users. It is usually used to send
  // some data to be processed by the app.
  if (!empty($param['data'])) {
    $message['data'] = $param['data'];
  }
  return $message;
}

/**
 * Validate mandatory data on received parameters.
 *
 * @param array $param
 *   Params that builds Push notification payload.
 *
 * @return bool
 *   TRUE|FALSE - if mandatory data is present.
 */
function _firebase_isParamValid(array $param) {

  // We either have the title and body OR
  // it's a silent push - require $param['data'].
  if (!empty($param['title']) && !empty($param['body'])) {
    return TRUE;
  }
  if (isset($param['data']) && firebase_checkReservedKeywords($param['data'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Validate reserved keywords on data.
 *
 * The key should not be a reserved word
 * ("from" or any word starting with "google" or "gcm").
 * Do not use any of the words defined here
 * https://firebase.google.com/docs/cloud-messaging/http-server-ref.
 *
 * Not checking ALL reserved keywords. Just eliminating the common ones.
 * Created this function to document this important restriction.
 *
 * @param array $data
 *   Params that builds Push notification payload.
 *
 * @return bool
 *   TRUE if keys are fine, and FALSE if not.
 */
function firebase_checkReservedKeywords($data) {
  foreach ($data as $key => $value) {
    if (preg_match('/(^from$)|(^gcm)|(^google)/', $key)) {
      return FALSE;
    }
  }
  return TRUE;
}

Functions

Namesort descending Description
firebase_checkReservedKeywords Validate reserved keywords on data.
firebase_form Form callback: Firebase settings.
firebase_help Implements hook_help().
firebase_menu Implements hook_menu().
firebase_send Sends the push notification.
_firebase_addMandatoryFields Adds mandatory fields to payload.
_firebase_addOptionalFields Adds optional fields to payload.
_firebase_buildHeaders Builds the push notification header.
_firebase_buildMessage Builds the push notification body.
_firebase_isParamValid Validate mandatory data on received parameters.
_firebase_sendPushNotification Execute the push notification.