<?php // Mobile App Notifications - Credentials
/*
Plugin Name: Mobile App Notifications
Description: Send queued push notifications to devices (runs every minute)
Version: 1.00
*/

//
addCronJob('man_pushNotifications_cron', "Sending queued Push Notifications", '* * * * *'); // Run every minute

//
addAction('section_init', '_man_pluginSetup_upgradeInstallAndSchemas',  null, 2);

// Plugin Menu - Add link to allow users to "Run Manually", this can be useful for testing
pluginAction_addHandlerAndLink(t('Run Manually'), 'man_pushNotifications_cron', 'admins');

// this function is called by cron, and logged under: Admin > General > Background Tasks
// return()'d content is logged as "Summary" and and print/echo content is logged as "output"
function man_pushNotifications_cron() {
  if (!inCLI()) {  // send headers (for running through web with "Run Manually" link above)
    if (!headers_sent()) {
      header("Content-type: text/plain");
      header("Content-Disposition: inline; filename='output.txt'"); // Force IE to display as text and not download file
      ob_disable();              // Turn off browser buffering
    }
    // This lines are only needed if your cron script is going to be called directly and you need to enforce security, if cron is added as a CMS "Background Task" then you can specify access rights in: pluginAction_addHandlerAndLink
    // $CMS_USER = getCurrentUserFromCMS();                                                                       // security check for web access - don't show cron filepaths unless logged in
    // if (!@$CMS_USER['isAdmin']) { die(t("You must be logged in as Admin to run this script from the web!")); } // security check for web access - don't show cron filepaths unless logged in
    ignore_user_abort( true ); // continue running even if user clicks stop on their browser
    session_write_close();     // v2.51 - End the current session and store session data so locked session data doesn't prevent concurrent access to CMS by user while backup in progress
  }
  set_time_limit(0);  // ignore PHP's max_execution_time directive

  // decode htmlencoded values (mysql_* function errors are htmlencoded by default);
  ob_start(function($output) { return html_entity_decode($output, ENT_QUOTES); });
  
  //
  print "Push Notification Queue Sender Cron \n";
  print "--------------------------------------------------------------------------------\n";

  // load credentials
  $credentialsFile = "mobileAppNotifications_credentials.php";
  $credentialsPath = __DIR__ . "/" . $credentialsFile;
  if (!file_exists($credentialsPath)) { die("You must create file '$credentialsFile' before you can send notifications!"); }
  require $credentialsPath; 
  
  //
  mab_sendNotifications();
  print "Done!\n";

  // return summary message
  $summary = "Successful";
  if (@$_REQUEST['_pluginAction'] == __FUNCTION__) { exit; }  // exit if being run manually from web
  return $summary;  // otherwise, return summary if being run by cron
}



