$from = "$fromDisplayName <$fromEmail>";
}
- //create the meta level record first ( email activity )
- $activityID = self::createEmailActivity($userID, $subject, $html, $text, $additionalDetails, $campaignId, $attachments, $caseId);
-
$returnProperties = [];
if (isset($messageToken['contact'])) {
foreach ($messageToken['contact'] as $key => $value) {
}
$sent = $notSent = [];
+ $attachmentFileIds = [];
+ $firstActivityCreated = FALSE;
foreach ($contactDetails as $values) {
$contactId = $values['contact_id'];
$emailAddress = $values['email'];
}
$sent = FALSE;
+ // To minimize storage requirements, only one copy of any file attachments uploaded to CiviCRM is kept,
+ // even when multiple contacts will receive separate emails from CiviCRM.
+ if (!empty($attachmentFileIds)) {
+ $attachments = array_merge_recursive($attachments, $attachmentFileIds);
+ }
+
+ // Create email activity.
+ $activityID = self::createEmailActivity($userID, $tokenSubject, $tokenHtml, $tokenText, $additionalDetails, $campaignId, $attachments, $caseId);
+
+ if ($firstActivityCreated == FALSE && !empty($attachments)) {
+ $attachmentFileIds = self::getAttachmentFileIds($activityID, $attachments);
+ $firstActivityCreated = TRUE;
+ }
+
if (self::sendMessage(
$from,
$userID,
return [$sent, $activityID];
}
+ /**
+ * Returns a array of attachment key with matching file ID.
+ *
+ * The function searches for all file Ids added for the activity and returns an array that
+ * uses the attachment key as the key and the file ID in the database for that matching attachment
+ * key by comparing the file URI for that attachment to the matching file URI fetched from the
+ * database. Having the file id matched per attachment key helps not to create a new file entry
+ * when a new activity with these attachments when the email activity is created.
+ *
+ * @param int $activityID
+ * Activity Id.
+ * @param array $attachments
+ * Attachments.
+ *
+ * @return array
+ * Array of attachment key versus file Id.
+ */
+ private static function getAttachmentFileIds($activityID, $attachments) {
+ $queryParams = [1 => [$activityID, 'Positive'], 2 => [CRM_Activity_DAO_Activity::getTableName(), 'String']];
+ $query = "SELECT file_id, uri FROM civicrm_entity_file INNER JOIN civicrm_file ON civicrm_entity_file.file_id = civicrm_file.id
+WHERE entity_id =%1 AND entity_table = %2";
+ $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
+
+ $fileDetails = [];
+ while ($dao->fetch()) {
+ $fileDetails[$dao->uri] = $dao->file_id;
+ }
+
+ $activityAttachments = [];
+ foreach ($attachments as $attachmentKey => $attachment) {
+ foreach ($fileDetails as $keyUri => $fileId) {
+ $path = explode('/', $attachment['uri']);
+ $filename = $path[count($path) - 1];
+ if ($filename == $keyUri) {
+ $activityAttachments[$attachmentKey]['id'] = $fileId;
+ }
+ }
+ }
+
+ return $activityAttachments;
+ }
+
/**
* Send SMS. Returns: bool $sent, int $activityId, int $success (number of sent SMS)
*
$mut->stop();
}
+ /**
+ * Checks that tokens are uniquely replaced for contacts.
+ */
+ public function testSendEmailWillReplaceTokensUniquelyForEachContact() {
+ $contactId1 = $this->individualCreate(['last_name' => 'Red']);
+ $contactId2 = $this->individualCreate(['last_name' => 'Pink']);
+
+ // create a logged in USER since the code references it for sendEmail user.
+ $this->createLoggedInUser();
+ $session = CRM_Core_Session::singleton();
+ $loggedInUser = $session->get('userID');
+ $contact = $this->callAPISuccess('Contact', 'get', ['sequential' => 1, 'id' => ['IN' => [$contactId1, $contactId2]]]);
+
+ // Create a campaign.
+ $result = $this->callAPISuccess('Campaign', 'create', [
+ 'version' => $this->_apiversion,
+ 'title' => __FUNCTION__ . ' campaign',
+ ]);
+ $campaign_id = $result['id'];
+
+ // Add contact tokens in subject, html , text.
+ $subject = __FUNCTION__ . ' subject' . '{contact.display_name}';
+ $html = __FUNCTION__ . ' html' . '{contact.display_name}';
+ $text = __FUNCTION__ . ' text' . '{contact.display_name}';
+ $userID = $loggedInUser;
+
+ CRM_Activity_BAO_Activity::sendEmail(
+ $contact['values'],
+ $subject,
+ $text,
+ $html,
+ $contact['values'][0]['email'],
+ $userID,
+ $from = __FUNCTION__ . '@example.com',
+ $attachments = NULL,
+ $cc = NULL,
+ $bcc = NULL,
+ $contactIds = array_column($contact['values'], 'id'),
+ $additionalDetails = NULL,
+ NULL,
+ $campaign_id
+ );
+ $result = $this->callAPISuccess('activity', 'get', ['campaign_id' => $campaign_id]);
+ // An activity created for each of the two contacts
+ $this->assertEquals(2, $result['count']);
+ $id = 0;
+ foreach ($result['values'] as $activity) {
+ $htmlValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $html);
+ $textValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $text);
+ $subjectValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $subject);
+ $details = "-ALTERNATIVE ITEM 0-
+$htmlValue
+-ALTERNATIVE ITEM 1-
+$textValue
+-ALTERNATIVE END-
+";
+ $this->assertEquals($activity['details'], $details, 'Activity details does not match.');
+ $this->assertEquals($activity['subject'], $subjectValue, 'Activity subject does not match.');
+ $id++;
+ }
+ }
+
+ /**
+ * Checks that attachments are not duplicated for activities.
+ */
+ public function testSendEmailDoesNotDuplicateAttachmentFileIdsForActivitiesCreated() {
+ $contactId1 = $this->individualCreate(['last_name' => 'Red']);
+ $contactId2 = $this->individualCreate(['last_name' => 'Pink']);
+
+ // create a logged in USER since the code references it for sendEmail user.
+ $this->createLoggedInUser();
+ $session = CRM_Core_Session::singleton();
+ $loggedInUser = $session->get('userID');
+ $contact = $this->callAPISuccess('Contact', 'get', ['sequential' => 1, 'id' => ['IN' => [$contactId1, $contactId2]]]);
+
+ // Create a campaign.
+ $result = $this->callAPISuccess('Campaign', 'create', [
+ 'version' => $this->_apiversion,
+ 'title' => __FUNCTION__ . ' campaign',
+ ]);
+ $campaign_id = $result['id'];
+
+ $subject = __FUNCTION__ . ' subject';
+ $html = __FUNCTION__ . ' html';
+ $text = __FUNCTION__ . ' text';
+ $userID = $loggedInUser;
+
+ $filepath = Civi::paths()->getPath('[civicrm.files]/custom');
+ $fileName = "test_email_create.txt";
+ $fileUri = "{$filepath}/{$fileName}";
+ // Create a file.
+ CRM_Utils_File::createFakeFile($filepath, 'Bananas do not bend themselves without a little help.', $fileName);
+ $attachments = [
+ 'attachFile_1' =>
+ [
+ 'uri' => $fileUri,
+ 'type' => 'text/plain',
+ 'location' => $fileUri,
+ ],
+ ];
+
+ CRM_Activity_BAO_Activity::sendEmail(
+ $contact['values'],
+ $subject,
+ $text,
+ $html,
+ $contact['values'][0]['email'],
+ $userID,
+ $from = __FUNCTION__ . '@example.com',
+ $attachments,
+ $cc = NULL,
+ $bcc = NULL,
+ $contactIds = array_column($contact['values'], 'id'),
+ $additionalDetails = NULL,
+ NULL,
+ $campaign_id
+ );
+ $result = $this->callAPISuccess('activity', 'get', ['campaign_id' => $campaign_id]);
+ // An activity created for each of the two contacts, i.e two activities.
+ $this->assertEquals(2, $result['count']);
+ $activityIds = array_column($result['values'], 'id');
+ $result = $this->callAPISuccess('Activity', 'get', [
+ 'return' => ['file_id'],
+ 'id' => ['IN' => $activityIds],
+ 'sequential' => 1,
+ ]);
+
+ // Verify that the that both activities are linked to the same File Id.
+ $this->assertEquals($result['values'][0]['file_id'], $result['values'][1]['file_id']);
+ }
+
/**
* Adds a case with one activity.
*