PushToken = ClassRegistry::init('PushToken'); } public function sendEmail($data) { $default = array( 'host' => Configure::read('expensecount.email.smtp.host'), 'port' => 587, 'auth' => 'plain', 'username' => Configure::read('expensecount.email.smtp.username'), 'password' => Configure::read('expensecount.email.smtp.password'), 'tsl' => false, 'transport' => 'Smtp', 'from' => array(Configure::read('expensecount.email.smtp.from') => 'ExpenseCount'), 'returnPath' => Configure::read('expensecount.email.smtp.from'), 'layout' => false, 'emailFormat' => 'html', 'charset' => 'utf-8', 'headerCharset' => 'utf-8', ); try { $Email = new CakeEmail($default); $Email->to($data["email_to"]); $Email->emailFormat('html'); $Email->template($data["template"], null)->viewVars(array('data' => $data)); $Email->subject($data["subject"]); $Email->replyTo($data["email_from"]); $Email->from(array(Configure::read('expensecount.email.smtp.from') => "ExpenseCount.com")); $Email->delivery = 'Smtp'; return $Email->send(); }catch (Exception $e) { CakeLog::write(LOG_ERR, 'Email sending failed: ' . $e->getMessage()); return $e->getMessage(); } } private function getAccessToken() { $cacheFile = TMP . 'fcm_token.cache'; if (file_exists($cacheFile)) { $cached = json_decode(file_get_contents($cacheFile), true); if (!empty($cached['token']) && $cached['expires_at'] > time()) { return $cached['token']; } } $scopes = ['https://www.googleapis.com/auth/firebase.messaging']; $credentials = new ServiceAccountCredentials( $scopes, json_decode(file_get_contents(FIREBASE_SECRET), true) ); $token = $credentials->fetchAuthToken(); if (!isset($token['access_token'])) { throw new \Exception('Unable to get access token'); } // Google tokens expire in 3600s; cache for 55 minutes to stay safely within that. file_put_contents($cacheFile, json_encode([ 'token' => $token['access_token'], 'expires_at' => time() + 3300, ])); return $token['access_token']; } public function sendPushNotification($tokens, $message, $async = false) { $projectId = FIREBASE_PROJECT_ID; if (empty($projectId)) { CakeLog::write(LOG_ERR, 'Push notification: Firebase project_id not configured'); return ['error' => 'Firebase project_id not configured']; } $url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"; try { $accessToken = $this->getAccessToken(); } catch (\Exception $e) { CakeLog::write(LOG_ERR, 'Push notification: Failed to get access token: ' . $e->getMessage()); return ['error' => 'Failed to get access token: ' . $e->getMessage()]; } $responses = []; foreach ($tokens as $key => $token) { $payload = [ "message" => [ "token" => $token['token'], "data" => $message, "apns" => [ "payload" => [ "aps" => [ "alert" => [ "title" => $message['title'], "body" => $message['body'] ], "sound" => "default", "badge" => 1, "mutable-content" => 1, "content-available" => 1 ] ] ] ] ]; $apiResponse = $this->fireAndForget($url, $payload, $accessToken); $responses[] = ['request' => [$token['user_id'], $token['device_id']], 'response' => $apiResponse]; } return $responses; } public function send($users,$message){ $tokens = array(); if(!empty($users)){ $filter = array('status' => 1,'user_id' => $users); $db_token = $this->PushToken->getToken($filter); if(!empty($db_token)) { foreach ($db_token as $token) { $tokens[] = ['user_id'=>$token['PushToken']['user_id'], 'device_id'=>$token['PushToken']['device_id'], 'token'=> $token['PushToken']['push_token']]; } } if(!empty($tokens)) { $response = $this->sendPushNotification($tokens, $message, true); }else{ $response["status"] = "-99"; $response["status_desc"] = "Push Token Not Found!"; $response["input"] = $users; } }else{ $response["status"] = "-99"; $response["status_desc"] = "User Not Found!"; $response["input"] = $users; } return $response; } }