// mab_sendNotifications - Send queued notifications
function mab_sendNotifications(){
  if (!mysql_get_lock(__FUNCTION__)) { dieAsCaller(__FUNCTION__ . ": Aborting, function already running..."); }

  // load unsent notifications
  $unsentNotifications = mysql_select('man_push_notifications', "`sent` = '0' LIMIT 10"); // Don't send more than X notifications at a time (NOTE: One notification can go to many devices)

  // for each unsent notification
  foreach ($unsentNotifications as $notification) {
    if ($notification['sent']) { dieAsCaller("Notification already sent!"); }

    // error checking
    // $credentials = getPushCredentialsFor($notification['app_id']);
    // if (empty($credentials['ANDROID_GOOGLE_API_KEY'])) {
    //   $error  = "Error: No ANDROID_GOOGLE_API_KEY specified in mobileAppNotifications_credentials.php file!\n";
    //   @trigger_error($error, E_USER_WARNING);  // log error
    //   die($error);
    // }

    // // Check "sent" - do this first so sending errors don't cause notifications to be resent every minute
    // if (!empty($_REQUEST['debug'])) { // use this url for resending push requests: ?_pluginAction=man_pushNotifications_cron&debug=1
    //   print "DEBUG: SENT flags are NOT being set.  Messages will be resent over and over until this disabled!\n\n";
    // }
    // else {
    //   mysql_update('man_push_notifications', $notification['num'], null, array('sent' => '1'));
    // }
    if($notification['notification_type'] == 'check_ins' && !checkInPastHalfHour($notification['notify_data'])) { continue; }

    mysql_update('man_push_notifications', $notification['num'], null, array('sent' => '1'));
    //
    $deviceTokens = []; // Array of arrays in format: [$platform, $tokenOrId, $badgeNumber], ...

    // Load target devices
    $devicesWhere = " `device_token` != '' AND `platform` != '' "; // must have a push token and defined platform (Android or iOS)
    if     ($notification['to'] == 'all')           { $devicesWhere .= ""; }
    elseif ($notification['to'] == 'logged-in')     { $devicesWhere .= " AND `user_num` != '' "; }
    elseif ($notification['to'] == 'not-logged-in') { $devicesWhere .= " AND `user_num` = '' "; }
    elseif ($notification['to'] == 'userNum')       { $devicesWhere .= mysql_escapef(" AND `user_num` = ?", $notification['to_userNum']); }
    else { dieAsCaller("Unknown 'to' value '" .htmlencode($notification['to']). "'"); }
    $targetDevices = mysql_select('man_mobile_devices', $devicesWhere);

    // Get push deviceToken/registrationID
    foreach ($targetDevices as $device) {
      if (!in_array($device['platform'], ['Android', 'iOS'])) { dieAsCaller("Unknown device platform '" .htmlencode($device['platform']). "'"); }

      // Geotargeting - Skip devices that are not in target area
      if ($notification['geotargeting']) {
        // Disable until further testing
        trigger_error("Geotargeting selected, but not enabled for this app!", E_USER_ERROR);
        $isDeviceInRange = _mab_isDeviceInRange($deviceRecord, $notification['geotargeting_geotargetNum'], $notification['geotargeting_range']);
        if (!$isDeviceInRange) { continue; }
      }

      //
      $deviceTokens[] = [$device['platform'], $device['device_token'], $device['badge']]; // NOTE: badge isn't used here yet, we're reloading it in ios function
    }
    
    // get tokenToDevice
    $tokenToDevice = array_groupBy($targetDevices, 'device_token');
    
    // iOS
    $deviceTokens_iOS = array_where($deviceTokens, [0 => 'iOS']); // where platform is...
    $deviceTokens_iOS = array_column($deviceTokens_iOS, 1); // get token
    $deviceTokens_iOS = array_unique(array_filter($deviceTokens_iOS)); // remove blanks and duplicates
    sort($deviceTokens_iOS); // sort
    
    foreach(array_chunk(array_unique($deviceTokens_iOS), 1000) as $deviceTokens_batch) {
      print htmlencodef("\niOS: ? devices (user_num, nickname, token)\n", count($deviceTokens_batch));
      print "--------------------------------------------------------------------------------\n";
      foreach ($deviceTokens_batch as $token) {
        $device = $tokenToDevice[$token];
        print "{$device['user_num']}, {$device['nickname']}, $token\n";
      }
      print "\n";

      //
      mab_sendNotifications_iOS($deviceTokens_batch, $notification['message'], $notification['app_id'], $notification['notify_data']);
    }
    // end: iOS
    
    // Android
    $deviceTokens_Android = array_where($deviceTokens, [0 => 'Android']); // where platform is...
    $deviceTokens_Android = array_column($deviceTokens_Android, 1); // get token
    $deviceTokens_Android = array_unique(array_filter($deviceTokens_Android)); // remove blanks and duplicates
    sort($deviceTokens_Android); // sort
    
    foreach(array_chunk($deviceTokens_Android, 1000) as $deviceTokens_batch) {
      print htmlencodef("\nAndroid: ? devices (user_num, nickname, token)\n", count($deviceTokens_batch));
      print "--------------------------------------------------------------------------------\n";
      foreach ($deviceTokens_batch as $token) {
        $device = $tokenToDevice[$token];
        print "{$device['user_num']}, {$device['nickname']}, $token\n";
      }
      print "\n";

      //
      mab_sendNotifications_android($deviceTokens_batch, $notification['message'], $notification['app_id'], $notification['notify_data']);
    }
    // end: Android

  }

  mysql_release_lock(__FUNCTION__);
}

