From 25bbfa1125d3aba4793103f2984344015fca7190 Mon Sep 17 00:00:00 2001 From: velz Date: Sat, 26 Oct 2024 17:51:09 +0530 Subject: [PATCH] FEAT_EXPLORED_GMAILAPI_ATTACHEMENT&MAIL_DELIVERY_STATUS --- app/Config/Routes.php | 3 +- app/Controllers/MasterController.php | 4 +- app/Helpers/MailHelper.php | 5 +- app/Libraries/GmailAPI.php | 158 +++++++++++++++++++++------ app/Views/employee_data_list.php | 2 + public/sample_excel/enrollment.xlsx | Bin 7646 -> 7762 bytes 6 files changed, 132 insertions(+), 40 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index ca0cbe4f..8c1521be 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -355,7 +355,8 @@ $routes->cli('cli/processjobs', 'JobWorker::processJobs'); $routes->get("processjob", "JobWorker::processJob"); $routes->cli('cli/new_gmail_token', 'MasterController::generateNewGmailAPIToken'); -$routes->cli('cli/send_mail_cli(/:any)?', 'MasterController::testGmailAPIViaCLI$1'); +$routes->cli('cli/send_mail_cli', 'MasterController::testGmailAPIViaCLI'); +$routes->cli('cli/check_bounce_mail_cli', 'MasterController::testCheckBounceMails'); $routes->cli('cli/app_check_list', 'MasterController::appCheckList'); $routes->cli('cli/new_gdrive_token', 'GoogleDriveController::generateNewGoogleDriveAccessToken'); diff --git a/app/Controllers/MasterController.php b/app/Controllers/MasterController.php index 691234eb..0a5bb5d1 100755 --- a/app/Controllers/MasterController.php +++ b/app/Controllers/MasterController.php @@ -34,6 +34,8 @@ use App\Models\InsurerExcelExportTemplateModel; use App\Models\SettingsModel; use App\Models\VehicleModel; +use CodeIgniter\CLI\CLI; + class MasterController extends AdminController { use ResponseTrait; @@ -1672,7 +1674,7 @@ class MasterController extends AdminController echo "Check the following..!\n"; echo "1. Update Gmail Oauth credentials in 'gmail_api_oauth_credentials' field in 'settings' table in JSON format.\n"; echo "2. Call this cli route to generate new access token: 'php public/index.php cli/new_gmail_token' from root of the project.\n"; - echo "3. Call this cli route : 'php public/index.php cli/send_mail_cli someone@gmail.com' to send test mail to testGmailAPIViaCLI ( replace someone@gmail.com with real time mail).\n"; + echo "3. Call this cli route : 'php public/index.php cli/send_mail_cli --to someone@gmail.com' to send test mail to testGmailAPIViaCLI ( replace someone@gmail.com with real time mail).\n"; } else { diff --git a/app/Helpers/MailHelper.php b/app/Helpers/MailHelper.php index f94d6419..a65a5b31 100755 --- a/app/Helpers/MailHelper.php +++ b/app/Helpers/MailHelper.php @@ -93,7 +93,8 @@ class MailHelper $emaill = $params['mail']; $subject = $params['subject']; $message = $params['message']; - + $attachments = isset($params['attachments']) && count($params['attachments']) ? $params['attachments'] : []; + //check BCC mail if (isset($params['bcc'])) { $bcc = $params['bcc']; @@ -118,7 +119,7 @@ class MailHelper try { - $res = $gmailapi->sendMessage($emaill, $subject, $message, 'no-reply@nhanceindia.in', $reply_to, $cc, $bcc); + $res = $gmailapi->sendMessage($emaill, $subject, $message, 'no-reply@nhanceindia.in', $reply_to, $cc, $bcc,$attachments); if($res['status']) { diff --git a/app/Libraries/GmailAPI.php b/app/Libraries/GmailAPI.php index f08a832e..e74101d4 100755 --- a/app/Libraries/GmailAPI.php +++ b/app/Libraries/GmailAPI.php @@ -26,18 +26,20 @@ class GmailAPI 'https://www.googleapis.com/auth/gmail.addons.current.message.readonly', 'https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.labels', - 'https://www.googleapis.com/auth/gmail.send' + 'https://www.googleapis.com/auth/gmail.send', + 'https://mail.google.com/' ]); $this->setAuthConfig(); //die(); $this->client->setAccessType('offline'); $this->client->setPrompt('select_account consent'); - - if (php_sapi_name() !== 'cli' || (uri_string() == 'cli/send_mail_cli' || uri_string() == 'cli/processjob') ) + // echo uri_string();//die(); + if (php_sapi_name() !== 'cli' || (uri_string() == 'cli/send_mail_cli' || uri_string() == 'cli/processjob' || uri_string() == 'cli/check_bounce_mail_cli') ) { + // echo 'getting token';die(); $this->setTokenFromDB(); // Call only if not in CLI } - + // die(); $this->service = new Gmail($this->client); } @@ -99,44 +101,73 @@ class GmailAPI $settingsModel->where('id', 1)->set([$this->tokenField => $token])->update(); } - public function createMessage($to, $subject, $htmlContent, $from, $replyTo, $cc = [], $bcc = []) - { - $boundary = uniqid(rand(), true); + public function createMessage($to, $subject, $htmlContent, $from, $replyTo, $cc = [], $bcc = [], $attachments = []) +{ + // print_r($to);die(); + $boundary = uniqid(rand(), true); // Unique boundary for separating parts - $rawMessageString = "From: $from\r\n"; - $rawMessageString .= "To: $to\r\n"; - $rawMessageString .= "Reply-To: $replyTo\r\n"; + // Initialize raw message headers + $rawMessageString = "From: $from\r\n"; + $rawMessageString .= "To: $to\r\n"; + $rawMessageString .= "Reply-To: $replyTo\r\n"; - if (!empty($cc)) { - $rawMessageString .= "Cc: " . implode(',', $cc) . "\r\n"; - } - - if (!empty($bcc)) { - $rawMessageString .= "Bcc: " . implode(',', $bcc) . "\r\n"; - } - - $rawMessageString .= "Subject: $subject\r\n"; - $rawMessageString .= "MIME-Version: 1.0\r\n"; - $rawMessageString .= "Content-Type: multipart/alternative; boundary=\"$boundary\"\r\n\r\n"; - $rawMessageString .= "--$boundary\r\n"; - $rawMessageString .= "Content-Type: text/html; charset=UTF-8\r\n"; - $rawMessageString .= "Content-Transfer-Encoding: 7bit\r\n\r\n"; - $rawMessageString .= $htmlContent . "\r\n"; - $rawMessageString .= "--$boundary--"; - - $rawMessage = base64_encode($rawMessageString); - $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage); - - $message = new Message(); - $message->setRaw($rawMessage); - - return $message; + if (!empty($cc)) { + $rawMessageString .= "Cc: " . implode(',', $cc) . "\r\n"; } - public function sendMessage($to, $subject, $htmlContent, $from, $replyTo, $cc = [], $bcc = []) + if (!empty($bcc)) { + $rawMessageString .= "Bcc: " . implode(',', $bcc) . "\r\n"; + } + + $rawMessageString .= "Subject: $subject\r\n"; + $rawMessageString .= "MIME-Version: 1.0\r\n"; + $rawMessageString .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n\r\n"; + + // Add HTML content as an alternative part + $rawMessageString .= "--$boundary\r\n"; + $rawMessageString .= "Content-Type: text/html; charset=UTF-8\r\n"; + $rawMessageString .= "Content-Transfer-Encoding: 7bit\r\n\r\n"; + $rawMessageString .= $htmlContent . "\r\n\r\n"; + + // Process each attachment + foreach ($attachments as $attachment) { + $filePath = $attachment['filePath']; + $fileName = $attachment['fileName']; + $fileData = file_get_contents($filePath); + + if ($fileData === false) { + continue; // Skip this attachment if file cannot be read + } + + $base64File = base64_encode($fileData); + + // Add each attachment with appropriate headers + $rawMessageString .= "--$boundary\r\n"; + $rawMessageString .= "Content-Type: application/octet-stream; name=\"$fileName\"\r\n"; + $rawMessageString .= "Content-Disposition: attachment; filename=\"$fileName\"\r\n"; + $rawMessageString .= "Content-Transfer-Encoding: base64\r\n\r\n"; + $rawMessageString .= chunk_split($base64File) . "\r\n\r\n"; + } + + // End boundary to signify the end of the message + $rawMessageString .= "--$boundary--"; + + // Encode the final raw message in base64 for Gmail API + $rawMessage = base64_encode($rawMessageString); + $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage); + + // Create Gmail message object + $message = new Message(); + $message->setRaw($rawMessage); + + return $message; +} + + + public function sendMessage($to, $subject, $htmlContent, $from, $replyTo, $cc = [], $bcc = [],$attachments = []) { try { - $message = $this->createMessage($to, $subject, $htmlContent, $from, $replyTo, $cc, $bcc); + $message = $this->createMessage($to, $subject, $htmlContent, $from, $replyTo, $cc, $bcc,$attachments); $res = $this->service->users_messages->send('me', $message); return array('status' => true, 'data' => $res); } catch (\Exception $e) { @@ -177,4 +208,59 @@ class GmailAPI $this->myLogger->logme('error', 'New token generated and stored in the database.'); print 'New token generated and stored in the database.'; } + + + + public function checkBounceStatus( string $userId = 'me',string $from_date = null,string $to_date = null,int $count = null,string $page = null) + { + $query = 'from:mailer-daemon OR "delivery failed" OR "failure notice"'; + if($from_date != '' && $to_date != '') + { + $query .= 'after:' . $from_date . ' before:' . $to_date; + } + $maxResults = $count ? $count : 20; + $pageToken = $page ? $page : null; + + $params = ['q' => $query,'maxResults' => $maxResults]; + if(isset($pageToken)){ $params['pageToken'] = $pageToken; } + $messages = $this->service->users_messages->listUsersMessages($userId, $params); + + $bounceDetails = []; + $pageToken = $messages->getNextPageToken(); + $bounceDetails['pageToken'] = $pageToken; + if (count($messages->getMessages()) > 0) { + foreach ($messages->getMessages() as $message) { + // Get full message details + $msg = $this->service->users_messages->get($userId, $message->getId(), ['format' => 'full']); + + // Extract important headers and content + $headers = $msg->getPayload()->getHeaders(); + $subject = null; + $date = null; + $to = null; + + foreach ($headers as $header) { + if ($header->getName() === 'Subject') { + $subject = $header->getValue(); + } + if ($header->getName() === 'Date') { + $date = $header->getValue(); + } + if ($header->getName() === 'To') { + $to = $header->getValue(); + } + } + + $bounceDetails['data'][] = [ + 'subject' => $subject, + 'date' => $date, + 'to' => $to, + 'snippet' => $msg->getSnippet(), + ]; + } + } + + return $bounceDetails; + } + } diff --git a/app/Views/employee_data_list.php b/app/Views/employee_data_list.php index 69ca1f74..2a70d76a 100755 --- a/app/Views/employee_data_list.php +++ b/app/Views/employee_data_list.php @@ -67,6 +67,7 @@ Name EMP Code Relationship + Gender Date of Birth Policy name Insurer name @@ -100,6 +101,7 @@ + - diff --git a/public/sample_excel/enrollment.xlsx b/public/sample_excel/enrollment.xlsx index dec7c20ffda4228bd47b41a2be60ea38311f37f9..568e3b5e8e6c534f0f3fbb277383bb8cf3bfb1d6 100755 GIT binary patch delta 4147 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 V?(={7w~P-6Q_U?zafS02@;{jId4>Q0 delta 4007 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 Wf4Y=hY=l2q5ZAZ_N$Y<9>;C|`FJAQk