src/ios/APPEmailComposerImpl.m
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "APPEmailComposerImpl.h"
#import <Cordova/CDVAvailability.h>
#import <MessageUI/MFMailComposeViewController.h>
#import <MobileCoreServices/MobileCoreServices.h>
@implementation APPEmailComposerImpl
#pragma mark -
#pragma mark Public
/**
* Checks if the mail composer is able to send mails.
*/
- (bool) canSendMail
{
return [MFMailComposeViewController canSendMail];
}
/**
* Checks if an app is available to handle the specified scheme.
*
* @param scheme An URL scheme, that defaults to 'mailto:
*/
- (bool) canOpenScheme:(NSString *)scheme
{
__block bool canOpen = false;
NSCharacterSet *set = [NSCharacterSet URLFragmentAllowedCharacterSet];
if (!scheme) {
scheme = @"mailto:";
} else if (![scheme containsString:@":"]) {
scheme = [scheme stringByAppendingString:@":"];
}
scheme = [[scheme stringByAppendingString:@"?test@test.de"]
stringByAddingPercentEncodingWithAllowedCharacters:set];
NSURL *url = [[NSURL URLWithString:scheme] absoluteURL];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
canOpen = [[UIApplication sharedApplication] canOpenURL:url];
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return canOpen;
}
/**
* Instantiates an email composer view.
*
* @param properties The email properties like subject, body, attachments
* @param delegateTo The mail composition view controller’s delegate.
*
* @return The configured email composer view
*/
- (MFMailComposeViewController*) mailComposerFromProperties:(NSDictionary*)props
delegateTo:(id)receiver
{
BOOL isHTML = [[props objectForKey:@"isHtml"] boolValue];
MFMailComposeViewController* draft;
draft = [[MFMailComposeViewController alloc] init];
// Sender
[self setSendingEmailAddress:[props objectForKey:@"from"] ofDraft:draft];
// Subject
[self setSubject:[props objectForKey:@"subject"] ofDraft:draft];
// Body (as HTML)
[self setBody:[props objectForKey:@"body"] ofDraft:draft isHTML:isHTML];
// Recipients
[self setToRecipients:[props objectForKey:@"to"] ofDraft:draft];
// CC Recipients
[self setCcRecipients:[props objectForKey:@"cc"] ofDraft:draft];
// BCC Recipients
[self setBccRecipients:[props objectForKey:@"bcc"] ofDraft:draft];
// Attachments
[self setAttachments:[props objectForKey:@"attachments"] ofDraft:draft];
draft.mailComposeDelegate = receiver;
return draft;
}
/**
* Creates an mailto-url-sheme.
*
* @param properties The email properties like subject, body, attachments
*
* @return The configured mailto-sheme
*/
- (NSURL*) urlFromProperties:(NSDictionary*)props
{
NSString* mailto = [props objectForKey:@"app"];
NSMutableArray* parts = [[NSMutableArray alloc] init];
NSString* query = @"";
NSString* subject = [props objectForKey:@"subject"];
NSString* body = [props objectForKey:@"body"];
NSArray* to = [props objectForKey:@"to"];
NSArray* cc = [props objectForKey:@"cc"];
NSArray* bcc = [props objectForKey:@"bcc"];
NSArray* attachments = [props objectForKey:@"attachments"];
NSCharacterSet* cs = [NSCharacterSet URLHostAllowedCharacterSet];
if (![mailto containsString:@"://"]) {
mailto = [mailto stringByAppendingString:@"://"];
}
if (to.count > 0) {
[parts addObject: [NSString stringWithFormat: @"to=%@",
[to componentsJoinedByString:@","]]];
}
if (cc.count > 0) {
[parts addObject: [NSString stringWithFormat: @"cc=%@",
[cc componentsJoinedByString:@","]]];
}
if (bcc.count > 0) {
[parts addObject: [NSString stringWithFormat: @"bcc=%@",
[bcc componentsJoinedByString:@","]]];
}
if (subject.length > 0) {
[parts addObject: [NSString stringWithFormat: @"subject=%@",
[subject stringByAddingPercentEncodingWithAllowedCharacters:cs]]];
}
if (body.length > 0) {
[parts addObject: [NSString stringWithFormat: @"body=%@",
[body stringByAddingPercentEncodingWithAllowedCharacters:cs]]];
}
if (attachments.count > 0) {
NSLog(@"The 'mailto' URI Scheme (RFC 2368) does not support attachments.");
}
query = [parts componentsJoinedByString:@"&"];
if (query.length > 0) {
query = [@"?" stringByAppendingString:query];
}
mailto = [mailto stringByAppendingString:query];
return [NSURL URLWithString:mailto];
}
#pragma mark -
#pragma mark Private
/**
* Sets the subject of the email draft.
*
* @param subject The subject
* @param draft The email composer view
*/
- (void) setSendingEmailAddress:(NSString*)from
ofDraft:(MFMailComposeViewController*)draft
{
if (@available(iOS 11.0, *)) {
[draft setPreferredSendingEmailAddress:from];
}
}
/**
* Sets the subject of the email draft.
*
* @param subject The subject
* @param draft The email composer view
*/
- (void) setSubject:(NSString*)subject
ofDraft:(MFMailComposeViewController*)draft
{
[draft setSubject:subject];
}
/**
* Sets the body of the email draft.
*
* @param body The body
* @param isHTML Indicates if the body is an HTML encoded string.
* @param draft The email composer view
*/
- (void) setBody:(NSString*)body ofDraft:(MFMailComposeViewController*)draft
isHTML:(BOOL)isHTML
{
[draft setMessageBody:body isHTML:isHTML];
}
/**
* Sets the recipients of the email draft.
*
* @param recipients The recipients
* @param draft The email composer view.
*/
- (void) setToRecipients:(NSArray*)recipients
ofDraft:(MFMailComposeViewController*)draft
{
[draft setToRecipients:recipients];
}
/**
* Sets the CC recipients of the email draft.
*
* @param ccRecipients The CC recipients
* @param draft The email composer view
*/
- (void) setCcRecipients:(NSArray*)ccRecipients
ofDraft:(MFMailComposeViewController*)draft
{
[draft setCcRecipients:ccRecipients];
}
/**
* Sets the BCC recipients of the email draft.
*
* @param bccRecipients The BCC recipients
* @param draft The email composer view.
*/
- (void) setBccRecipients:(NSArray*)bccRecipients
ofDraft:(MFMailComposeViewController*)draft
{
[draft setBccRecipients:bccRecipients];
}
/**
* Sets the attachments of the email draft.
*
* @param attachments The attachments
* @param draft The email composer view
*/
- (void) setAttachments:(NSArray*)attatchments
ofDraft:(MFMailComposeViewController*)draft
{
if (!attatchments) return;
for (NSString* path in attatchments)
{
NSData* data = [self getDataForAttachmentPath:path];
if (!data) continue;
NSString* basename = [self getBasenameFromAttachmentPath:path];
NSString* pathExt = [basename pathExtension];
NSString* fileName = [basename pathComponents].lastObject;
NSString* mimeType = [self getMimeTypeFromFileExtension:pathExt];
// Couldn't find mimeType, must be some type of binary data
if (mimeType == nil) mimeType = @"application/octet-stream";
[draft addAttachmentData:data mimeType:mimeType fileName:fileName];
}
}
/**
* Returns the data for a given (relative) attachment path.
*
* @param path An absolute/relative path or the base64 data
*
* @return The data for the attachment.
*/
- (NSData*) getDataForAttachmentPath:(NSString*)path
{
if ([path hasPrefix:@"file:///"])
{
return [self dataForAbsolutePath:path];
}
else if ([path hasPrefix:@"res:"])
{
return [self dataForResource:path];
}
else if ([path hasPrefix:@"file://"])
{
return [self dataForAsset:path];
}
else if ([path hasPrefix:@"app://"])
{
return [self dataForAppInternalPath:path];
}
else if ([path hasPrefix:@"base64:"])
{
return [self dataFromBase64:path];
}
NSFileManager* fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:path]){
NSLog(@"File not found: %@", path);
}
return [fm contentsAtPath:path];
}
/**
* Retrieves the data for an absolute attachment path.
*
* @param path An absolute file path.
*
* @return The data for the attachment.
*/
- (NSData*) dataForAbsolutePath:(NSString*)path
{
NSFileManager* fm = [NSFileManager defaultManager];
NSString* absPath;
absPath = [path stringByReplacingOccurrencesOfString:@"file://"
withString:@""];
if (![fm fileExistsAtPath:absPath]) {
NSLog(@"File not found: %@", absPath);
}
NSData* data = [fm contentsAtPath:absPath];
return data;
}
/**
* Retrieves the data for a resource path.
*
* @param path A relative file path.
*
* @return The data for the attachment.
*/
- (NSData*) dataForResource:(NSString*)path
{
NSString* imgName = [[path pathComponents].lastObject
stringByDeletingPathExtension];
#ifdef __CORDOVA_4_0_0
if ([imgName isEqualToString:@"icon"]) {
imgName = @"AppIcon60x60@3x";
}
#endif
UIImage* img = [UIImage imageNamed:imgName];
if (img == NULL) {
NSLog(@"File not found: %@", path);
}
NSData* data = UIImagePNGRepresentation(img);
return data;
}
/**
* Retrieves the data for a asset path.
*
* @param path A relative www file path.
*
* @return The data for the attachment.
*/
- (NSData*) dataForAsset:(NSString*)path
{
NSFileManager* fm = [NSFileManager defaultManager];
NSString* absPath;
NSBundle* mainBundle = [NSBundle mainBundle];
NSString* bundlePath = [[mainBundle bundlePath]
stringByAppendingString:@"/"];
absPath = [path stringByReplacingOccurrencesOfString:@"file:/"
withString:@"www"];
absPath = [bundlePath stringByAppendingString:absPath];
if (![fm fileExistsAtPath:absPath]) {
NSLog(@"File not found: %@", absPath);
}
NSData* data = [fm contentsAtPath:absPath];
return data;
}
/**
* Retrieves the file URL for an internal app path.
*
* @param path A relative file path from main bundle dir.
*
* @return The data for the attachment.
*/
- (NSData*) dataForAppInternalPath:(NSString*)path
{
NSFileManager* fm = [NSFileManager defaultManager];
NSBundle* mainBundle = [NSBundle mainBundle];
NSString* absPath = [mainBundle bundlePath];
if (![fm fileExistsAtPath:absPath]) {
NSLog(@"File not found: %@", absPath);
}
NSData* data = [fm contentsAtPath:absPath];
return data;
}
/**
* Retrieves the data for a base64 encoded string.
*
* @param base64String Base64 encoded string.
*
* @return The data for the attachment.
*/
- (NSData*) dataFromBase64:(NSString*)base64String
{
NSUInteger length = [base64String length];
NSRegularExpression *regex;
NSString *dataString;
regex = [NSRegularExpression regularExpressionWithPattern:@"^base64:[^/]+.."
options:NSRegularExpressionCaseInsensitive
error:NULL];
dataString = [regex stringByReplacingMatchesInString:base64String
options:0
range:NSMakeRange(0, length)
withTemplate:@""];
NSData* data = [[NSData alloc] initWithBase64EncodedString:dataString
options:NSDataBase64DecodingIgnoreUnknownCharacters];
return data;
}
/**
* Retrieves the mime type from the file extension.
*
* @param extension The file's extension.
*
* @return The coresponding MIME type.
*/
- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension
{
if (!extension)
return nil;
// Get the UTI from the file's extension
CFStringRef ext = (CFStringRef)CFBridgingRetain(extension);
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL);
// Converting UTI to a mime type
NSString *result = (NSString*)
CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType));
CFRelease(ext);
CFRelease(type);
return result;
}
/**
* Retrieves the attachments basename.
*
* @param path The file path or bas64 data of the attachment.
*
* @return The attachments basename.
*/
- (NSString*) getBasenameFromAttachmentPath:(NSString*)path
{
if ([path hasPrefix:@"base64:"])
{
NSString* pathWithoutPrefix;
pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:"
withString:@""];
return [pathWithoutPrefix substringToIndex:
[pathWithoutPrefix rangeOfString:@"//"].location];
}
return path;
}
@end