背景:最新開發一個讀取郵件內容,傳送郵件的功能,然而是內網配置 POP、IMAP、SMTP 都不可用,僅支援 Exchange 的通訊。
PHP Exchange Web服務庫(PHP-ews)旨在使用Exchange Web服務簡化與Microsoft Exchange伺服器的通訊。它處理使用SOAP服務所需的NTLM身份驗證,併為形成請求所需的複雜型別提供物件導向的介面。
github:github.com/jamesiarmes/php-ews
安裝
composer require php-ews/php-ews "~1.0" // 需開啟 soap 擴充套件
使用
.env
# Laravel-exchange EWS郵件服務
# 郵件傳送服務地址
EWS_HOST=smtp.office365.com
# 傳送郵件郵箱
EWS_USERNAME=xxxx@outlook.com
# 傳送郵件密碼
EWS_PASSWORD=xxxxx
# 回覆郵件的地址
MAIL_TO_ADDRESS=xxxx@tencent.com
# 回覆郵件者姓名
MAIL_FROM_NAME=xxx
建立EWS類
App\Handlers\EWS.php
<?php
namespace App\Handlers;
use Illuminate\Support\Facades\Storage;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseFolderIdsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfRequestAttachmentIdsType;
use jamesiarmes\PhpEws\Client;
use App\Exceptions\EWSException;
use jamesiarmes\PhpEws\Enumeration\DefaultShapeNamesType;
use jamesiarmes\PhpEws\Enumeration\IndexBasePointType;
use jamesiarmes\PhpEws\Enumeration\ItemQueryTraversalType;
use jamesiarmes\PhpEws\Request\FindItemType;
use jamesiarmes\PhpEws\Request\GetAttachmentType;
use jamesiarmes\PhpEws\Request\GetItemType;
use jamesiarmes\PhpEws\Type\BodyType;
use jamesiarmes\PhpEws\Type\IndexedPageViewType;
use jamesiarmes\PhpEws\Type\ItemIdType;
use jamesiarmes\PhpEws\Type\ItemResponseShapeType;
use jamesiarmes\PhpEws\Type\MessageType;
use jamesiarmes\PhpEws\Request\SendItemType;
use jamesiarmes\PhpEws\Type\EmailAddressType;
use jamesiarmes\PhpEws\Request\CreateItemType;
use jamesiarmes\PhpEws\Type\RequestAttachmentIdType;
use jamesiarmes\PhpEws\Type\TargetFolderIdType;
use jamesiarmes\PhpEws\Type\FileAttachmentType;
use jamesiarmes\PhpEws\Enumeration\BodyTypeType;
use jamesiarmes\PhpEws\Type\SingleRecipientType;
use jamesiarmes\PhpEws\Request\CreateAttachmentType;
use jamesiarmes\PhpEws\Enumeration\ResponseClassType;
use jamesiarmes\PhpEws\Type\DistinguishedFolderIdType;
use jamesiarmes\PhpEws\ArrayType\ArrayOfRecipientsType;
use jamesiarmes\PhpEws\Enumeration\MessageDispositionType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfAllItemsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfAttachmentsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseItemIdsType;
use jamesiarmes\PhpEws\Enumeration\DistinguishedFolderIdNameType;
class EWS
{
/**
* [EWS client]
*
* @var [Object]
*/
public $client;
/**
* [郵箱地址]
*
* @var [String]
*/
public $emails;
/**
* [郵件標題]
*
* @var [String]
*/
public $subject;
/**
* [郵件內容]
*
* @var [String]
*/
public $body;
/**
* [附件的絕對路徑]
*
* @var [String]
*/
public $attachment;
/**
* [構造方法]
*
* @return [Object]
*/
public function __construct()
{
$host = env('EWS_HOST', null);
$username = env('EWS_USERNAME', null);
$password = env('EWS_PASSWORD', null);
$version = Client::VERSION_2016;
$this->client = new Client($host, $username, $password, $version);
}
/**
* 傳送郵件
*
* @param [Array] $data [資料]
*
* @return [Json] [結果]
*/
public function send($data)
{
if(empty($data['emails'])){
throw new EWSException('The email is invalid.', 422);
}
if(!empty($data['attachments'])){
foreach ($data['attachments'] as $path) {
if(!file_exists($path)){
throw new EWSException("The file:{$path} is not exists.", 404);
}
}
}
//初始化
$this->emails = $data['emails'] ?? '';
$this->subject = $data['subject'] ?? '';
$this->body = $data['body'] ?? '';
$this->attachments = $data['attachments'] ?? '';
//建立所需要引數
$message = $this->createMessage();
$request = $this->createRequest($message);
//建立草稿並返回
$item = $this->createItem($request);
$send = $this->doSend($item);
return $send;
}
/**
* [建立草稿並且返回]
*
* @param [Request] $request [message]
*
* @return [Array] [返回建立資訊]
*/
protected function createItem($request)
{
$response = $this->client->CreateItem($request);
//分析結果
$response_messages = $response->ResponseMessages->CreateItemResponseMessage;
$data = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the created messages, printing the id for each.
foreach ($response_message->Items->Message as $item) {
$data['code'] = 0;
$data['item_id'] = $item->ItemId->Id;
$data['change_key'] = $item->ItemId->ChangeKey;
}
}
return $data;
}
/**
* [執行傳送]
*
* @param [Array] $item [草稿]
*
* @return [Array] [結果]
*/
protected function doSend($item)
{
//判斷是否有附件新增
if(!empty($this->attachments) && $item['code'] == 0){
$item = $this->addAttachment($item);
}
$message_id = $item['item_id'];
$change_key = $item['change_key'];
// Build the request.
$request = new SendItemType();
$request->SaveItemToFolder = true;
$request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
// Add the message to the request.
$item = new ItemIdType();
$item->Id = $message_id;
$item->ChangeKey = $change_key;
$request->ItemIds->ItemId[] = $item;
// Configure the folder to save the sent message to.
$send_folder = new TargetFolderIdType();
$send_folder->DistinguishedFolderId = new DistinguishedFolderIdType();
$send_folder->DistinguishedFolderId->Id = DistinguishedFolderIdNameType::SENT;
$request->SavedItemFolderId = $send_folder;
$response = $this->client->SendItem($request);
// Iterate over the results, printing any error messages.
$response_messages = $response->ResponseMessages->SendItemResponseMessage;
$data = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
$data['code'] = 0;
$data['message'] = 'Message sent successfully.';
}
return $data;
}
/**
* [新增附件]
*
* @param [Item] $item [草稿]
*
* @return [Array] [附件結果]
*/
protected function addAttachment($item)
{
// Build the request,
$request = new CreateAttachmentType();
$request->ParentItemId = new ItemIdType();
$request->ParentItemId->Id = $item['item_id'];
$request->Attachments = new NonEmptyArrayOfAttachmentsType();
// Build the file attachment.
foreach ($this->attachments as $path) {
// Open file handlers.
$file = new \SplFileObject($path);
$finfo = finfo_open();
$attachment = new FileAttachmentType();
$attachment->Content = $file->openFile()->fread($file->getSize());
$attachment->Name = $file->getBasename();
$attachment->ContentType = finfo_file($finfo, $path);
$request->Attachments->FileAttachment[] = $attachment;
}
$response = $this->client->CreateAttachment($request);
$response_messages = $response->ResponseMessages->CreateAttachmentResponseMessage;
$item = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the created attachments, printing the id of each.
foreach ($response_message->Attachments->FileAttachment as $attachment) {
$item['code'] = 0;
$item['item_id'] = $attachment->AttachmentId->RootItemId;
$item['change_key'] = $attachment->AttachmentId->RootItemChangeKey;
$item['attachment_id'] = $attachment->AttachmentId->Id;
}
}
return $item;
}
/**
* 建立草稿箱
*
* @param [Message] $message [要傳送的文字資訊]
*
* @return [request] []
*/
protected function createRequest($message)
{
// Build the request,
$request = new CreateItemType();
$request->Items = new NonEmptyArrayOfAllItemsType();
// Add the message to the request.
$request->Items->Message[] = $message;
// Save the message, but do not send it.
$request->MessageDisposition = MessageDispositionType::SAVE_ONLY;
return $request;
}
/**
* 建立 發件人 標題 內容
*
* @param [Client] $client [EWS客戶端]
* @param [EmailAddress] $email [郵箱地址]
* @param [Subject] $subject [標題]
* @param [Body] $body [內容]
*
* @return [Message] [message]
*/
protected function createMessage()
{
// Create the message.
$message = new MessageType();
$message->Subject = $this->subject;
$message->ToRecipients = new ArrayOfRecipientsType();
// Set the sender.
$message->From = new SingleRecipientType();
$message->From->Mailbox = new EmailAddressType();
$message->From->Mailbox->EmailAddress = env('EWS_USERNAME', null);
foreach ($this->emails as $key => $email) {
// Set the recipient.
$to = $email['to'] ?? '';
$name = $email['name'] ?? $to;
$recipient = new EmailAddressType();
$recipient->Name = $name;
$recipient->EmailAddress = $to;
$message->ToRecipients->Mailbox[] = $recipient;
}
// Set the message body.
$message->Body = new BodyType();
$message->Body->BodyType = BodyTypeType::HTML;
$message->Body->_ = $this->body;
return $message;
}
/**
* 獲取郵件ID
* @param int $page_size
* @return array
* @throws EWSException
*/
public function getEmailIds(int $page_size = 10): array
{
// Build the request.
$request = new FindItemType();
$request->ParentFolderIds = new NonEmptyArrayOfBaseFolderIdsType();
$request->Traversal = ItemQueryTraversalType::SHALLOW;
// Return all message properties.
$request->ItemShape = new ItemResponseShapeType();
$request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;
// Search in the user's inbox.
$folder_id = new DistinguishedFolderIdType();
$folder_id->Id = DistinguishedFolderIdNameType::INBOX;
$request->ParentFolderIds->DistinguishedFolderId[] = $folder_id;
// Limits the number of items retrieved
$request->IndexedPageItemView = new IndexedPageViewType();
$request->IndexedPageItemView->BasePoint = IndexBasePointType::BEGINNING;
$request->IndexedPageItemView->Offset = 0;
$request->IndexedPageItemView->MaxEntriesReturned = $page_size;
$response = $this->client->FindItem($request);
$email_ids = [];
// Iterate over the results, printing any error messages or message subjects.
$response_messages = $response->ResponseMessages->FindItemResponseMessage;
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to search for messages with :".$response_message->MessageText, $response_message->ResponseCode);
}
foreach ($response_message->RootFolder->Items->Message as $message) {
$email_ids[] = $message->ItemId->Id;
}
}
return $email_ids;
}
/**
* 根據郵件ID 獲取文字內容和下載附件
* @param array $email_ids
* @return array
* @throws EWSException
*/
public function getBodyContent(array $email_ids): array
{
// Build the request.
$request = new GetItemType();
$request->ItemShape = new ItemResponseShapeType();
$request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;
$request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
// Iterate over the message ids, setting each one on the request.
foreach ($email_ids as $email_id) {
$item = new ItemIdType();
$item->Id = $email_id;
$request->ItemIds->ItemId[] = $item;
}
$response = $this->client->GetItem($request);
$bodyContent = [];
// Iterate over the results, printing any error messages or message subjects.
$response_messages = $response->ResponseMessages->GetItemResponseMessage;
foreach ($response_messages as $key => $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to get message with :".$response_message->MessageText, $response_message->ResponseCode);
}
$attachments = array();
// Iterate over the messages, printing the subject for each.
foreach ($response_message->Items->Message as $item) {
$body = str_replace(array("\r\n", "\r", "\n"), "", strip_tags($item->Body->_));
preg_match("/:(\d+)]/", $body, $passwordMatches);
$bodyContent[$key] = [
'message_id' => $item->InternetMessageId,
'subject' => $item->Subject,
'email_id' => $item->ItemId->Id,
'body' => $body,
'password' => last($passwordMatches),
];
// If there are no attachments for the item, move on to the next
// message.
if (empty($item->Attachments)) {
continue;
}
// Iterate over the attachments for the message.
foreach ($item->Attachments->FileAttachment as $attachment) {
$attachments[] = $attachment->AttachmentId->Id;
}
// Build the request to get the attachments.
$request = new GetAttachmentType();
$request->AttachmentIds = new NonEmptyArrayOfRequestAttachmentIdsType();
// Iterate over the attachments for the message.
foreach ($attachments as $attachment_id) {
$id = new RequestAttachmentIdType();
$id->Id = $attachment_id;
$request->AttachmentIds->AttachmentId[] = $id;
}
$response = $this->client->GetAttachment($request);
// Iterate over the response messages, printing any error messages or
// saving the attachments.
$attachment_response_messages = $response->ResponseMessages->GetAttachmentResponseMessage;
foreach ($attachment_response_messages as $attachment_response_message) {
// Make sure the request succeeded.
if ($attachment_response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to get attachment with :".$response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the file attachments, saving each one.
$attachments = $attachment_response_message->Attachments->FileAttachment;
foreach ($attachments as $attachment) {
$bodyContent[$key]['attachments'] = [
'AttachmentId' => $attachment->AttachmentId->Id,
'AttachmentName' => $attachment->Name,
];
Storage::disk('public')->put($attachment->Name, $attachment->Content);
}
}
}
}
return $bodyContent;
}
}
獲取郵件內容及附件:
$exchange = new EWS();
try {
$emailIds = $exchange->getEmailIds(5);
} catch (EWSException $e) {
throw new EWSException("獲取郵件ID失敗 :".$e->getMessage());
}
try {
$bodyContents = $exchange->getBodyContent($emailIds);
} catch (EWSException $e) {
throw new EWSException("獲取文字內容和下載附件失敗 :".$e->getMessage());
}
獲取附件解壓後的內容(有密碼):
public function getZipBodyContent($bodyContents): array
{
foreach ($bodyContents as $bodyContent) {
// 判斷主題是否包含 Tencentflex 或 文字是否有密碼
if (!Str::contains($bodyContent['subject'], 'Tencentflex') || !$bodyContent['password']) {
continue;
}
$zip = new ZipArchive();
// 開啟壓縮包
if (!$zip->open(config('filesystems.disks.public.root').'/'.$bodyContent['attachments']['AttachmentName'])) {
logger()->error("開啟檔案失敗。檔名: ".$bodyContent['attachments']['AttachmentName']);
return [];
}
// 解壓密碼
$zip->setPassword($bodyContent['password']);
// 設定解壓到新的目錄
if (!$zip->extractTo(config('filesystems.disks.public.root'))) {
logger()->error("密碼錯誤, 解壓失敗。檔名: ".$bodyContent['attachments']['AttachmentName']);
return [];
}
$rows = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
$rows += Excel::toArray(new VolunteerServiceInfosImport(), config('filesystems.disks.public.root').'/'.$filename)[0];
Storage::disk('public')->delete($filename); // 刪除ZIP附件解壓後的檔案
Storage::disk('public')->delete($bodyContent['attachments']['AttachmentName']); // 刪除ZIP附件
}
$zip->close();
logger()->info("[下載附件成功, No: ".$bodyContent['email_id'].'][檔案內容]['.json_encode($rows, JSON_UNESCAPED_UNICODE)."]");
return [
'rows' => $rows,
'subject' => $bodyContent['subject'], // 郵件主題
'zipFileName' => $bodyContent['attachments']['AttachmentName'], // 附件壓縮包
'message_id' => $bodyContent['message_id'], // 郵件ID
];
}
return [];
}
傳送郵件:
// 要傳送給的人的姓名和郵箱地址
$emails = [
[
'to' => env("MAIL_TO_ADDRESS"),
'name' => env("MAIL_FROM_NAME")
],
];
// 附件路徑
$attachments = [
storage_path('1.pdf'),
storage_path('2.pdf')
];
// 傳送
$exchange = new EWS();
$send = $exchange->send([
'emails' => $emails,
'subject' => 'Laravel-exchange EWS服務庫',
'body' => response(view('emails.pdf', ['name' => '郵箱測試']))->getContent(), //這裡可以使用blade模板自定義你的模板樣式
'attachments' => $attachments
]);
return response()->json($send);
自定義異常
App\Exceptions\EWSException.php
<?php
namespace App\Exceptions;
use Exception;
class EWSException extends Exception
{
}
自定義異常處理:
App\Exceptions\Handler.php
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
if ($exception instanceof EWSException) {
//如果是郵件傳送異常,這裡做一些記錄
}
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if($exception instanceof EWSException) {
$result = [
"code" => $exception->getCode(),
"message" => $exception->getMessage()
];
return response()->json($result, $exception->getCode());
}
return parent::render($request, $exception);
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結