{**************************************************************************************************} { } { Project JEDI Code Library (JCL) } { } { The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); } { you may not use this file except in compliance with the License. You may obtain a copy of the } { License at http://www.mozilla.org/MPL/ } { } { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF } { ANY KIND, either express or implied. See the License for the specific language governing rights } { and limitations under the License. } { } { The Original Code is JclMapi.pas. } { } { The Initial Developer of the Original Code is Petr Vones. } { Portions created by Petr Vones are Copyright (C) Petr Vones. All Rights Reserved. } { } { Contributors: } { Marcel van Brakel } { Robert Marquardt (marquardt) } { Matthias Thoma (mthoma) } { Petr Vones (pvones) } { } {**************************************************************************************************} { } { Various classes and support routines for sending e-mail through Simple MAPI } { } { Unit owner: Petr Vones } { } {**************************************************************************************************} // Last modified: $Date: 2005/12/12 21:54:10 $ // For history see end of file unit JclMapi; {$I jcl.inc} interface uses Windows, Classes, Contnrs, Mapi, SysUtils, JclBase; type EJclMapiError = class(EJclError) private FErrorCode: DWORD; public property ErrorCode: DWORD read FErrorCode; end; // Simple MAPI interface TJclMapiClient = record ClientName: string; ClientPath: string; RegKeyName: string; Valid: Boolean; end; TJclMapiClientConnect = (ctAutomatic, ctMapi, ctDirect); TJclSimpleMapi = class(TObject) private FAnyClientInstalled: Boolean; FBeforeUnloadClient: TNotifyEvent; FClients: array of TJclMapiClient; FClientConnectKind: TJclMapiClientConnect; FClientLibHandle: THandle; FDefaultClientIndex: Integer; FDefaultProfileName: string; FFunctions: array[0..11] of ^Pointer; FMapiInstalled: Boolean; FMapiVersion: string; FProfiles: array of string; FSelectedClientIndex: Integer; FSimpleMapiInstalled: Boolean; { TODO : consider to move this to a internal single instance class with smart linking } FMapiAddress: TFNMapiAddress; FMapiDeleteMail: TFNMapiDeleteMail; FMapiDetails: TFNMapiDetails; FMapiFindNext: TFNMapiFindNext; FMapiFreeBuffer: TFNMapiFreeBuffer; FMapiLogOff: TFNMapiLogOff; FMapiLogOn: TFNMapiLogOn; FMapiReadMail: TFNMapiReadMail; FMapiResolveName: TFNMapiResolveName; FMapiSaveMail: TFNMapiSaveMail; FMapiSendDocuments: TFNMapiSendDocuments; FMapiSendMail: TFNMapiSendMail; function GetClientCount: Integer; function GetClients(Index: Integer): TJclMapiClient; function GetCurrentClientName: string; function GetProfileCount: Integer; function GetProfiles(Index: Integer): string; procedure SetSelectedClientIndex(const Value: Integer); procedure SetClientConnectKind(const Value: TJclMapiClientConnect); function UseMapi: Boolean; protected procedure BeforeUnloadClientLib; dynamic; procedure CheckListIndex(I, ArrayLength: Integer); function GetClientLibName: string; class function ProfilesRegKey: string; procedure ReadMapiSettings; public constructor Create; destructor Destroy; override; function ClientLibLoaded: Boolean; procedure LoadClientLib; procedure UnloadClientLib; property AnyClientInstalled: Boolean read FAnyClientInstalled; property ClientConnectKind: TJclMapiClientConnect read FClientConnectKind write SetClientConnectKind; property ClientCount: Integer read GetClientCount; property Clients[Index: Integer]: TJclMapiClient read GetClients; default; property CurrentClientName: string read GetCurrentClientName; property DefaultClientIndex: Integer read FDefaultClientIndex; property DefaultProfileName: string read FDefaultProfileName; property MapiInstalled: Boolean read FMapiInstalled; property MapiVersion: string read FMapiVersion; property ProfileCount: Integer read GetProfileCount; property Profiles[Index: Integer]: string read GetProfiles; property SelectedClientIndex: Integer read FSelectedClientIndex write SetSelectedClientIndex; property SimpleMapiInstalled: Boolean read FSimpleMapiInstalled; property BeforeUnloadClient: TNotifyEvent read FBeforeUnloadClient write FBeforeUnloadClient; // Simple MAPI functions property MapiAddress: TFNMapiAddress read FMapiAddress; property MapiDeleteMail: TFNMapiDeleteMail read FMapiDeleteMail; property MapiDetails: TFNMapiDetails read FMapiDetails; property MapiFindNext: TFNMapiFindNext read FMapiFindNext; property MapiFreeBuffer: TFNMapiFreeBuffer read FMapiFreeBuffer; property MapiLogOff: TFNMapiLogOff read FMapiLogOff; property MapiLogOn: TFNMapiLogOn read FMapiLogOn; property MapiReadMail: TFNMapiReadMail read FMapiReadMail; property MapiResolveName: TFNMapiResolveName read FMapiResolveName; property MapiSaveMail: TFNMapiSaveMail read FMapiSaveMail; property MapiSendDocuments: TFNMapiSendDocuments read FMapiSendDocuments; property MapiSendMail: TFNMapiSendMail read FMapiSendMail; end; const // Simple email classes MapiAddressTypeSMTP = 'SMTP'; MapiAddressTypeFAX = 'FAX'; MapiAddressTypeTLX = 'TLX'; type TJclEmailRecipKind = (rkOriginator, rkTO, rkCC, rkBCC); TJclEmailRecip = class(TObject) private FAddress: string; FAddressType: string; FKind: TJclEmailRecipKind; FName: string; protected function SortingName: string; public function AddressAndName: string; class function RecipKindToString(const AKind: TJclEmailRecipKind): string; property AddressType: string read FAddressType write FAddressType; property Address: string read FAddress write FAddress; property Kind: TJclEmailRecipKind read FKind write FKind; property Name: string read FName write FName; end; TJclEmailRecips = class(TObjectList) private FAddressesType: string; function GetItems(Index: Integer): TJclEmailRecip; function GetOriginator: TJclEmailRecip; public function Add(const Address: string; const Name: string = ''; const Kind: TJclEmailRecipKind = rkTO; const AddressType: string = ''): Integer; procedure SortRecips; property AddressesType: string read FAddressesType write FAddressesType; property Items[Index: Integer]: TJclEmailRecip read GetItems; default; property Originator: TJclEmailRecip read GetOriginator; end; TJclEmailFindOption = (foFifo, foUnreadOnly); TJclEmailLogonOption = (loLogonUI, loNewSession, loForceDownload); TJclEmailReadOption = (roAttachments, roHeaderOnly, roMarkAsRead); TJclEmailFindOptions = set of TJclEmailFindOption; TJclEmailLogonOptions = set of TJclEmailLogonOption; TJclEmailReadOptions = set of TJclEmailReadOption; TJclEmailReadMsg = record ConversationID: string; DateReceived: TDateTime; MessageType: string; Flags: FLAGS; end; TJclTaskWindowsList = array of THandle; TJclEmail = class(TJclSimpleMapi) private FAttachments: TStringList; FBody: string; FFindOptions: TJclEmailFindOptions; FHtmlBody: Boolean; FLogonOptions: TJclEmailLogonOptions; FParentWnd: THandle; FParentWndValid: Boolean; FReadMsg: TJclEmailReadMsg; FRecipients: TJclEmailRecips; FSeedMessageID: string; FSessionHandle: THandle; FSubject: string; FTaskWindowList: TJclTaskWindowsList; function GetAttachments: TStrings; function GetParentWnd: THandle; function GetUserLogged: Boolean; procedure SetBody(const Value: string); procedure SetParentWnd(const Value: THandle); protected procedure BeforeUnloadClientLib; override; procedure DecodeRecips(RecipDesc: PMapiRecipDesc; Count: Integer); function InternalSendOrSave(Save: Boolean; ShowDialog: Boolean): Boolean; function LogonOptionsToFlags(ShowDialog: Boolean): DWORD; public constructor Create; destructor Destroy; override; function Address(const Caption: string = ''; EditFields: Integer = 3): Boolean; procedure Clear; function Delete(const MessageID: string): Boolean; function FindFirstMessage: Boolean; function FindNextMessage: Boolean; procedure LogOff; procedure LogOn(const ProfileName: string = ''; const Password: string = ''); function MessageReport(Strings: TStrings; MaxWidth: Integer = 80; IncludeAddresses: Boolean = False): Integer; function Read(const Options: TJclEmailReadOptions = []): Boolean; function ResolveName(var Name, Address: string; ShowDialog: Boolean = False): Boolean; procedure RestoreTaskWindows; function Save: Boolean; procedure SaveTaskWindows; function Send(ShowDialog: Boolean = True): Boolean; procedure SortAttachments; property Attachments: TStrings read GetAttachments; property Body: string read FBody write SetBody; property FindOptions: TJclEmailFindOptions read FFindOptions write FFindOptions; property HtmlBody: Boolean read FHtmlBody write FHtmlBody; property LogonOptions: TJclEmailLogonOptions read FLogonOptions write FLogonOptions; property ParentWnd: THandle read GetParentWnd write SetParentWnd; property ReadMsg: TJclEmailReadMsg read FReadMsg; property Recipients: TJclEmailRecips read FRecipients; property SeedMessageID: string read FSeedMessageID write FSeedMessageID; property SessionHandle: THandle read FSessionHandle; property Subject: string read FSubject write FSubject; property UserLogged: Boolean read GetUserLogged; end; // Simple email send function function JclSimpleSendMail(const Recipient, Name, Subject, Body: string; const Attachment: string = ''; ShowDialog: Boolean = True; ParentWND: THandle = 0; const ProfileName: string = ''; const Password: string = ''): Boolean; function JclSimpleSendFax(const Recipient, Name, Subject, Body: string; const Attachment: string = ''; ShowDialog: Boolean = True; ParentWND: THandle = 0; const ProfileName: string = ''; const Password: string = ''): Boolean; function JclSimpleBringUpSendMailDialog(const Subject, Body: string; const Attachment: string = ''; ParentWND: THandle = 0; const ProfileName: string = ''; const Password: string = ''): Boolean; // MAPI Errors function MapiCheck(const Res: DWORD; IgnoreUserAbort: Boolean = True): DWORD; function MapiErrorMessage(const ErrorCode: DWORD): string; implementation uses JclFileUtils, JclLogic, JclRegistry, JclResources, JclStrings, JclSysInfo, JclSysUtils; const MapiDll = 'mapi32.dll'; MapiExportNames: array [0..11] of PChar = ( 'MAPIAddress', 'MAPIDeleteMail', 'MAPIDetails', 'MAPIFindNext', 'MAPIFreeBuffer', 'MAPILogoff', 'MAPILogon', 'MAPIReadMail', 'MAPIResolveName', 'MAPISaveMail', 'MAPISendDocuments', 'MAPISendMail'); AddressTypeDelimiter = ':'; //=== MAPI Errors check ====================================================== function MapiCheck(const Res: DWORD; IgnoreUserAbort: Boolean): DWORD; var Error: EJclMapiError; begin if (Res = SUCCESS_SUCCESS) or (IgnoreUserAbort and (Res = MAPI_E_USER_ABORT)) then Result := Res else begin Error := EJclMapiError.CreateResFmt(@RsMapiError, [Res, MapiErrorMessage(Res)]); Error.FErrorCode := Res; raise Error; end; end; function MapiErrorMessage(const ErrorCode: DWORD): string; begin case ErrorCode of MAPI_E_USER_ABORT: Result := RsMapiErrUSER_ABORT; MAPI_E_FAILURE: Result := RsMapiErrFAILURE; MAPI_E_LOGIN_FAILURE: Result := RsMapiErrLOGIN_FAILURE; MAPI_E_DISK_FULL: Result := RsMapiErrDISK_FULL; MAPI_E_INSUFFICIENT_MEMORY: Result := RsMapiErrINSUFFICIENT_MEMORY; MAPI_E_ACCESS_DENIED: Result := RsMapiErrACCESS_DENIED; MAPI_E_TOO_MANY_SESSIONS: Result := RsMapiErrTOO_MANY_SESSIONS; MAPI_E_TOO_MANY_FILES: Result := RsMapiErrTOO_MANY_FILES; MAPI_E_TOO_MANY_RECIPIENTS: Result := RsMapiErrTOO_MANY_RECIPIENTS; MAPI_E_ATTACHMENT_NOT_FOUND: Result := RsMapiErrATTACHMENT_NOT_FOUND; MAPI_E_ATTACHMENT_OPEN_FAILURE: Result := RsMapiErrATTACHMENT_OPEN_FAILURE; MAPI_E_ATTACHMENT_WRITE_FAILURE: Result := RsMapiErrATTACHMENT_WRITE_FAILURE; MAPI_E_UNKNOWN_RECIPIENT: Result := RsMapiErrUNKNOWN_RECIPIENT; MAPI_E_BAD_RECIPTYPE: Result := RsMapiErrBAD_RECIPTYPE; MAPI_E_NO_MESSAGES: Result := RsMapiErrNO_MESSAGES; MAPI_E_INVALID_MESSAGE: Result := RsMapiErrINVALID_MESSAGE; MAPI_E_TEXT_TOO_LARGE: Result := RsMapiErrTEXT_TOO_LARGE; MAPI_E_INVALID_SESSION: Result := RsMapiErrINVALID_SESSION; MAPI_E_TYPE_NOT_SUPPORTED: Result := RsMapiErrTYPE_NOT_SUPPORTED; MAPI_E_AMBIGUOUS_RECIPIENT: Result := RsMapiErrAMBIGUOUS_RECIPIENT; MAPI_E_MESSAGE_IN_USE: Result := RsMapiErrMESSAGE_IN_USE; MAPI_E_NETWORK_FAILURE: Result := RsMapiErrNETWORK_FAILURE; MAPI_E_INVALID_EDITFIELDS: Result := RsMapiErrINVALID_EDITFIELDS; MAPI_E_INVALID_RECIPS: Result := RsMapiErrINVALID_RECIPS; MAPI_E_NOT_SUPPORTED: Result := RsMapiErrNOT_SUPPORTED; else Result := ''; end; end; procedure RestoreTaskWindowsList(const List: TJclTaskWindowsList); var I: Integer; function RestoreTaskWnds(Wnd: THandle; List: TJclTaskWindowsList): BOOL; stdcall; var I: Integer; EnableIt: Boolean; begin if IsWindowVisible(Wnd) then begin EnableIt := False; for I := 1 to Length(List) - 1 do if List[I] = Wnd then begin EnableIt := True; Break; end; EnableWindow(Wnd, EnableIt); end; Result := True; end; begin if Length(List) > 0 then begin EnumThreadWindows(MainThreadID, @RestoreTaskWnds, Integer(List)); for I := 0 to Length(List) - 1 do EnableWindow(List[I], True); SetFocus(List[0]); end; end; function SaveTaskWindowsList: TJclTaskWindowsList; function SaveTaskWnds(Wnd: THandle; var Data: TJclTaskWindowsList): BOOL; stdcall; var C: Integer; begin if IsWindowVisible(Wnd) and IsWindowEnabled(Wnd) then begin C := Length(Data); SetLength(Data, C + 1); Data[C] := Wnd; end; Result := True; end; begin SetLength(Result, 1); Result[0] := GetFocus; EnumThreadWindows(MainThreadID, @SaveTaskWnds, Integer(@Result)); end; //=== { TJclSimpleMapi } ===================================================== constructor TJclSimpleMapi.Create; begin inherited Create; FFunctions[0] := @@FMapiAddress; FFunctions[1] := @@FMapiDeleteMail; FFunctions[2] := @@FMapiDetails; FFunctions[3] := @@FMapiFindNext; FFunctions[4] := @@FMapiFreeBuffer; FFunctions[5] := @@FMapiLogOff; FFunctions[6] := @@FMapiLogOn; FFunctions[7] := @@FMapiReadMail; FFunctions[8] := @@FMapiResolveName; FFunctions[9] := @@FMapiSaveMail; FFunctions[10] := @@FMapiSendDocuments; FFunctions[11] := @@FMapiSendMail; FDefaultClientIndex := -1; FClientConnectKind := ctAutomatic; FSelectedClientIndex := -1; ReadMapiSettings; end; destructor TJclSimpleMapi.Destroy; begin UnloadClientLib; inherited Destroy; end; procedure TJclSimpleMapi.BeforeUnloadClientLib; begin if Assigned(FBeforeUnloadClient) then FBeforeUnloadClient(Self); end; procedure TJclSimpleMapi.CheckListIndex(I, ArrayLength: Integer); begin if (I < 0) or (I >= ArrayLength) then raise EJclMapiError.CreateResFmt(@RsMapiInvalidIndex, [I]); end; function TJclSimpleMapi.ClientLibLoaded: Boolean; begin Result := FClientLibHandle <> 0; end; function TJclSimpleMapi.GetClientCount: Integer; begin Result := Length(FClients); end; function TJclSimpleMapi.GetClientLibName: string; begin if UseMapi then Result := MapiDll else Result := FClients[FSelectedClientIndex].ClientPath; end; function TJclSimpleMapi.GetClients(Index: Integer): TJclMapiClient; begin CheckListIndex(Index, ClientCount); Result := FClients[Index]; end; function TJclSimpleMapi.GetCurrentClientName: string; begin if UseMapi then Result := 'MAPI' else if ClientCount > 0 then Result := Clients[SelectedClientIndex].ClientName else Result := ''; end; function TJclSimpleMapi.GetProfileCount: Integer; begin Result := Length(FProfiles); end; function TJclSimpleMapi.GetProfiles(Index: Integer): string; begin CheckListIndex(Index, ProfileCount); Result := FProfiles[Index]; end; procedure TJclSimpleMapi.LoadClientLib; var I: Integer; P: Pointer; begin if ClientLibLoaded then Exit; FClientLibHandle := LoadLibrary(PChar(GetClientLibName)); if FClientLibHandle = 0 then RaiseLastOSError; for I := 0 to Length(FFunctions) - 1 do begin P := GetProcAddress(FClientLibHandle, PChar(MapiExportNames[I])); if P = nil then begin UnloadClientLib; raise EJclMapiError.CreateResFmt(@RsMapiMissingExport, [MapiExportNames[I]]); end else FFunctions[I]^ := P; end; end; class function TJclSimpleMapi.ProfilesRegKey: string; begin if IsWinNT then Result := 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles' else Result := 'SOFTWARE\Microsoft\Windows Messaging Subsystem\Profiles'; end; procedure TJclSimpleMapi.ReadMapiSettings; const MessageSubsytemKey = 'SOFTWARE\Microsoft\Windows Messaging Subsystem'; MailClientsKey = 'SOFTWARE\Clients\Mail'; var DefaultValue, ClientKey: string; SL: TStringList; I: Integer; function CheckValid(var Client: TJclMapiClient): Boolean; var I: Integer; LibHandle: THandle; begin LibHandle := LoadLibraryEx(PChar(Client.ClientPath), 0, DONT_RESOLVE_DLL_REFERENCES); Result := (LibHandle <> 0); if Result then begin for I := Low(MapiExportNames) to High(MapiExportNames) do if GetProcAddress(LibHandle, PChar(MapiExportNames[I])) = nil then begin Result := False; Break; end; FreeLibrary(LibHandle); end; Client.Valid := Result; end; begin FClients := nil; FDefaultClientIndex := -1; FProfiles := nil; FDefaultProfileName := ''; SL := TStringList.Create; try if RegKeyExists(HKEY_LOCAL_MACHINE, MessageSubsytemKey) then begin FMapiInstalled := RegReadStringDef(HKEY_LOCAL_MACHINE, MessageSubsytemKey, 'MAPIX', '') = '1'; FSimpleMapiInstalled := RegReadStringDef(HKEY_LOCAL_MACHINE, MessageSubsytemKey, 'MAPI', '') = '1'; FMapiVersion := RegReadStringDef(HKEY_LOCAL_MACHINE, MessageSubsytemKey, 'MAPIXVER', ''); end; FAnyClientInstalled := FMapiInstalled; if RegKeyExists(HKEY_LOCAL_MACHINE, MailClientsKey) then begin DefaultValue := RegReadStringDef(HKEY_LOCAL_MACHINE, MailClientsKey, '', ''); if RegGetKeyNames(HKEY_LOCAL_MACHINE, MailClientsKey, SL) then begin SetLength(FClients, SL.Count); for I := 0 to SL.Count - 1 do begin FClients[I].RegKeyName := SL[I]; FClients[I].Valid := False; ClientKey := MailClientsKey + '\' + SL[I]; if RegKeyExists(HKEY_LOCAL_MACHINE, ClientKey) then begin FClients[I].ClientName := RegReadStringDef(HKEY_LOCAL_MACHINE, ClientKey, '', ''); FClients[I].ClientPath := RegReadStringDef(HKEY_LOCAL_MACHINE, ClientKey, 'DLLPath', ''); ExpandEnvironmentVar(FClients[I].ClientPath); if CheckValid(FClients[I]) then FAnyClientInstalled := True; end; end; FDefaultClientIndex := SL.IndexOf(DefaultValue); FSelectedClientIndex := FDefaultClientIndex; end; end; if RegKeyExists(HKEY_CURRENT_USER, ProfilesRegKey) then begin FDefaultProfileName := RegReadStringDef(HKEY_CURRENT_USER, ProfilesRegKey, 'DefaultProfile', ''); if RegGetKeyNames(HKEY_CURRENT_USER, ProfilesRegKey, SL) then begin SetLength(FProfiles, SL.Count); for I := 0 to SL.Count - 1 do FProfiles[I] := SL[I]; end; end; finally SL.Free; end; end; procedure TJclSimpleMapi.SetClientConnectKind(const Value: TJclMapiClientConnect); begin if FClientConnectKind <> Value then begin FClientConnectKind := Value; UnloadClientLib; end; end; procedure TJclSimpleMapi.SetSelectedClientIndex(const Value: Integer); begin CheckListIndex(Value, ClientCount); if FSelectedClientIndex <> Value then begin FSelectedClientIndex := Value; UnloadClientLib; end; end; procedure TJclSimpleMapi.UnloadClientLib; var I: Integer; begin if ClientLibLoaded then begin BeforeUnloadClientLib; FreeLibrary(FClientLibHandle); FClientLibHandle := 0; for I := 0 to Length(FFunctions) - 1 do FFunctions[I]^ := nil; end; end; function TJclSimpleMapi.UseMapi: Boolean; begin case FClientConnectKind of ctAutomatic: UseMapi := FSimpleMapiInstalled; ctMapi: UseMapi := True; ctDirect: UseMapi := False; else UseMapi := True; end; end; //=== { TJclEmailRecip } ===================================================== function TJclEmailRecip.AddressAndName: string; var N: string; begin if Name = '' then N := Address else N := Name; Result := Format('"%s" <%s>', [N, Address]); end; class function TJclEmailRecip.RecipKindToString(const AKind: TJclEmailRecipKind): string; const Idents: array [TJclEmailRecipKind] of string = ( RsMapiMailORIG, RsMapiMailTO, RsMapiMailCC, RsMapiMailBCC); begin case AKind of rkOriginator: Result := RsMapiMailORIG; rkTO: Result := RsMapiMailTO; rkCC: Result := RsMapiMailCC; rkBCC: Result := RsMapiMailBCC; end; end; function TJclEmailRecip.SortingName: string; begin if FName = '' then Result := FAddress else Result := FName; end; //=== { TJclEmailRecips } ==================================================== function TJclEmailRecips.Add(const Address, Name: string; const Kind: TJclEmailRecipKind; const AddressType: string): Integer; var Item: TJclEmailRecip; begin Item := TJclEmailRecip.Create; try Item.Address := Trim(Address); Item.AddressType := AddressType; Item.Name := Name; Item.Kind := Kind; Result := inherited Add(Item); except Item.Free; raise; end; end; function TJclEmailRecips.GetItems(Index: Integer): TJclEmailRecip; begin Result := TJclEmailRecip(Get(Index)); end; function TJclEmailRecips.GetOriginator: TJclEmailRecip; var I: Integer; begin Result := nil; for I := 0 to Count - 1 do if Items[I].Kind = rkOriginator then begin Result := Items[I]; Break; end; end; function EmailRecipsCompare(Item1, Item2: Pointer): Integer; var R1, R2: TJclEmailRecip; begin R1 := TJclEmailRecip(Item1); R2 := TJclEmailRecip(Item2); Result := Integer(R1.Kind) - Integer(R2.Kind); if Result = 0 then Result := AnsiCompareStr(R1.SortingName, R2.SortingName); end; procedure TJclEmailRecips.SortRecips; begin Sort(EmailRecipsCompare); end; //=== { TJclEmail } ========================================================== constructor TJclEmail.Create; begin inherited Create; FAttachments := TStringList.Create; FLogonOptions := [loLogonUI]; FFindOptions := [foFifo]; FRecipients := TJclEmailRecips.Create(True); FRecipients.AddressesType := MapiAddressTypeSMTP; end; destructor TJclEmail.Destroy; begin FreeAndNil(FAttachments); FreeAndNil(FRecipients); inherited Destroy; end; function TJclEmail.Address(const Caption: string; EditFields: Integer): Boolean; var NewRecipCount: ULONG; NewRecips: PMapiRecipDesc; Recips: TMapiRecipDesc; Res: DWORD; begin LoadClientLib; NewRecips := nil; NewRecipCount := 0; Res := MapiAddress(FSessionHandle, ParentWnd, PChar(Caption), EditFields, nil, 0, Recips, LogonOptionsToFlags(False), 0, @NewRecipCount, NewRecips); Result := (MapiCheck(Res, True) = SUCCESS_SUCCESS); if Result then try DecodeRecips(NewRecips, NewRecipCount); finally MapiFreeBuffer(NewRecips); end; end; procedure TJclEmail.BeforeUnloadClientLib; begin LogOff; inherited BeforeUnloadClientLib; end; procedure TJclEmail.Clear; begin Attachments.Clear; Body := ''; FSubject := ''; Recipients.Clear; FReadMsg.MessageType := ''; FReadMsg.DateReceived := 0; FReadMsg.ConversationID := ''; FReadMsg.Flags := 0; end; procedure TJclEmail.DecodeRecips(RecipDesc: PMapiRecipDesc; Count: Integer); var S: string; N, I: Integer; Kind: TJclEmailRecipKind; begin for I := 0 to Count - 1 do begin if RecipDesc = nil then Break; Kind := rkOriginator; with RecipDesc^ do begin case ulRecipClass of MAPI_ORIG: Kind := rkOriginator; MAPI_TO: Kind := rkTO; MAPI_CC: Kind := rkCC; MAPI_BCC: Kind := rkBCC; $FFFFFFFF: // Eudora client version 5.2.0.9 bug Kind := rkOriginator; else MapiCheck(MAPI_E_INVALID_MESSAGE, True); end; S := lpszAddress; N := Pos(AddressTypeDelimiter, S); if N = 0 then Recipients.Add(S, lpszName, Kind) else Recipients.Add(Copy(S, N + 1, Length(S)), lpszName, Kind, Copy(S, 1, N - 1)); end; Inc(RecipDesc); end; end; function TJclEmail.Delete(const MessageID: string): Boolean; begin LoadClientLib; Result := MapiCheck(MapiDeleteMail(FSessionHandle, 0, PChar(MessageID), 0, 0), False) = SUCCESS_SUCCESS; end; function TJclEmail.FindFirstMessage: Boolean; begin SeedMessageID := ''; Result := FindNextMessage; end; function TJclEmail.FindNextMessage: Boolean; var MsgID: array [0..512] of AnsiChar; Flags, Res: ULONG; begin Result := False; if not UserLogged then Exit; Flags := MAPI_LONG_MSGID; if foFifo in FFindOptions then Inc(Flags, MAPI_GUARANTEE_FIFO); if foUnreadOnly in FFindOptions then Inc(Flags, MAPI_UNREAD_ONLY); Res := MapiFindNext(FSessionHandle, 0, nil, PChar(FSeedMessageID), Flags, 0, MsgId); Result := (Res = SUCCESS_SUCCESS); if Result then SeedMessageID := MsgID else begin SeedMessageID := ''; if Res <> MAPI_E_NO_MESSAGES then MapiCheck(Res, True); end; end; function TJclEmail.GetAttachments: TStrings; begin Result := FAttachments; end; function TJclEmail.GetParentWnd: THandle; begin if FParentWndValid then Result := FParentWnd else Result := GetMainAppWndFromPid(GetCurrentProcessId); end; function TJclEmail.GetUserLogged: Boolean; begin Result := (FSessionHandle <> 0); end; function TJclEmail.InternalSendOrSave(Save, ShowDialog: Boolean): Boolean; const RecipClasses: array [TJclEmailRecipKind] of DWORD = (MAPI_ORIG, MAPI_TO, MAPI_CC, MAPI_BCC); var AttachArray: array of TMapiFileDesc; RecipArray: array of TMapiRecipDesc; RealAdresses: array of string; MapiMessage: TMapiMessage; Flags, Res: DWORD; I: Integer; MsgID: array [0..512] of AnsiChar; HtmlBodyFileName: string; begin if not AnyClientInstalled then raise EJclMapiError.CreateRes(@RsMapiMailNoClient); HtmlBodyFileName := ''; try if FHtmlBody then begin HtmlBodyFileName := FindUnusedFileName(PathAddSeparator(GetWindowsTempFolder) + 'JclMapi', 'htm', 'Temp'); Attachments.Insert(0, HtmlBodyFileName); StringToFile(HtmlBodyFileName, Body); end; // Create attachments if Attachments.Count > 0 then begin SetLength(AttachArray, Attachments.Count); for I := 0 to Attachments.Count - 1 do begin if not FileExists(Attachments[I]) then MapiCheck(MAPI_E_ATTACHMENT_NOT_FOUND, False); Attachments[I] := ExpandFileName(Attachments[I]); FillChar(AttachArray[I], SizeOf(TMapiFileDesc), #0); AttachArray[I].nPosition := DWORD(-1); AttachArray[I].lpszFileName := nil; AttachArray[I].lpszPathName := PChar(Attachments[I]); end; end else AttachArray := nil; // Create recipients if Recipients.Count > 0 then begin SetLength(RecipArray, Recipients.Count); SetLength(RealAdresses, Recipients.Count); for I := 0 to Recipients.Count - 1 do begin FillChar(RecipArray[I], SizeOf(TMapiRecipDesc), #0); with RecipArray[I], Recipients[I] do begin ulRecipClass := RecipClasses[Kind]; if Name = '' then // some clients requires Name item always filled begin if FAddress = '' then MapiCheck(MAPI_E_INVALID_RECIPS, False); lpszName := PChar(FAddress); end else lpszName := PChar(FName); if FAddressType <> '' then RealAdresses[I] := FAddressType + AddressTypeDelimiter + FAddress else if Recipients.AddressesType <> '' then RealAdresses[I] := Recipients.AddressesType + AddressTypeDelimiter + FAddress else RealAdresses[I] := FAddress; lpszAddress := PCharOrNil(RealAdresses[I]); end; end; end else begin if ShowDialog then RecipArray := nil else MapiCheck(MAPI_E_INVALID_RECIPS, False); end; // Load MAPI client library LoadClientLib; // Fill MapiMessage structure FillChar(MapiMessage, SizeOf(MapiMessage), #0); MapiMessage.lpszSubject := PChar(FSubject); if FHtmlBody then MapiMessage.lpszNoteText := #0 else MapiMessage.lpszNoteText := PChar(FBody); MapiMessage.lpRecips := PMapiRecipDesc(RecipArray); MapiMessage.nRecipCount := Length(RecipArray); MapiMessage.lpFiles := PMapiFileDesc(AttachArray); MapiMessage.nFileCount := Length(AttachArray); Flags := LogonOptionsToFlags(ShowDialog); if Save then begin StrPLCopy(MsgID, SeedMessageID, SizeOf(MsgID)); Res := MapiSaveMail(FSessionHandle, ParentWND, MapiMessage, Flags, 0, MsgID); if Res = SUCCESS_SUCCESS then SeedMessageID := MsgID; end else Res := MapiSendMail(FSessionHandle, ParentWND, MapiMessage, Flags, 0); Result := (MapiCheck(Res, True) = SUCCESS_SUCCESS); finally if HtmlBodyFileName <> '' then begin DeleteFile(HtmlBodyFileName); Attachments.Delete(0); end; end; end; procedure TJclEmail.LogOff; begin if UserLogged then begin MapiCheck(MapiLogOff(FSessionHandle, ParentWND, 0, 0), True); FSessionHandle := 0; end; end; procedure TJclEmail.LogOn(const ProfileName, Password: string); begin if not UserLogged then begin LoadClientLib; MapiCheck(MapiLogOn(ParentWND, PChar(ProfileName), PChar(Password), LogonOptionsToFlags(False), 0, @FSessionHandle), True); end; end; function TJclEmail.LogonOptionsToFlags(ShowDialog: Boolean): DWORD; begin Result := 0; if FSessionHandle = 0 then begin if loLogonUI in FLogonOptions then Inc(Result, MAPI_LOGON_UI); if loNewSession in FLogonOptions then Inc(Result, MAPI_NEW_SESSION); if loForceDownload in FLogonOptions then Inc(Result, MAPI_FORCE_DOWNLOAD); end; if ShowDialog then Inc(Result, MAPI_DIALOG); end; function TJclEmail.MessageReport(Strings: TStrings; MaxWidth: Integer; IncludeAddresses: Boolean): Integer; const NameDelimiter = ', '; var LabelsWidth: Integer; NamesList: array [TJclEmailRecipKind] of string; ReportKind: TJclEmailRecipKind; I, Cnt: Integer; BreakStr, S: string; begin Cnt := Strings.Count; LabelsWidth := Length(RsMapiMailSubject); for ReportKind := Low(ReportKind) to High(ReportKind) do begin NamesList[ReportKind] := ''; LabelsWidth := Max(LabelsWidth, Length(TJclEmailRecip.RecipKindToString(ReportKind))); end; BreakStr := AnsiCrLf + StringOfChar(' ', LabelsWidth + 2); for I := 0 to Recipients.Count - 1 do with Recipients[I] do begin if IncludeAddresses then S := AddressAndName else S := Name; NamesList[Kind] := NamesList[Kind] + S + NameDelimiter; end; Strings.BeginUpdate; try for ReportKind := Low(ReportKind) to High(ReportKind) do if NamesList[ReportKind] <> '' then begin S := StrPadRight(TJclEmailRecip.RecipKindToString(ReportKind), LabelsWidth, AnsiSpace) + ': ' + Copy(NamesList[ReportKind], 1, Length(NamesList[ReportKind]) - Length(NameDelimiter)); Strings.Add(WrapText(S, BreakStr, [AnsiTab, AnsiSpace], MaxWidth)); end; S := RsMapiMailSubject + ': ' + Subject; Strings.Add(WrapText(S, BreakStr, [AnsiTab, AnsiSpace], MaxWidth)); Result := Strings.Count - Cnt; Strings.Add(''); Strings.Add(WrapText(Body, AnsiCrLf, [AnsiTab, AnsiSpace, '-'], MaxWidth)); finally Strings.EndUpdate; end; end; function TJclEmail.Read(const Options: TJclEmailReadOptions): Boolean; var Flags: ULONG; Msg: PMapiMessage; I: Integer; Files: PMapiFileDesc; function CopyAndStrToInt(const S: string; Index, Count: Integer): Integer; begin Result := StrToIntDef(Copy(S, Index, Count), 0); end; function MessageDateToDate(const S: string): TDateTime; var T: TSystemTime; begin FillChar(T, SizeOf(T), #0); with T do begin wYear := CopyAndStrToInt(S, 1, 4); wMonth := CopyAndStrToInt(S, 6, 2); wDay := CopyAndStrToInt(S, 9, 2); wHour := CopyAndStrToInt(S, 12, 2); wMinute := CopyAndStrToInt(S, 15,2); Result := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); end; end; begin Result := False; if not UserLogged then Exit; Clear; Flags := 0; if roHeaderOnly in Options then Inc(Flags, MAPI_ENVELOPE_ONLY); if not (roMarkAsRead in Options) then Inc(Flags, MAPI_PEEK); if not (roAttachments in Options) then Inc(Flags, MAPI_SUPPRESS_ATTACH); MapiCheck(MapiReadMail(SessionHandle, 0, PChar(FSeedMessageID), Flags, 0, Msg), True); if Msg <> nil then try DecodeRecips(Msg^.lpOriginator, 1); DecodeRecips(Msg^.lpRecips, Msg^.nRecipCount); FSubject := Msg^.lpszSubject; Body := AdjustLineBreaks(Msg^.lpszNoteText); Files := Msg^.lpFiles; if Files <> nil then for I := 0 to Msg^.nFileCount - 1 do begin if Files^.lpszPathName <> nil then Attachments.Add(Files^.lpszPathName) else Attachments.Add(Files^.lpszFileName); Inc(Files); end; FReadMsg.MessageType := Msg^.lpszMessageType; if Msg^.lpszDateReceived <> nil then FReadMsg.DateReceived := MessageDateToDate(Msg^.lpszDateReceived); FReadMsg.ConversationID := Msg^.lpszConversationID; FReadMsg.Flags := Msg^.flFlags; Result := True; finally MapiFreeBuffer(Msg); end; end; function TJclEmail.ResolveName(var Name, Address: string; ShowDialog: Boolean): Boolean; var Recip: PMapiRecipDesc; Res, Flags: DWORD; begin LoadClientLib; Flags := LogonOptionsToFlags(ShowDialog) or MAPI_AB_NOMODIFY; Recip := nil; Res := MapiResolveName(FSessionHandle, ParentWnd, PChar(Name), Flags, 0, Recip); Result := (MapiCheck(Res, True) = SUCCESS_SUCCESS) and (Recip <> nil); if Result then begin Address := Recip^.lpszAddress; Name := Recip^.lpszName; MapiFreeBuffer(Recip); end; end; procedure TJclEmail.RestoreTaskWindows; begin RestoreTaskWindowsList(FTaskWindowList); FTaskWindowList := nil; end; function TJclEmail.Save: Boolean; begin Result := InternalSendOrSave(True, False); end; procedure TJclEmail.SaveTaskWindows; begin FTaskWindowList := SaveTaskWindowsList; end; function TJclEmail.Send(ShowDialog: Boolean): Boolean; begin Result := InternalSendOrSave(False, ShowDialog); end; procedure TJclEmail.SetBody(const Value: string); begin if Value = '' then FBody := '' else FBody := StrEnsureSuffix(AnsiCrLf, Value); end; procedure TJclEmail.SetParentWnd(const Value: THandle); begin FParentWnd := Value; FParentWndValid := True; end; procedure TJclEmail.SortAttachments; begin FAttachments.Sort; end; //=== Simple email send function ============================================= function SimpleSendHelper(const ARecipient, AName, ASubject, ABody: string; const AAttachment: string; AShowDialog: Boolean; AParentWND: THandle; const AProfileName, APassword, AAddressType: string): Boolean; begin with TJclEmail.Create do try if AParentWND <> 0 then ParentWnd := AParentWND; if ARecipient <> '' then Recipients.Add(ARecipient, AName, rkTO, AAddressType); Subject := ASubject; Body := ABody; if AAttachment <> '' then Attachments.Add(AAttachment); if AProfileName <> '' then LogOn(AProfileName, APassword); Result := Send(AShowDialog); finally Free; end; end; function JclSimpleSendMail(const Recipient, Name, Subject, Body: string; const Attachment: string; ShowDialog: Boolean; ParentWND: THandle; const ProfileName: string; const Password: string): Boolean; begin Result := SimpleSendHelper(Recipient, Name, Subject, Body, Attachment, ShowDialog, ParentWND, ProfileName, Password, MapiAddressTypeSMTP); end; function JclSimpleSendFax(const Recipient, Name, Subject, Body: string; const Attachment: string; ShowDialog: Boolean; ParentWND: THandle; const ProfileName: string; const Password: string): Boolean; begin Result := SimpleSendHelper(Recipient, Name, Subject, Body, Attachment, ShowDialog, ParentWND, ProfileName, Password, MapiAddressTypeFAX); end; function JclSimpleBringUpSendMailDialog(const Subject, Body: string; const Attachment: string; ParentWND: THandle; const ProfileName: string; const Password: string): Boolean; begin Result := SimpleSendHelper('', '', Subject, Body, Attachment, True, ParentWND, ProfileName, Password, MapiAddressTypeSMTP); end; // History: // $Log: JclMapi.pas,v $ // Revision 1.15 2005/12/12 21:54:10 outchy // HWND changed to THandle (linking problems with BCB). // // Revision 1.14 2005/03/08 08:33:22 marquardt // overhaul of exceptions and resourcestrings, minor style cleaning // // Revision 1.13 2005/02/25 07:20:15 marquardt // add section lines // // Revision 1.12 2005/02/24 16:34:52 marquardt // remove divider lines, add section lines (unfinished) // // Revision 1.11 2004/10/25 20:42:07 mthoma // #0002255 // // Revision 1.10 2004/10/17 21:29:23 mthoma // Used version rev 1.2 to remove all rev 1.3 contributions. // // Revision 1.9 2004/10/17 21:00:15 mthoma // cleaning // // Revision 1.8 2004/07/31 06:21:03 marquardt // fixing TStringLists, adding BeginUpdate/EndUpdate, finalization improved // // Revision 1.7 2004/07/28 18:00:53 marquardt // various style cleanings, some minor fixes // // Revision 1.6 2004/06/16 07:30:30 marquardt // added tilde to all IFNDEF ENDIFs, inherited qualified // // Revision 1.5 2004/06/02 03:23:47 rrossmair // cosmetic changes in several units (code formatting, help TODOs processed etc.) // // Revision 1.4 2004/05/05 07:33:49 rrossmair // header updated according to new policy: initial developers & contributors listed // // Revision 1.3 2004/04/06 04:55:17 // adapt compiler conditions, add log entry // end.