/**
  * @param $consoleCommand SyncCommandBase
  * @return \Closure
  */
 private static function generatePublisher($consoleCommand)
 {
     return function ($conversationsList) use($consoleCommand) {
         // Publish/create tickets
         // ----------------------
         $errorMapping = array();
         $consoleCommand->createProgressBar(count($conversationsList));
         /* @var $hybridConversation HybridConversation */
         foreach ($conversationsList as $hybridConversation) {
             /* @var $conversation Conversation */
             $conversation = $hybridConversation->getConversation();
             $grooveTicketNumber = $hybridConversation->getGrooveTicketNumber();
             try {
                 $client = $consoleCommand->getHelpScoutClient();
                 $createConversationResponse = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($client, $conversation) {
                     $client->createConversation($conversation, true);
                     // imported = true to prevent spam!
                 });
             } catch (ApiException $e) {
                 $createdBy = $conversation->getCreatedBy()->getEmail() ? $conversation->getCreatedBy()->getEmail() : "user #" . $conversation->getCreatedBy()->getId();
                 $consoleCommand->error("Failed to upload HelpScout conversation \"" . $conversation->getSubject() . "\" by " . $createdBy . " at " . $conversation->getCreatedAt() . " (Groove ticket #{$grooveTicketNumber}). Message was: \n" . APIHelper::formatApiExceptionArray($e));
                 if ($e->getErrors()) {
                     foreach ($e->getErrors() as $error) {
                         $errorMessage = 'Error: [' . $error['property'] . '] ' . $error['message'] . ' (value = ' . print_r($error['value'], TRUE) . ") (Groove ticket #{$grooveTicketNumber})";
                         $errorMapping[$error['message']][] = $errorMessage;
                         $consoleCommand->getProgressBar()->setMessage($errorMessage . str_pad(' ', 20));
                     }
                 } else {
                     $errorMapping[$e->getMessage()][] = "[" . $conversation->getCreatedAt() . "] " . $conversation->getSubject() . " (Groove ticket #{$grooveTicketNumber})";
                 }
             } catch (\CurlException $ce) {
                 $errorMessage = "CurlException encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")";
                 $consoleCommand->error($errorMessage . ": " . $ce->getMessage());
                 $errorMapping[$ce->getMessage()][] = $errorMessage;
             } catch (\ErrorException $errex) {
                 $errorMessage = "Exception encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")";
                 $consoleCommand->error($errorMessage . ": " . $errex->getMessage());
                 $errorMapping[$errex->getMessage()][] = $errorMessage;
             } catch (\Exception $ex) {
                 $errorMessage = "Exception encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")";
                 $consoleCommand->error($errorMessage . ": " . $ex->getMessage());
                 $errorMapping[$ex->getMessage()][] = $errorMessage;
             }
             $consoleCommand->getProgressBar()->advance();
         }
         if ($consoleCommand->getProgressBar()->getMaxSteps() === 0) {
             $consoleCommand->getProgressBar()->clear();
         } else {
             $consoleCommand->getProgressBar()->finish();
         }
         if (sizeof($errorMapping) > 0) {
             $filename = 'sync-tickets-' . date('YmdHis');
             $contents = APIHelper::convertErrorMappingArrayToCSVArray($errorMapping);
             APIHelper::exportArrayToCSV($filename, $contents);
             $consoleCommand->warn("\nEncountered " . count($contents) . " errors, which have been exported to {$filename}.csv (default location: storage/exports)");
         }
     };
 }
 public function __construct()
 {
     parent::__construct();
     $this->grooveClient = new GrooveClient(config('services.groove.key'));
     $this->helpscoutClient = ApiClient::getInstance();
     try {
         $this->helpscoutClient->setKey(config('services.helpscout.key'));
     } catch (ApiException $e) {
         $this->error("There was an error creating the HelpScout client. Message was: " . APIHelper::formatApiExceptionArray($e));
         return;
     }
     self::$rate_limits[GROOVE] = config('services.groove.ratelimit');
     self::$rate_limits[HELPSCOUT] = config('services.helpscout.ratelimit');
 }
 /**
  * @param $consoleCommand SyncCommandBase
  * @return \Closure
  */
 private static function generatePublisher($consoleCommand)
 {
     return function ($customersList) use($consoleCommand) {
         // Publish/create customers
         // ------------------------
         $errorMapping = array();
         $consoleCommand->createProgressBar(count($customersList));
         /* @var $customer \HelpScout\model\Customer */
         foreach ($customersList as $customer) {
             try {
                 $client = $consoleCommand->getHelpScoutClient();
                 $helpscoutCreateCustomerResponse = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($client, $customer) {
                     $client->createCustomer($customer);
                 });
             } catch (ApiException $e) {
                 //                    $customerEmails = array_map(function($emailEntry) {
                 //                        /* @var $emailEntry \HelpScout\model\customer\EmailEntry */
                 //                        return $emailEntry->getValue();
                 //                    }, $customer->getEmails());
                 //                    $consoleCommand->error("Failed to upload HelpScout customer (" . implode(',', $customerEmails) . ")" . ". Message was: " . APIHelper::formatApiExceptionArray($e));
                 foreach ($e->getErrors() as $error) {
                     $errorMapping[$error['message']][] = "[" . $error['property'] . "] " . $error['message'] . ": " . $error['value'];
                     $consoleCommand->getProgressBar()->setMessage('Error: [' . $error['property'] . '] ' . $error['message'] . ' (' . $error['value'] . ')' . str_pad(' ', 20));
                 }
             }
             $consoleCommand->getProgressBar()->advance();
         }
         if ($consoleCommand->getProgressBar()->getMaxSteps() === 0) {
             $consoleCommand->getProgressBar()->clear();
         } else {
             $consoleCommand->getProgressBar()->finish();
         }
         if (sizeof($errorMapping) > 0) {
             $filename = 'sync-customers-' . date('YmdHis');
             $contents = APIHelper::convertErrorMappingArrayToCSVArray($errorMapping);
             APIHelper::exportArrayToCSV($filename, $contents);
             $consoleCommand->warn("\nEncountered " . count($contents) . " errors, which have been exported to {$filename}.csv (default location: storage/exports)");
         }
     };
 }
 private function performInitialValidation()
 {
     $mailboxesService = $this->getGrooveClient()->mailboxes();
     $agentsService = $this->getGrooveClient()->agents();
     // Validation check: Ensure each mailbox in Groove maps to a HelpScout mailbox
     $this->info("Validation check: ensuring each mailbox in Groove maps to a HelpScout mailbox");
     $grooveMailboxes = $this->makeRateLimitedRequest(GROOVE, function () use($mailboxesService) {
         return $mailboxesService->mailboxes()['mailboxes'];
     });
     $hasErrors = false;
     foreach ($grooveMailboxes as $grooveMailbox) {
         $grooveMailboxName = $grooveMailbox['name'];
         if (!($helpscoutMailbox = APIHelper::findMatchingMailboxByName($grooveMailboxName))) {
             $this->error('Missing corresponding HelpScout mailbox named: ' . $grooveMailboxName);
             $hasErrors = true;
         } else {
             $this->info("[ OK ] " . $grooveMailboxName . " mapped to " . $helpscoutMailbox->getEmail() . " (" . $helpscoutMailbox->getId() . ")");
         }
     }
     // Validation check: Ensure each agent has a corresponding user in HelpScout
     $this->info("\nValidation check: ensuring each Groove agent maps to a corresponding HelpScout user");
     $grooveAgents = $this->makeRateLimitedRequest(GROOVE, function () use($agentsService) {
         return $agentsService->list()['agents'];
     });
     foreach ($grooveAgents as $grooveAgent) {
         $grooveAgentEmail = $grooveAgent['email'];
         if (!($helpscoutUser = APIHelper::findMatchingUserWithEmail($grooveAgentEmail))) {
             $this->error('Missing corresponding HelpScout user for email: ' . $grooveAgentEmail);
             $hasErrors = true;
         } else {
             $this->info("[ OK ] " . $grooveAgentEmail . " mapped to HelpScout user " . $helpscoutUser->getFullName() . " (" . $helpscoutUser->getId() . ")");
         }
     }
     if ($hasErrors) {
         $this->error("\nValidation failed. Please correct the above errors, otherwise we cannot proceed.");
         exit;
     }
     $this->info("\nValidation passed.");
 }
 /**
  * @param $consoleCommand SyncCommandBase
  * @return Closure
  */
 private static function generateProcessor($consoleCommand)
 {
     return function ($customersList) use($consoleCommand) {
         $processedCustomers = array();
         foreach ($customersList as $grooveCustomer) {
             // Groove: email, name, about, twitter_username, title, company_name, phone_number, location, website_url, linkedin_username
             // HelpScout Customer (subset of Person): firstName, lastName, photoUrl, photoType, gender, age, organization, jobTitle, location, createdAt, modifiedAt
             // HelpScout Person: id, firstName, lastName, email, phone, type (user, customer, team)
             $grooveCustomerEmail = $grooveCustomer['email'];
             try {
                 // TODO: create hybrid model that contains both the Groove and Customer objects
                 $customer = new Customer();
                 // Groove doesn't separate these fields
                 /* @var $fullName string */
                 $fullName = $grooveCustomer['name'];
                 list($firstName, $lastName) = APIHelper::extractFirstAndLastNameFromFullName($fullName, $consoleCommand);
                 $customer->setFirstName($firstName);
                 $customer->setLastName($lastName);
                 // Organization must be 60 characters or less
                 $customerOrganization = $grooveCustomer['company_name'];
                 if ($customerOrganization && strlen($customerOrganization) > 60) {
                     $consoleCommand->warn("Warning: Customer organization \"{$customerOrganization}\" must be 60 characters or less. Truncating. (email: {$grooveCustomerEmail})");
                     $customerOrganization = substr($customerOrganization, 0, 60);
                 }
                 $customer->setOrganization($customerOrganization);
                 // Job title must be 60 characters or less
                 $customerJobTitle = $grooveCustomer['title'];
                 if ($customerJobTitle && strlen($customerJobTitle) > 60) {
                     $consoleCommand->warn("Warning: Customer job title \"{$customerJobTitle}\" must be 60 characters or less. Truncating. (email: {$grooveCustomerEmail})");
                     $customerJobTitle = substr($customerJobTitle, 0, 60);
                 }
                 $customer->setJobTitle($customerJobTitle);
                 $customer->setLocation($grooveCustomer['location']);
                 $customer->setBackground($grooveCustomer['about']);
                 // Groove doesn't have addresses
                 if ($grooveCustomer['phone_number'] != null) {
                     $phonenumber = new PhoneEntry();
                     $phonenumber->setValue($grooveCustomer['phone_number']);
                     $phonenumber->setLocation("home");
                     $customer->setPhones(array($phonenumber));
                 }
                 // Emails: at least one email is required
                 // Groove only supports one email address, which means the email field could contain multiple emails
                 $emailAddresses = array();
                 $splitEmails = preg_split("/( |;|,)/", $grooveCustomer['email']);
                 // test to make sure all email addresses are valid
                 if (sizeof($splitEmails) == 1) {
                     $emailEntry = new EmailEntry();
                     $emailEntry->setValue($grooveCustomer['email']);
                     $emailEntry->setLocation("primary");
                     array_push($emailAddresses, $emailEntry);
                 } else {
                     // Test to make sure every email address is valid
                     $first = true;
                     foreach ($splitEmails as $addressToTest) {
                         if (strlen(trim($addressToTest)) === 0) {
                             continue;
                         }
                         if (!filter_var($addressToTest, FILTER_VALIDATE_EMAIL)) {
                             // breaking up the address resulted in invalid emails; use the original address
                             $emailAddresses = array();
                             $emailEntry = new EmailEntry();
                             $emailEntry->setValue($grooveCustomer['email']);
                             $emailEntry->setLocation("primary");
                             array_push($emailAddresses, $emailEntry);
                             break;
                         } else {
                             $emailEntry = new EmailEntry();
                             $emailEntry->setValue($addressToTest);
                             if ($first) {
                                 $emailEntry->setLocation("primary");
                                 $first = false;
                             } else {
                                 $emailEntry->setLocation("other");
                             }
                             array_push($emailAddresses, $emailEntry);
                         }
                     }
                 }
                 $customer->setEmails($emailAddresses);
                 // Social Profiles (Groove supports Twitter and LinkedIn)
                 $socialProfiles = array();
                 if ($grooveCustomer['twitter_username'] != null) {
                     $twitter = new SocialProfileEntry();
                     $twitter->setValue($grooveCustomer['twitter_username']);
                     $twitter->setType("twitter");
                     $socialProfiles[] = $twitter;
                 }
                 if ($grooveCustomer['linkedin_username'] != null) {
                     $linkedin = new SocialProfileEntry();
                     $linkedin->setValue($grooveCustomer['linkedin_username']);
                     $linkedin->setType("linkedin");
                     $socialProfiles[] = $linkedin;
                 }
                 $customer->setSocialProfiles($socialProfiles);
                 // Groove doesn't have chats
                 if ($grooveCustomer['website_url'] != null) {
                     $website = new WebsiteEntry();
                     $website->setValue($grooveCustomer['website_url']);
                     $customer->setWebsites(array($website));
                 }
                 $processedCustomers[] = $customer;
             } catch (ApiException $e) {
                 $grooveCustomerName = $grooveCustomer['name'];
                 $consoleCommand->error("Failed to create HelpScout customer for Groove customer \"{$grooveCustomerName}\" ({$grooveCustomerEmail}). Message was: " . APIHelper::formatApiExceptionArray($e));
             }
         }
         return $processedCustomers;
     };
 }
 /**
  * Create a new command instance.
  *
  * @return void
  */
 public function __construct()
 {
     parent::__construct();
     APIHelper::setConsoleCommand($this);
     date_default_timezone_set('America/Toronto');
 }
 /**
  * @param $consoleCommand SyncCommandBase
  * @return \Closure
  */
 private static function generateProcessor($consoleCommand)
 {
     return function ($ticketsList) use($consoleCommand) {
         $processedTickets = array();
         $checkForDuplicates = $consoleCommand->option('checkDuplicates');
         foreach ($ticketsList as $grooveTicket) {
             $customerEmail = null;
             $grooveTicketNumber = $grooveTicket['number'];
             try {
                 if ($checkForDuplicates) {
                     /* @var $searchResults Collection */
                     $dateString = $grooveTicket['created_at'];
                     $searchResults = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($consoleCommand, $dateString) {
                         return $consoleCommand->getHelpScoutClient()->conversationSearch("(modifiedAt:[{$dateString} TO {$dateString}])");
                     });
                     if ($searchResults->getCount() > 1) {
                         $helpscoutConversationNumber = null;
                         /* @var $searchConversation SearchConversation */
                         foreach ($searchResults->getItems() as $searchConversation) {
                             if (strcasecmp($searchConversation->getSubject(), $grooveTicket['title']) === 0) {
                                 $helpscoutConversationNumber = $searchConversation->getNumber();
                                 break;
                             }
                         }
                         if ($helpscoutConversationNumber) {
                             $consoleCommand->warn("Warning: Duplicate ticket #{$grooveTicketNumber} \"" . $grooveTicket['title'] . "\" on {$dateString} already uploaded to HelpScout (conversation #{$helpscoutConversationNumber}). Skipping.");
                             continue;
                         }
                     }
                 }
                 $hybridConversation = new HybridConversation();
                 $hybridConversation->setGrooveTicketNumber($grooveTicketNumber);
                 $conversation = new Conversation();
                 $conversation->setType('email');
                 $conversation->setSubject($grooveTicket['title']);
                 // mailbox
                 $mailboxName = $grooveTicket['mailbox'];
                 $assignedMailbox = APIHelper::findMatchingMailboxByName($mailboxName);
                 if (!$assignedMailbox) {
                     $mailboxRef = APIHelper::findMatchingMailboxByEmail(config('services.helpscout.default_mailbox'))->toRef();
                 } else {
                     $mailboxRef = $assignedMailbox->toRef();
                 }
                 $conversation->setMailbox($mailboxRef);
                 if (!$conversation->getMailbox()) {
                     $exception = new ApiException("Mailbox not found in HelpScout: " . $mailboxName);
                     $exception->setErrors(array(['property' => 'mailbox', 'message' => 'Mailbox not found', 'value' => $mailboxName]));
                     throw $exception;
                 }
                 $tags = $grooveTicket['tags'];
                 if ($tags && count($tags) > 0) {
                     $conversation->setTags($tags);
                 }
                 // CustomerRef
                 $matches = array();
                 if (isset($grooveTicket['links']['customer']) && preg_match('@^https://api.groovehq.com/v1/customers/(.*)@i', $grooveTicket['links']['customer']['href'], $matches) === 1) {
                     $customerEmail = $matches[1];
                     if (filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) {
                         $helpscoutPersonRef = new PersonRef((object) array('email' => $customerEmail, 'type' => 'customer'));
                         $conversation->setCustomer($helpscoutPersonRef);
                         $conversation->setCreatedBy($helpscoutPersonRef);
                     } else {
                         $grooveCustomer = $consoleCommand->makeRateLimitedRequest(GROOVE, function () use($consoleCommand, $customerEmail) {
                             return $consoleCommand->getGrooveClient()->customers()->find(['customer_email' => $customerEmail])['customer'];
                         });
                         $helpscoutPersonRef = new PersonRef((object) array('email' => $grooveCustomer['email'], 'type' => 'customer'));
                         list($firstName, $lastName) = APIHelper::extractFirstAndLastNameFromFullName($grooveCustomer['name'], $consoleCommand);
                         $helpscoutPersonRef->setFirstName($firstName);
                         $helpscoutPersonRef->setLastName($lastName);
                         $conversation->setCustomer($helpscoutPersonRef);
                         $conversation->setCreatedBy($helpscoutPersonRef);
                     }
                 } else {
                     throw new ApiException("No customer defined for ticket: #{$grooveTicketNumber}");
                 }
                 // CreatedAt
                 $datetime = new DateTime($grooveTicket['created_at']);
                 $conversation->setCreatedAt($datetime->format('c'));
                 $conversation->setThreads(self::retrieveThreadsForGrooveTicket($consoleCommand, $grooveTicket));
                 $status = APIHelper::getHelpScoutStatusForGrooveState($grooveTicket['state']);
                 if ($status) {
                     $conversation->setStatus($status);
                 } else {
                     $consoleCommand->error("Unknown state provided for Groove ticket #{$grooveTicketNumber}: " . $grooveTicket['state']);
                 }
                 $hybridConversation->setConversation($conversation);
                 $processedTickets[] = $hybridConversation;
             } catch (ApiException $e) {
                 $consoleCommand->error("Failed to create HelpScout conversation for Groove ticket (#{$grooveTicketNumber} created by {$customerEmail} at " . $grooveTicket['created_at'] . "). Message was: \n" . APIHelper::formatApiExceptionArray($e));
             } catch (\CurlException $ce) {
                 $errorMessage = "CurlException encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\"";
                 $consoleCommand->error($errorMessage . ": " . $ce->getMessage());
             } catch (\ErrorException $errex) {
                 $errorMessage = "Error encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\"";
                 $consoleCommand->error($errorMessage . ": " . $errex->getMessage());
             } catch (\Exception $ex) {
                 $errorMessage = "Exception encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\"";
                 $consoleCommand->error($errorMessage . ": " . $ex->getMessage());
             }
         }
         return $processedTickets;
     };
 }