//
function mab_sendNotifications_android($registrationIds, $notification_text, $app_id, $notification_data) {
  $credentials = getPushCredentialsFor($app_id);

  //
  $headers = [
    "Content-Type: application/json",
    "Authorization: key=" . $credentials['ANDROID_GOOGLE_API_KEY'],
  ];
  $data = [
    'registration_ids' => $registrationIds,
    'data' => [
      'message'   => $notification_text,
      'title'     => $credentials['ANDROID_PUSH_TITLE'],
      'vibrate'   => 1,
      'sound'     => 1,
      'largeIcon' => 'large_icon',
      'smallIcon' => 'small_icon',
      'notification_data' => json_decode($notification_data, true),
    ],
  ];

  // debug
  //print "Connecting to https://android.googleapis.com/gcm/send\n";

  //
  $ch = curl_init();
  curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
  curl_setopt( $ch, CURLOPT_URL, "https://fcm.googleapis.com/fcm/send" );
  curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
  curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
  curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode($data) );
  $responseJSON = curl_exec($ch);
  $response     = json_decode($responseJSON, true);
  $getinfo = curl_getinfo($ch);
  curl_close($ch);
  //print "Debug Response: " .print_r($response, true). "\n"; 
  
  //
  if ($getinfo['http_code'] == 401) {
    $error  = "Error connecting to FCM. Received HTTP {$getinfo['http_code']} (Unauthorized) from {$getinfo['url']}\n\n";
    $error .= "DEVELOPERS: Go to: Google Developer Console > API Manager > Credentials\n"; 
    $error .= " - Link: https://console.developers.google.com/apis/credentials\n";
    $error .= " - Confirm API Key was copied correctly from Google, we're using the API Key: {$credentials['ANDROID_GOOGLE_API_KEY']}\n";
    $error .= " - Confirm API Key allows access from this server's external IP address.\n";

    @trigger_error($error, E_USER_WARNING);  // log error
    die($error);
  }
  
  // debug:
  //showme($headers);
  //showme($data);
  
  if ($response['success'] == 1) { echo 'FCM Messages successfully delivered.' . "\n\n"; }
  else                           { echo "FCM Error: " .$response . "\n"; }
}


//
function mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, $useSandboxPushServer = null){
  $credentials = getPushCredentialsFor($app_id);
  
  // Send to both production and sandbox push servers if requested (so development builds get pushes)
  if (is_null($useSandboxPushServer)) {
    mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, false);
    if ($credentials['IOS_APSN_DEVMODE']) {
      mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, true);
    }
    return;
  }

  // set push server
  if ($useSandboxPushServer) { $apns_url = "https://api.sandbox.push.apple.com/3/device/"; $forDevices = "for development apps"; }
  else                       { $apns_url = "https://api.push.apple.com/3/device/";         $forDevices = "for production apps"; }
  print "Sending to APSN Server: $apns_url ($forDevices)\n";
  
  // error checking - check certificate exists
  if     (!$credentials['IOS_APSN_CERT_FILEPATH'])              { trigger_error("No certificate specified for IOS_APSN_CERT_FILEPATH!", E_USER_ERROR); }
  elseif (!file_exists($credentials['IOS_APSN_CERT_FILEPATH'])) { trigger_error("Specified certificate for IOS_APSN_CERT_FILEPATH doesn't exist!\n{$credentials['IOS_APSN_CERT_FILEPATH']}\n", E_USER_ERROR); }
  

  $arr_length = count($registrationIds);
  for($i=0;$i<$arr_length;$i++){

    // add debug message for sandbox environment
    if ($useSandboxPushServer) { $notification_text .= "\n[Sent to beta/devopment apps]"; }
  
    // Encode the payload as JSON
    // $badgeValue = intval(@$tokensToBadgeValues[$registrationIds[$i]]) ?: 1;
    $payload = json_encode([
      'aps' => [
        'alert' => $notification_text,
        'sound' => 'default',
        'badge' => 1,
      ],
      'data' => json_decode($notification_data)
    ]);

    $apnsTopic = $credentials['IOS_APP_BUNDLE_NAME'];
    $apns_topic = array("apns-topic: $apnsTopic");

    $ch = curl_init($apns_url.$registrationIds[$i]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $apns_topic);
    curl_setopt($ch, CURLOPT_SSLCERT, $credentials['IOS_APSN_CERT_FILEPATH']);
    curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $credentials['IOS_APSN_CERT_PASSPHRASE']);


    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $errors = [];
    if (!$response) { $errors[] = 'APNs Message not delivered' . "\n" . PHP_EOL; }
    else          { $errors[] = 'APNs Message successfully delivered' . "\n" . PHP_EOL; }
  }

  //
  if (in_array('APNs Message not delivered',$errors)){
    echo 'Some APNs Messages not delivered.' . "\n" . PHP_EOL;
  } else {
    echo 'APNs Messages successfully delivered.' . "\n" . PHP_EOL;
  }

}

