Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 300 |
| FirebaseCloudMessagingService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 13 |
2970.00 | |
0.00% |
0 / 300 |
| __construct | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
| __clone | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
| getInstance | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 5 |
|||
| saveFcmToken | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 15 |
|||
| addNewFcmToken | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 31 |
|||
| updateFcmToken | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 24 |
|||
| checkIfTokenExist | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 17 |
|||
| deleteFcmToken | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 15 |
|||
| getUserFcmTokens | |
0.00% |
0 / 1 |
42.00 | |
0.00% |
0 / 28 |
|||
| sendPushNotification | |
0.00% |
0 / 1 |
156.00 | |
0.00% |
0 / 84 |
|||
| getUserFcmTokensUsingEmailIds | |
0.00% |
0 / 1 |
56.00 | |
0.00% |
0 / 50 |
|||
| deleteInvalidFcmTokens | |
0.00% |
0 / 1 |
20.00 | |
0.00% |
0 / 8 |
|||
| generateAccessToken | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 19 |
|||
| <?php | |
| namespace com\linways\core\ams\professional\service\mobile; | |
| use com\linways\base\constant\UserType; | |
| use com\linways\core\ams\professional\constant\DevelopmentPlatform; | |
| use com\linways\core\ams\professional\exception\mobile\MobileException; | |
| use com\linways\core\ams\professional\request\GenerateFcmAccessTokenRequest; | |
| use com\linways\core\ams\professional\request\mobile\GetUserFcmTokensUsingEmailIdsRequest; | |
| use com\linways\core\ams\professional\request\mobile\PushNotificationRequest; | |
| use com\linways\core\ams\professional\request\mobile\SaveFcmTokenRequest; | |
| use com\linways\core\ams\professional\response\GenerateFcmAccessTokenResponse; | |
| use com\linways\core\ams\professional\response\GetUserFcmTokensUsingEmailIdsResponse; | |
| use com\linways\core\ams\professional\service\BaseService; | |
| use Google\Auth\CredentialsLoader; | |
| use Google\Auth\HttpHandler\HttpHandlerFactory; | |
| use GuzzleHttp\Client; | |
| use GuzzleHttp\HandlerStack; | |
| use stdClass; | |
| /** | |
| * User: sarathks | |
| * Date: 20/05/22 | |
| */ | |
| class FirebaseCloudMessagingService extends BaseService | |
| { | |
| private $mapper = []; | |
| private $commonApiUtil = null; | |
| private $logger = null; | |
| private $cache; | |
| /** | |
| * Presence of a static member variable | |
| * | |
| * @var null | |
| */ | |
| private static $_instance = null; | |
| /** | |
| * Locked down the constructor or Prevent any outside instantiation of this class | |
| * | |
| * FirebaseCloudMessagingService constructor. | |
| */ | |
| private function __construct() | |
| { | |
| } | |
| /** | |
| * Prevent any object or instance of that class to be cloned | |
| */ | |
| private function __clone() | |
| { | |
| } | |
| /** | |
| * Have a single globally accessible static method | |
| * | |
| * @return FirebaseCloudMessagingService|null | |
| */ | |
| public static function getInstance() | |
| { | |
| if (!is_object(self::$_instance)) | |
| self::$_instance = new self(); | |
| return self::$_instance; | |
| } | |
| /** | |
| * Save FCM Token | |
| * @param SaveFcmTokenRequest $request | |
| * @return String $newFcmToken | |
| * @throws MobileException | |
| */ | |
| public function saveFcmToken(SaveFcmTokenRequest $request) | |
| { | |
| try { | |
| $isExist = $this->checkIfTokenExist($request->newFcmToken,$request->userId); | |
| if($isExist) { | |
| return; | |
| } | |
| if (empty($request->currentFcmToken)) { | |
| return $this->addNewFcmToken($request); | |
| } else { | |
| return $this->updateFcmToken($request); | |
| } | |
| } catch (\Exception $e) { | |
| throw $e; | |
| } | |
| } | |
| /** | |
| * Add new FCM Token | |
| * @param SaveFcmTokenRequest $request | |
| * @return String $newFcmToken | |
| * @throws MobileException | |
| */ | |
| public function addNewFcmToken(SaveFcmTokenRequest $request) | |
| { | |
| if (empty($request->newFcmToken)) { | |
| throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid fcm token"); | |
| } | |
| if (empty($request->userId)) { | |
| throw new MobileException(MobileException::INVALID_USER_ID, "Invalid user id"); | |
| } | |
| if (empty($request->userType)) { | |
| throw new MobileException(MobileException::INVALID_USER_TYPE, "Invalid user type"); | |
| } | |
| $sql="DELETE FROM v4_firebase_cloud_messaging_tokens where fcm_token = '$request->newFcmToken'"; | |
| try { | |
| $this->executeQuery($sql); | |
| $sql = "INSERT | |
| INTO | |
| `v4_firebase_cloud_messaging_tokens` (`fcm_token`, | |
| `user_id`, | |
| `user_type`, | |
| `platform`, | |
| `is_valid`) | |
| VALUES ('$request->newFcmToken', | |
| '$request->userId', | |
| '$request->userType', | |
| '$request->platform', | |
| '$request->isValid')"; | |
| $this->executeQuery($sql); | |
| return $request->newFcmToken; | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Update FCM Token | |
| * @param SaveFcmTokenRequest $request | |
| * @return String $newFcmToken | |
| * @throws MobileException | |
| */ | |
| public function updateFcmToken(SaveFcmTokenRequest $request) | |
| { | |
| if (empty($request->currentFcmToken)) { | |
| throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid current fcm token"); | |
| } | |
| if (empty($request->newFcmToken)) { | |
| throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid new fcm token"); | |
| } | |
| $sql = "UPDATE | |
| `v4_firebase_cloud_messaging_tokens` | |
| SET | |
| fcm_token = '$request->newFcmToken', | |
| created_date = UTC_TIMESTAMP() | |
| WHERE | |
| fcm_token = '$request->currentFcmToken' | |
| AND user_id = '$request->userId' | |
| AND platform = '$request->platform' | |
| AND user_type = '$request->userType'"; | |
| try { | |
| $this->executeQuery($sql); | |
| return $request->newFcmToken; | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Check if same FCM Token exist for a user | |
| * @param $fcmToken | |
| * @return String $newFcmToken | |
| * @throws MobileException | |
| */ | |
| public function checkIfTokenExist($token, $userId) | |
| { | |
| if (empty($token)) { | |
| throw new MobileException(MobileException::INVALID_FCM_TOKEN, "Invalid fcm token"); | |
| } | |
| $sql = "SELECT | |
| fcm_token | |
| FROM | |
| `v4_firebase_cloud_messaging_tokens` | |
| WHERE | |
| fcm_token = '$token' and user_id = '$userId'"; | |
| try { | |
| $tokens = $this->executeQueryForList($sql); | |
| return count($tokens); | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Delete FCM Token | |
| * @param $request | |
| * @throws MobileException | |
| */ | |
| public function deleteFcmToken($request) | |
| { | |
| // $request->fcmTokens is an array of tokens to be deleted; | |
| if (empty($request->fcmTokens)) { | |
| return; | |
| } | |
| $sql = "DELETE | |
| FROM | |
| `v4_firebase_cloud_messaging_tokens` | |
| WHERE | |
| fcm_token IN ('" . implode("','", $request->fcmTokens) . "')"; | |
| try { | |
| $this->executeQuery($sql); | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| public function getUserFcmTokens($fcmTokenRequestObject, $platform = DevelopmentPlatform::MOBILE) { | |
| // fcmTokenRequestObject should contain the keys as usertype and values as array of ids of that type for eg:- | |
| // $userIdObject = new stdClass(); | |
| // $userIdObject->STAFF = ['200']; | |
| // $userIdObject->STUDENT = ['3588']; | |
| $whereCondArr = []; | |
| $whereCond = ""; | |
| foreach($fcmTokenRequestObject as $userType => $ids) { | |
| $whereCondArr []= " (user_id IN ('" . implode("','", $ids) . "') AND user_type = '$userType') "; | |
| } | |
| if(!empty($whereCondArr)) { | |
| $whereCond .= " AND (".implode(" OR ", $whereCondArr).")"; | |
| } | |
| if($platform){ | |
| $whereCond .= " AND platform = '$platform' "; | |
| } | |
| if (empty($whereCondArr)) { | |
| throw new MobileException(MobileException::INVALID_USER_ID, "Invalid user ids"); | |
| } | |
| $sql = "SELECT | |
| fcm_token | |
| FROM | |
| `v4_firebase_cloud_messaging_tokens` WHERE 1=1 | |
| $whereCond"; | |
| try { | |
| $tokens = (array)$this->executeQueryForList($sql); | |
| $result = array_values(array_unique(array_map(function($token) { | |
| return $token->fcm_token; | |
| }, $tokens))); | |
| return $result; | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| public function sendPushNotification(PushNotificationRequest $request) { | |
| $url = 'https://fcm.googleapis.com/v1/projects/linways-b8708/messages:send'; | |
| $accessTokenReq = new GenerateFcmAccessTokenRequest(); | |
| $accessTokenData = $this->generateAccessToken($accessTokenReq); | |
| $accessToken = $accessTokenData->accessToken; | |
| // PREPARING THE FCM API REQUEST PARAM | |
| $notificationObject = array( | |
| 'title' => $request->title ?? '', | |
| 'body' => $request->body ?? '' | |
| ); | |
| $messageObject = array( | |
| 'notification' => $notificationObject | |
| ); | |
| // PUSING DATA OBJECT IF PRESENT | |
| $dataObject = $request->data; | |
| if (!empty($dataObject)) { | |
| $messageObject['data'] = $dataObject; | |
| } | |
| // PUSHING WEBPUSH OBJECT IF PRESENT | |
| $webPushObject = array(); | |
| if (!empty($request->webFcmOptionLink)) { | |
| $webPushObject['fcm_options'] = array('link' => $request->webFcmOptionLink); | |
| } | |
| if (!empty($webPushObject)) { | |
| $messageObject['webpush'] = $webPushObject; | |
| } | |
| $fields = array( | |
| 'message' => $messageObject | |
| ); | |
| $headers = array( | |
| 'Content-Type:application/json', | |
| 'Authorization: Bearer ' . $accessToken | |
| ); | |
| $ch = curl_init(); | |
| curl_setopt($ch, CURLOPT_URL, $url); | |
| curl_setopt($ch, CURLOPT_POST, true); | |
| curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
| curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); | |
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
| // EARLIER OUR REQUEST CONTAINED registration_ids KEY | |
| $registationIds = $request->fcmTokens; | |
| // FCM HTTP V1 API ONLY SUPPORT INDIVIDUAL TOKENS | |
| if(!empty($registationIds) && count($registationIds) > 1000) { | |
| // FOR NOW WE ARE SENDING TOKENS ONE BY ONE. IN FUTURE THIS CAN BE CHANGED TOPIC BASED. SUBSCRIBE ALL THESE TOKENS TO TOPIC AND THEN SEND TO TOPIC | |
| while(count($registationIds)) { | |
| $splicedRegistarationIds = array_splice($registationIds,0,1000); | |
| $unRegisteredTokens = []; | |
| foreach($splicedRegistarationIds as $token) { | |
| $fields['message']['token'] = $token; | |
| curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
| $result = curl_exec($ch); | |
| $result = json_decode($result); | |
| // ACCUMULATING UNREGISTERED TOKENS | |
| $errorDetails = $result->error->details; | |
| $errorCode = $errorDetails[0]->errorCode; | |
| if ($errorCode == 'UNREGISTERED') { | |
| $unRegisteredTokens[] = $token; | |
| } | |
| } | |
| $deleteRequest = new \stdClass(); | |
| $deleteRequest->fcmTokens = $unRegisteredTokens; | |
| $this->deleteFcmToken($deleteRequest); | |
| $unRegisteredTokens = []; | |
| sleep(2); // SLEEP FOR 2 SECONDS TO AVOID RATE LIMITING | |
| } | |
| return; | |
| } | |
| else if(!empty($registationIds)) { | |
| $unRegisteredTokens = []; | |
| foreach($registationIds as $token) { | |
| $fields['message']['token'] = $token; | |
| curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
| $result = curl_exec($ch); | |
| $result = json_decode($result); | |
| $errorDetails = $result->error->details; | |
| $errorCode = $errorDetails[0]->errorCode; | |
| if ($errorCode == 'UNREGISTERED') { | |
| $unRegisteredTokens[] = $token; | |
| } | |
| } | |
| $deleteRequest = new \stdClass(); | |
| $deleteRequest->fcmTokens = $unRegisteredTokens; | |
| $this->deleteFcmToken($deleteRequest); | |
| $unRegisteredTokens = []; | |
| return; | |
| } | |
| // OTHER CASES OF NOT USING REGISTRATION IDS LIKE TOPIC AND ALL | |
| curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); | |
| $result = curl_exec($ch); | |
| $result = json_decode($result); | |
| $this->deleteInvalidFcmTokens($request->fields, $result); | |
| } | |
| /** | |
| * Get user fcm tokens using user email ids | |
| * | |
| * request->userTypeEmailMap should be a map as follows | |
| * $userIdObject = new stdClass(); | |
| * $userIdObject->STAFF = ['staff1@email.com','staff2@email.com']; | |
| * $userIdObject->STUDENT = ['student1@email.com','student2@email.com']; | |
| * | |
| * @param GetUserFcmTokensUsingEmailIdsRequest $request | |
| * @return GetUserFcmTokensUsingEmailIdsResponse $responseData | |
| * @throws MobileException | |
| **/ | |
| public function getUserFcmTokensUsingEmailIds(GetUserFcmTokensUsingEmailIdsRequest $request) | |
| { | |
| try { | |
| $fcmTokens = []; | |
| $userTypeEmailIdMap = $request->userTypeEmailMap; | |
| if (!empty($request->platform)) { | |
| $platformCond = " AND vfcmt.platform = '$request->platform' "; | |
| } | |
| foreach($userTypeEmailIdMap as $userType => $emailIds) { | |
| if (!empty($emailIds)) { | |
| if ($userType === UserType::STAFF) { | |
| $sql = "SELECT | |
| vfcmt.fcm_token | |
| FROM | |
| staffaccounts s | |
| INNER JOIN v4_firebase_cloud_messaging_tokens vfcmt ON | |
| vfcmt.user_id = s.staffID | |
| AND vfcmt.user_type = 'STAFF' | |
| WHERE | |
| s.staffEmail IN ('" . implode("','", $emailIds) . "') | |
| $platformCond "; | |
| $tokenList = $this->executeQueryForList($sql); | |
| $staffTokens = array_values(array_unique(array_map(function($token) { | |
| return $token->fcm_token; | |
| }, $tokenList))); | |
| $fcmTokens = array_merge($fcmTokens, $staffTokens); | |
| } | |
| else if ($userType === UserType::STUDENT) { | |
| $sql = "SELECT | |
| vfcmt.fcm_token | |
| FROM | |
| studentaccount s | |
| INNER JOIN v4_firebase_cloud_messaging_tokens vfcmt ON | |
| vfcmt.user_id = s.studentID | |
| AND vfcmt.user_type = 'STUDENT' | |
| WHERE | |
| s.studentEmail IN ('" . implode("','", $emailIds) . "') | |
| $platformCond "; | |
| $tokenList = $this->executeQueryForList($sql); | |
| $studentTokens = array_values(array_unique(array_map(function($token) { | |
| return $token->fcm_token; | |
| }, $tokenList))); | |
| $fcmTokens = array_merge($fcmTokens, $studentTokens); | |
| } | |
| } | |
| } | |
| $responseData = new GetUserFcmTokensUsingEmailIdsResponse(); | |
| $responseData->fcmTokens = $fcmTokens; | |
| return $responseData; | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Delete FCM Tokens that are invalid | |
| * @param $fcmResult | |
| * @throws MobileException | |
| */ | |
| protected function deleteInvalidFcmTokens($fcmRequestFields, $fcmResult) { | |
| if(!empty($fcmRequestFields['registration_ids']) && $fcmResult->failure > 0) { | |
| $failedTokens = array_filter($fcmRequestFields['registration_ids'], function($token, $key) use ($fcmResult) { | |
| return $fcmResult->results[$key]->error == 'NotRegistered' || $fcmResult->results[$key]->error == 'InvalidRegistration'; | |
| }, ARRAY_FILTER_USE_BOTH); | |
| $deleteRequest = new \stdClass(); | |
| $deleteRequest->fcmTokens = $failedTokens; | |
| $this->deleteFcmToken($deleteRequest); | |
| } | |
| } | |
| /** | |
| * Generate Access token for calling FCM api | |
| * | |
| * This method generates access token for calling fcm http v1 api | |
| * | |
| * @param GenerateFcmAccessTokenRequest $request | |
| * @return GenerateFcmAccessTokenResponse $response | |
| * @throws MobileException | |
| **/ | |
| public function generateAccessToken(GenerateFcmAccessTokenRequest $request) | |
| { | |
| try { | |
| $credentialsPath = $GLOBALS['GOOGLE_FIREBASE_CREDENTIALS_PATH']; | |
| $credentialJsonContent = file_get_contents($credentialsPath); | |
| $credentialJson = json_decode($credentialJsonContent, true); | |
| $credentials = CredentialsLoader::makeCredentials( | |
| ['https://www.googleapis.com/auth/firebase.messaging'], | |
| $credentialJson | |
| ); | |
| $httpHandler = HttpHandlerFactory::build(new Client(['handler' => HandlerStack::create()])); | |
| $credentials->fetchAuthToken($httpHandler); | |
| $accessToken = $credentials->getLastReceivedToken()['access_token']; | |
| $response = new GenerateFcmAccessTokenResponse(); | |
| $response->accessToken = $accessToken; | |
| return $response; | |
| } catch (\Exception $e) { | |
| throw new MobileException($e->getCode(), $e->getMessage()); | |
| } | |
| } | |
| } |