arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.m
plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.mRaw
  1#import "SocialSharing.h"
  2#import "NSString+URLEncoding.h"
  3#import <Cordova/CDV.h>
  4#import <Social/Social.h>
  5#import <Foundation/NSException.h>
  6#import <MessageUI/MFMessageComposeViewController.h>
  7#import <MessageUI/MFMailComposeViewController.h>
  8#import <MobileCoreServices/MobileCoreServices.h>
  9
 10static NSString *const kShareOptionMessage = @"message";
 11static NSString *const kShareOptionSubject = @"subject";
 12static NSString *const kShareOptionFiles = @"files";
 13static NSString *const kShareOptionUrl = @"url";
 14
 15@implementation SocialSharing {
 16  UIPopoverController *_popover;
 17  NSString *_popupCoordinates;
 18}
 19
 20- (void)pluginInitialize {
 21  if ([self isEmailAvailable]) {
 22    [self cycleTheGlobalMailComposer];
 23  }
 24}
 25
 26- (void)available:(CDVInvokedUrlCommand*)command {
 27  BOOL avail = NO;
 28  if (NSClassFromString(@"UIActivityViewController")) {
 29    avail = YES;
 30  }
 31  CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail];
 32  [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
 33}
 34
 35- (NSString*)getIPadPopupCoordinates {
 36  if (_popupCoordinates != nil) {
 37    return _popupCoordinates;
 38  }
 39  if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) {
 40    return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"];
 41  } else {
 42    // prolly a wkwebview, ignoring for now
 43    return nil;
 44  }
 45}
 46
 47- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command {
 48  _popupCoordinates  = [command.arguments objectAtIndex:0];
 49}
 50
 51- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps {
 52  CGRect rect = CGRectZero;
 53  if ([comps count] == 4) {
 54    rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]);
 55  }
 56  return rect;
 57}
 58
 59- (void)share:(CDVInvokedUrlCommand*)command {
 60  [self shareInternal:command
 61          withOptions:@{
 62                        kShareOptionMessage: [command.arguments objectAtIndex:0],
 63                        kShareOptionSubject: [command.arguments objectAtIndex:1],
 64                        kShareOptionFiles: [command.arguments objectAtIndex:2],
 65                        kShareOptionUrl: [command.arguments objectAtIndex:3]
 66                      }
 67    isBooleanResponse:YES
 68];
 69}
 70
 71- (void)shareWithOptions:(CDVInvokedUrlCommand*)command {
 72  NSDictionary* options = [command.arguments objectAtIndex:0];
 73  [self shareInternal:command
 74          withOptions:options
 75    isBooleanResponse:NO
 76   ];
 77}
 78
 79- (void)shareInternal:(CDVInvokedUrlCommand*)command withOptions:(NSDictionary*)options isBooleanResponse:(BOOL)boolResponse {
 80  [self.commandDelegate runInBackground:^{ //avoid main thread block  especially if sharing big files from url
 81    if (!NSClassFromString(@"UIActivityViewController")) {
 82      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
 83      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
 84      return;
 85    }
 86
 87    NSString *message   = options[kShareOptionMessage];
 88    NSString *subject   = options[kShareOptionSubject];
 89    NSArray  *filenames = options[kShareOptionFiles];
 90    NSString *urlString = options[kShareOptionUrl];
 91
 92    NSMutableArray *activityItems = [[NSMutableArray alloc] init];
 93
 94    if (message != (id)[NSNull null] && message != nil) {
 95    [activityItems addObject:message];
 96    }
 97
 98    if (filenames != (id)[NSNull null] && filenames != nil && filenames.count > 0) {
 99      NSMutableArray *files = [[NSMutableArray alloc] init];
100      for (NSString* filename in filenames) {
101        NSObject *file = [self getImage:filename];
102        if (file == nil) {
103          file = [self getFile:filename];
104        }
105        if (file != nil) {
106          [files addObject:file];
107        }
108      }
109      [activityItems addObjectsFromArray:files];
110    }
111
112    if (urlString != (id)[NSNull null] && urlString != nil) {
113        [activityItems addObject:[NSURL URLWithString:[urlString URLEncodedString]]];
114    }
115
116    UIActivity *activity = [[UIActivity alloc] init];
117    NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil];
118    UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];
119    if (subject != (id)[NSNull null] && subject != nil) {
120      [activityVC setValue:subject forKey:@"subject"];
121    }
122
123    if ([activityVC respondsToSelector:(@selector(setCompletionWithItemsHandler:))]) {
124      [activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * returnedItems, NSError * activityError) {
125        [self cleanupStoredFiles];
126        if (boolResponse) {
127          [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]
128                                      callbackId:command.callbackId];
129        } else {
130          NSDictionary * result = @{@"completed":@(completed), @"app":activityType == nil ? @"" : activityType};
131          [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]
132                                      callbackId:command.callbackId];
133        }
134      }];
135    } else {
136      // let's suppress this warning otherwise folks will start opening issues while it's not relevant
137#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
138        [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) {
139          [self cleanupStoredFiles];
140          NSDictionary * result = @{@"completed":@(completed), @"app":activityType};
141          CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
142          [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
143        }];
144#pragma GCC diagnostic warning "-Wdeprecated-declarations"
145      }
146
147    NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"];
148    if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) {
149      activityVC.excludedActivityTypes = socialSharingExcludeActivities;
150    }
151
152    dispatch_async(dispatch_get_main_queue(), ^(void){
153      // iPad on iOS >= 8 needs a different approach
154      if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
155        NSString* iPadCoords = [self getIPadPopupCoordinates];
156        if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) {
157          NSArray *comps = [iPadCoords componentsSeparatedByString:@","];
158          CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
159          if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
160#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
161            activityVC.popoverPresentationController.sourceView = self.webView;
162            activityVC.popoverPresentationController.sourceRect = rect;
163#endif
164          } else {
165            _popover = [[UIPopoverController alloc] initWithContentViewController:activityVC];
166            _popover.delegate = self;
167            [_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
168          }
169        } else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
170#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
171          activityVC.popoverPresentationController.sourceView = self.webView;
172          // position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8)
173          NSArray *comps = [NSArray arrayWithObjects:
174                            [NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200],
175                            [NSNumber numberWithInt:self.viewController.view.frame.size.height],
176                            [NSNumber numberWithInt:400],
177                            [NSNumber numberWithInt:400],
178                            nil];
179          CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
180          activityVC.popoverPresentationController.sourceRect = rect;
181#endif
182        }
183      }
184      [[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil];
185    });
186  }];
187}
188
189- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command {
190  [self shareViaInternal:command type:SLServiceTypeTwitter];
191}
192
193- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command {
194  [self shareViaInternal:command type:SLServiceTypeFacebook];
195}
196
197- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command {
198  // If Fb app is installed a message is not prefilled.
199  // When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already.
200  NSString *message = [command.arguments objectAtIndex:0];
201  if (message != (id)[NSNull null]) {
202    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
203      BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9
204      if (fbAppInstalled) {
205        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
206        [pasteboard setValue:message forPasteboardType:@"public.text"];
207        NSString *hint = [command.arguments objectAtIndex:4];
208        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
209        [alert show];
210        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
211          [alert dismissWithClickedButtonIndex:-1 animated:YES];
212        });
213      }
214    });
215  }
216  [self shareViaInternal:command type:SLServiceTypeFacebook];
217}
218
219- (void)shareVia:(CDVInvokedUrlCommand*)command {
220  [self shareViaInternal:command type:[command.arguments objectAtIndex:4]];
221}
222
223- (void)canShareVia:(CDVInvokedUrlCommand*)command {
224  NSString *via = [command.arguments objectAtIndex:4];
225  CDVPluginResult * pluginResult;
226  if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) {
227    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
228  } else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) {
229    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
230  } else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) {
231    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
232  } else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) {
233    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
234  } else if ([self isAvailableForSharing:command type:via]) {
235    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
236  } else {
237    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
238  }
239  [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
240}
241
242- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command {
243  if ([self isEmailAvailable]) {
244    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
245    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
246  } else {
247    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
248    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
249  }
250}
251
252- (bool)isEmailAvailable {
253  Class messageClass = (NSClassFromString(@"MFMailComposeViewController"));
254  return messageClass != nil && [messageClass canSendMail];
255}
256
257- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command
258                         type:(NSString *) type {
259  // isAvailableForServiceType returns true if you pass it a type that is not
260  // in the defined constants, this is probably a bug on apples part
261  if(!([type isEqualToString:SLServiceTypeFacebook]
262       || [type isEqualToString:SLServiceTypeTwitter]
263       || [type isEqualToString:SLServiceTypeTencentWeibo]
264       || [type isEqualToString:SLServiceTypeSinaWeibo])) {
265    return false;
266  }
267  // wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed
268  @try {
269    return [SLComposeViewController isAvailableForServiceType:type];
270  }
271  @catch (NSException* exception) {
272    return false;
273  }
274}
275
276- (void)shareViaInternal:(CDVInvokedUrlCommand*)command
277                    type:(NSString *) type {
278
279  NSString *message   = [command.arguments objectAtIndex:0];
280  // subject is not supported by the SLComposeViewController
281  NSArray  *filenames = [command.arguments objectAtIndex:2];
282  NSString *urlString = [command.arguments objectAtIndex:3];
283
284  // boldly invoke the target app, because the phone will display a nice message asking to configure the app
285  SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type];
286  if (message != (id)[NSNull null]) {
287    [composeViewController setInitialText:message];
288  }
289
290  for (NSString* filename in filenames) {
291    UIImage* image = [self getImage:filename];
292    if (image != nil) {
293      [composeViewController addImage:image];
294    }
295  }
296
297  if (urlString != (id)[NSNull null]) {
298    [composeViewController addURL:[NSURL URLWithString:[urlString URLEncodedString]]];
299  }
300
301  [composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {
302    if (SLComposeViewControllerResultCancelled == result) {
303      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"];
304      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
305    } else if ([self isAvailableForSharing:command type:type]) {
306      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result];
307      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
308    } else {
309      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
310      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
311    }
312    // required for iOS6 (issues #162 and #167)
313    [self.viewController dismissViewControllerAnimated:YES completion:nil];
314  }];
315  [[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil];
316}
317
318- (void)shareViaEmail:(CDVInvokedUrlCommand*)command {
319  if ([self isEmailAvailable]) {
320
321    if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) {
322      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin"
323                                                      message:@"Sharing via email is not supported on the iOS 8 simulator."
324                                                     delegate:nil
325                                            cancelButtonTitle:@"OK"
326                                            otherButtonTitles:nil];
327      [alert show];
328      return;
329    }
330
331    [self cycleTheGlobalMailComposer];
332
333    self.globalMailComposer.mailComposeDelegate = self;
334
335    if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) {
336      NSString *message = [command.arguments objectAtIndex:0];
337      BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound;
338      [self.globalMailComposer setMessageBody:message isHTML:isHTML];
339    }
340
341    if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) {
342      [self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]];
343    }
344
345    if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) {
346      [self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]];
347    }
348
349    if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) {
350      [self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]];
351    }
352
353    if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) {
354      [self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]];
355    }
356
357    if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) {
358      NSArray* attachments = [command.arguments objectAtIndex:5];
359      NSFileManager* fileManager = [NSFileManager defaultManager];
360      for (NSString* path in attachments) {
361        NSURL *file = [self getFile:path];
362        NSData* data = [fileManager contentsAtPath:file.path];
363
364        if (!data) {
365          CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"invalid attachment"];
366          [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
367          return;
368        }
369
370        NSString* fileName;
371        NSString* mimeType;
372        NSString* basename = [self getBasenameFromAttachmentPath:path];
373
374          //Find data anywhere in string
375          NSRange rangeData = [basename rangeOfString:@"data:"];
376          if (rangeData.location == NSNotFound)
377          {
378              fileName = [basename pathComponents].lastObject;
379              mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]];
380          }
381          else
382          {
383              mimeType = (NSString*)[[[basename substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
384              
385              //Find df anywhere in string
386              NSRange rangeDF = [basename rangeOfString:@"df:"];
387              //If not found fallback to default name
388              if (rangeDF.location == NSNotFound) {
389                  fileName = @"attachment.";
390                  fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]];
391              } else {
392                  //Found, apply name
393                  fileName = (NSString*)[[[basename substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
394              }
395              
396              
397              NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject];
398              data = [SocialSharing dataFromBase64String:base64content];
399          }
400          [self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName];
401      }
402    }
403
404    // remember the command, because we need it in the didFinishWithResult method
405    _command = command;
406
407    [self.commandDelegate runInBackground:^{
408      [[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil];
409    }];
410
411  } else {
412    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
413    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
414  }
415}
416
417- (UIViewController*) getTopMostViewController {
418  UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
419  while (presentingViewController.presentedViewController != nil) {
420    presentingViewController = presentingViewController.presentedViewController;
421  }
422  return presentingViewController;
423}
424
425- (NSString*) getBasenameFromAttachmentPath:(NSString*)path {
426  if ([path hasPrefix:@"base64:"]) {
427    NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""];
428    return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location];
429  }
430  return [path componentsSeparatedByString: @"?"][0];
431}
432
433- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension {
434  if (!extension) {
435    return nil;
436  }
437  // Get the UTI from the file's extension
438  CFStringRef ext = (CFStringRef)CFBridgingRetain(extension);
439  CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL);
440  // Converting UTI to a mime type
441  NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType));
442  CFRelease(ext);
443  CFRelease(type);
444  return result;
445}
446
447/**
448 * Delegate will be called after the mail composer did finish an action
449 * to dismiss the view.
450 */
451- (void) mailComposeController:(MFMailComposeViewController*)controller
452           didFinishWithResult:(MFMailComposeResult)result
453                         error:(NSError*)error {
454  bool ok = result == MFMailComposeResultSent;
455  [self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}];
456  CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
457  [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
458}
459
460-(void)cycleTheGlobalMailComposer {
461  // we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976
462  self.globalMailComposer = nil;
463  self.globalMailComposer = [[MFMailComposeViewController alloc] init];
464}
465
466- (bool)canShareViaSMS {
467  Class messageClass = (NSClassFromString(@"MFMessageComposeViewController"));
468  return messageClass != nil && [messageClass canSendText];
469}
470
471- (void)shareViaSMS:(CDVInvokedUrlCommand*)command {
472  if ([self canShareViaSMS]) {
473    NSDictionary* options = [command.arguments objectAtIndex:0];
474    NSString *phonenumbers = [command.arguments objectAtIndex:1];
475    NSString *message = [options objectForKey:@"message"];
476    NSString *subject = [options objectForKey:@"subject"];
477    NSString *image = [options objectForKey:@"image"];
478
479    MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
480    picker.messageComposeDelegate = (id) self;
481    if (message != (id)[NSNull null]) {
482      picker.body = message;
483    }
484    if (subject != (id)[NSNull null]) {
485      [picker setSubject:subject];
486    }
487    if (image != nil && image != (id)[NSNull null]) {
488      BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)];
489      if (canSendAttachments) {
490        NSURL *file = [self getFile:image];
491        if (file != nil) {
492          [picker addAttachmentURL:file withAlternateFilename:nil];
493        }
494      }
495    }
496
497    if (phonenumbers != (id)[NSNull null]) {
498      [picker setRecipients:[phonenumbers componentsSeparatedByString:@","]];
499    }
500    // remember the command, because we need it in the didFinishWithResult method
501    _command = command;
502    [self.commandDelegate runInBackground:^{
503      picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
504      [[self getTopMostViewController] presentViewController:picker animated:NO completion:nil];
505    }];
506  } else {
507    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
508    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
509  }
510}
511
512// Dismisses the SMS composition interface when users taps Cancel or Send
513- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
514  bool ok = result == MessageComposeResultSent;
515  [[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil];
516  CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
517  [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
518}
519
520- (bool)canShareViaInstagram {
521  return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9
522}
523
524- (bool)canShareViaWhatsApp {
525  return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9
526}
527
528// this is only an internal test method for now, can be used to open a share sheet with 'Open in xx' links for tumblr, drive, dropbox, ..
529- (void)openImage:(NSString *)imageName {
530  UIImage* image =[self getImage:imageName];
531  if (image != nil) {
532    NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"];
533    [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
534    _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
535    _documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function
536    [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
537  }
538}
539
540- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command {
541
542  // on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted.
543  // If it's not, this method will ask permission to the user on iOS9 for opening the app,
544  // which is of course better than Instagram sharing not working at all because you forgot to whitelist it.
545  // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..)
546  if (!IsAtLeastiOSVersion(@"9.0")) {
547    if (![self canShareViaInstagram]) {
548      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
549      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
550      return;
551    }
552  }
553
554  NSString *message   = [command.arguments objectAtIndex:0];
555  // subject is not supported by the SLComposeViewController
556  NSArray  *filenames = [command.arguments objectAtIndex:2];
557
558  // only use the first image (for now.. maybe we can share in a loop?)
559  UIImage* image = nil;
560  for (NSString* filename in filenames) {
561    image = [self getImage:filename];
562    break;
563  }
564
565//  NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0];
566  NSString *tmpDir = NSTemporaryDirectory();
567  NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"];
568  [UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES];
569
570  _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
571  _documentInteractionController.delegate = self;
572  _documentInteractionController.UTI = @"com.instagram.exclusivegram";
573
574  if (message != (id)[NSNull null]) {
575    // no longer working, so ..
576    _documentInteractionController.annotation = @{@"InstagramCaption" : message};
577
578    // .. we put the message on the clipboard (you app can prompt the user to paste it)
579    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
580    [pasteboard setValue:message forPasteboardType:@"public.text"];
581  }
582
583  // remember the command for the delegate method
584  _command = command;
585
586  // test for #513
587  dispatch_async(dispatch_get_main_queue(), ^(void){
588    [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES];
589  });
590}
591
592- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command {
593
594  // on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted.
595  // If it's not, this method will ask permission to the user on iOS9 for opening the app,
596  // which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it.
597  // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..)
598  if (!IsAtLeastiOSVersion(@"9.0")) {
599    if (![self canShareViaWhatsApp]) {
600      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
601      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
602      return;
603    }
604  }
605
606  NSString *message   = [command.arguments objectAtIndex:0];
607  // subject is not supported by the SLComposeViewController
608  NSArray  *filenames = [command.arguments objectAtIndex:2];
609  NSString *urlString = [command.arguments objectAtIndex:3];
610  NSString *abid = [command.arguments objectAtIndex:4];
611
612  // only use the first image (for now.. maybe we can share in a loop?)
613  UIImage* image = nil;
614  for (NSString* filename in filenames) {
615    image = [self getImage:filename];
616    break;
617  }
618
619  // with WhatsApp, we can share an image OR text+url.. image wins if set
620  if (image != nil) {
621    NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"];
622    [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
623    _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
624    _documentInteractionController.UTI = @"net.whatsapp.image";
625    _documentInteractionController.delegate = self;
626    _command = command;
627    [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
628  } else {
629    // append an url to a message, if both are passed
630    NSString * shareString = @"";
631    if (message != (id)[NSNull null]) {
632      shareString = message;
633    }
634    if (urlString != (id)[NSNull null]) {
635      if ([shareString isEqual: @""]) {
636        shareString = urlString;
637      } else {
638        shareString = [NSString stringWithFormat:@"%@ %@", shareString, [urlString URLEncodedString]];
639      }
640    }
641    NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
642    // also encode the '=' character
643    encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
644    encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
645    NSString * abidString = @"";
646    if (abid != (id)[NSNull null]) {
647      abidString = [NSString stringWithFormat:@"abid=%@&", abid];
648    }
649    NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?%@text=%@", abidString, encodedShareString];
650
651    NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp];
652    [[UIApplication sharedApplication] openURL: whatsappURL];
653    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
654    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
655  }
656}
657
658- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command {
659  self.command = command;
660  NSArray *filenames = [command.arguments objectAtIndex:0];
661  [self.commandDelegate runInBackground:^{
662    bool shared = false;
663    for (NSString* filename in filenames) {
664      UIImage* image = [self getImage:filename];
665      if (image != nil) {
666        shared = true;
667        UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil);
668      }
669    }
670    if (!shared) {
671      CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"];
672      [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
673    }
674  }];
675}
676
677// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback)
678// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem
679- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo {
680  if (error) {
681    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription];
682    [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
683  } else {
684    CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
685    [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
686  }
687}
688
689-(UIImage*)getImage: (NSString *)imageName {
690  UIImage *image = nil;
691  if (imageName != (id)[NSNull null]) {
692    if ([imageName hasPrefix:@"http"]) {
693      image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]];
694    } else if ([imageName hasPrefix:@"www/"]) {
695      image = [UIImage imageNamed:imageName];
696    } else if ([imageName hasPrefix:@"file://"]) {
697      image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]];
698    } else if ([imageName hasPrefix:@"data:"]) {
699      // using a base64 encoded string
700      NSURL *imageURL = [NSURL URLWithString:imageName];
701      NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
702      image = [UIImage imageWithData:imageData];
703    } else if ([imageName hasPrefix:@"assets-library://"]) {
704      // use assets-library
705      NSURL *imageURL = [NSURL URLWithString:imageName];
706      NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
707      image = [UIImage imageWithData:imageData];
708    } else {
709      // assume anywhere else, on the local filesystem
710      image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]];
711    }
712  }
713  return image;
714}
715
716-(NSURL*)getFile: (NSString *)fileName {
717  NSURL *file = nil;
718  if (fileName != (id)[NSNull null]) {
719    NSRange rangeData = [fileName rangeOfString:@"data:"];
720    if ([fileName hasPrefix:@"http"]) {
721      NSURL *url = [NSURL URLWithString:fileName];
722      NSData *fileData = [NSData dataWithContentsOfURL:url];
723      NSString *name = (NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject];
724      file = [NSURL fileURLWithPath:[self storeInFile:[name componentsSeparatedByString: @"?"][0] fileData:fileData]];
725    } else if ([fileName hasPrefix:@"www/"]) {
726      NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
727      NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName];
728      file = [NSURL fileURLWithPath:fullPath];
729    } else if ([fileName hasPrefix:@"file://"]) {
730      // stripping the first 6 chars, because the path should start with / instead of file://
731      file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]];
732     } else if (rangeData.location != NSNotFound ){
733        //If found "data:"
734        NSString *fileType  = (NSString*)[[[fileName substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
735        
736        NSString* attachmentName;
737        //Find df anywhere in string
738        NSRange rangeDF = [fileName rangeOfString:@"df:"];
739        //If not found fallback to default name
740        if (rangeDF.location == NSNotFound) {
741            attachmentName = @"attachment.";
742            attachmentName = [attachmentName stringByAppendingString:(NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]];
743        } else {
744            //Found, apply name
745            attachmentName = (NSString*)[[[fileName substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
746        }
747        
748        
749        NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject];
750        NSData* data = [SocialSharing dataFromBase64String:base64content];
751        file = [NSURL fileURLWithPath:[self storeInFile:attachmentName fileData:data]];
752
753    } else {
754      // assume anywhere else, on the local filesystem
755      file = [NSURL fileURLWithPath:fileName];
756    }
757  }
758  return file;
759}
760
761-(NSString*) storeInFile: (NSString*) fileName
762                fileData: (NSData*) fileData {
763  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
764  NSString *documentsDirectory = [paths objectAtIndex:0];
765  NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
766  [fileData writeToFile:filePath atomically:YES];
767  _tempStoredFile = filePath;
768  return filePath;
769}
770
771- (void) cleanupStoredFiles {
772  if (_tempStoredFile != nil) {
773    NSError *error;
774    [[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error];
775  }
776}
777
778+ (NSData*) dataFromBase64String:(NSString*)aString {
779  return [[NSData alloc] initWithBase64EncodedString:aString options:0];
780}
781
782#pragma mark - UIPopoverControllerDelegate methods
783
784- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view {
785  NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","];
786  CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps];
787  rect->origin = newRect.origin;
788}
789
790- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
791  _popover = nil;
792}
793
794#pragma mark - UIDocumentInteractionControllerDelegate methods
795
796- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application {
797  // note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only)
798      NSLog(@"SocialSharing app selected: %@", application);
799}
800
801- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller {
802  if (self.command != nil) {
803    CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
804    [self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId];
805  }
806}
807
808@end