/*function mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, $useSandboxPushServer = null){
  $credentials = getPushCredentialsFor($app_id);
  
  // Send to both production and sandbox push servers if requested (so development builds get pushes)
  if (is_null($useSandboxPushServer)) {
    mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, false);
    if ($credentials['IOS_APSN_DEVMODE']) {
      mab_sendNotifications_iOS($registrationIds, $notification_text, $app_id, $notification_data, true);
    }
    return;
  }

  // set push server
  if ($useSandboxPushServer) { $apns_url = "ssl://gateway.sandbox.push.apple.com:2195"; $forDevices = "for development apps"; }
  else                       { $apns_url = "ssl://gateway.push.apple.com:2195";         $forDevices = "for production apps"; }
  print "Sending to APSN Server: $apns_url ($forDevices)\n";
  
  // get device token to unread counts
  $registrationIdsAsCSV = mysql_escapeCSV($registrationIds);
  $devices              = mysql_select('man_mobile_devices', " `platform` != '' AND `device_token` IN ($registrationIdsAsCSV)");
  // $tokensToBadgeValues  = array_column($devices, 'badge', 'device_token');


  // error checking - check certificate exists
  if     (!$credentials['IOS_APSN_CERT_FILEPATH'])              { trigger_error("No certificate specified for IOS_APSN_CERT_FILEPATH!", E_USER_ERROR); }
  elseif (!file_exists($credentials['IOS_APSN_CERT_FILEPATH'])) { trigger_error("Specified certificate for IOS_APSN_CERT_FILEPATH doesn't exist!\n{$credentials['IOS_APSN_CERT_FILEPATH']}\n", E_USER_ERROR); }
  
  //
  $ctx = stream_context_create();
  stream_context_set_option($ctx, 'ssl', 'local_cert', $credentials['IOS_APSN_CERT_FILEPATH']);
  stream_context_set_option($ctx, 'ssl', 'passphrase', $credentials['IOS_APSN_CERT_PASSPHRASE']);

  // Open a connection to the APNS server

  // debug:
  //print "Connecting to $apns_url\n";
  $fp = stream_socket_client(
      $apns_url, $err,
      $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

  if (!$fp) {
      exit("Failed to connect: $err $errstr" . PHP_EOL);
  }

  //echo 'Connected to APNS' . PHP_EOL;

  //
  $arr_length = count($registrationIds);
  for($i=0;$i<$arr_length;$i++){

    // add debug message for sandbox environment
    if ($useSandboxPushServer) { $notification_text .= "\n[Sent to beta/devopment apps]"; }
  
    // Encode the payload as JSON
    // $badgeValue = intval(@$tokensToBadgeValues[$registrationIds[$i]]) ?: 1;
    $payload = json_encode([
      'aps' => [
        'alert' => $notification_text,
        'sound' => 'default',
        'badge' => 1,
      ],
      'data' => json_decode($notification_data)
    ]);

    // debug
    // print "Setting '{$registrationIds[$i]}' badge to '$badgeValue'\n";
    // print "Payload: $payload\n";
    // print_r((string)$registrationIds[$i]);
    // print_r($registrationIds[$i]);
    // print_r($notification_text);

    // Build the binary notification
    $msg = chr(0) . pack('n', 32) . pack('H*', $registrationIds[$i]) . pack('n', strlen($payload)) . $payload;

    // Send it to the server
    $result = fwrite($fp, $msg, strlen($msg));

    $errors = [];
    if (!$result) { $errors[] = 'APNs Message not delivered' . "\n" . PHP_EOL; }
    else          { $errors[] = 'APNs Message successfully delivered' . "\n" . PHP_EOL; }
  }

  //
  if (in_array('APNs Message not delivered',$errors)){
    echo 'Some APNs Messages not delivered.' . "\n" . PHP_EOL;
  } else {
    echo 'APNs Messages successfully delivered.' . "\n" . PHP_EOL;
  }
  // Close the connection to the server
  // Workaround to check if there were any errors during the last seconds of sending.
  // usleep(500000); //Pause for half a second. Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved

  fclose($fp);

}*/


// return true/false if user is in range.  Skips users without lng/lat set
function _mab_isDeviceInRange($deviceRecord, $geoTargetNum, $maxDistance, $kmOrMiles = 'miles') {

  // skip if missing device coordinates
  if (!$deviceRecord['latitude'] || !$deviceRecord['longitude']) { return false; }

  // load/cache geotarget records
  static $geotargetRecordsByNum;
  if (!$geotargetRecordsByNum) {
    $geotargetRecords      = mysql_select('man_push_geotargets');
    $geotargetRecordsByNum = array_groupBy($geotargetRecords, 'num');
  }

  // skip if device not in target area
  $target   = $geotargetRecordsByNum[$geoTargetNum];
  $distance = _mab_getDistanceBetweenCoordinates($deviceRecord['latitude'], $deviceRecord['longitude'], $target['latitude'], $target['longitude'], $kmOrMiles);
  $inRange  = ($distance <= $maxDistance);

  // debug message
  $deviceDebug   = "Device #{$deviceRecord['num']} ({$deviceRecord['latitude']},{$deviceRecord['longitude']})";
  $targetDebug = "target {$target['latitude']},{$target['longitude']}";
  $inRangeText = $inRange ? "Yes" : "No";
//  print "DEBUG: isUserInRange: $deviceDebug is $distance $kmOrMiles from $targetDebug - inRange: $inRangeText - ({$deviceRecord['mobile_device_type']}, {$deviceRecord['device_token']})\n";

  //
  return $inRange;
}


// Get distance between two geographic coordinates
// Reference: http://stackoverflow.com/a/10054282
function _mab_getDistanceBetweenCoordinates($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $kmOrMiles = 'miles') {
  if     ($kmOrMiles != 'miles') { $earthRadius = 3959; }
  elseif ($kmOrMiles != 'km')    { $earthRadius = 6371; }
  else                           { dieAsCaller("kmOrMiles must be 'km' or 'miles'!"); }

  // convert from degrees to radians
  $latFrom = deg2rad($latitudeFrom);
  $lonFrom = deg2rad($longitudeFrom);
  $latTo   = deg2rad($latitudeTo);
  $lonTo   = deg2rad($longitudeTo);

  //
  $lonDelta = $lonTo - $lonFrom;
  $a = pow(cos($latTo) * sin($lonDelta), 2) +
       pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
  $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);

  $angle    = atan2(sqrt($a), $b);
  $distance = $angle * $earthRadius;
  return $distance;
}

//
function _man_pluginSetup_upgradeInstallAndSchemas() {
  // on install/upgrade - create schemas - specify these in the order they should appear in menu
  $schemas = array('man_mobile_devices','man_push_notifications','man_push_geotargets'); 
  plugin_createSchemas($schemas);
}


// list($errorsAndAlerts, $pushNotification) = man_addPushNotification($message, $user);
function man_addPushNotification($message, $user) {
  $date             = mysql_datetime();
  $pushNotification = [];
  $errorsAndAlerts  = "";
  
  if(!isset($user['num'])) { $errorsAndAlerts .= "No user passed with notification.\n"; }
  if(!$errorsAndAlerts) {
    $pushNotification = [
      'createdDate'               => $date,
      'createdByUserNum'          => $user['num'],
      'updatedDate'               => $date,
      'updatedByUserNum'          => $user['num'],
      'app_id'                    => 'urbanhalal.com',
      'message'                   => $message,
      'to_userNum'                => $user['num'],
      'sent'                      => '0',
      'geotargeting'              => '0',
      'geotargeting_geotargetNum' => '',
      'geotargeting_range'        => '',
      'debug_response'            => '',
    ];
    $pushNotification['num'] = mysql_insert('man_push_notifications', $pushNotification, true);
  }
  return [ $errorsAndAlerts, $pushNotification ];

}

//Abhishek: function check if user's checkin time past half hour
function checkInPastHalfHour($data) {
  $data = json_decode($data);

  //$listing = mysql_get('listings', $data->listingsNum);

  //if(!$listing) { return false; }

  //date_default_timezone_set($listing['timezone']);
  
  $currentTime   = time();
  $checkInTime   = strtotime($data->last_check_in);
  $timediff      = $currentTime - $checkInTime;


  //date_default_timezone_set('UTC');


  if($timediff > 600)
  {
    return true;
  }
  return false;

}