3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 require_once 'Mail/mime.php';
21 * Class CRM_Mailing_Event_BAO_Unsubscribe
23 class CRM_Mailing_Event_BAO_Unsubscribe
extends CRM_Mailing_Event_DAO_Unsubscribe
{
28 public function __construct() {
29 parent
::__construct();
33 * Unsubscribe a contact from the domain.
37 * @param int $queue_id
38 * The Queue Event ID of the recipient.
43 * Was the contact successfully unsubscribed?
45 public static function unsub_from_domain($job_id, $queue_id, $hash) {
46 $q = CRM_Mailing_Event_BAO_Queue
::verify($job_id, $queue_id, $hash);
51 $transaction = new CRM_Core_Transaction();
53 $now = date('YmdHis');
54 if (CRM_Core_BAO_Email
::isMultipleBulkMail()) {
55 $email = new CRM_Core_BAO_Email();
56 $email->id
= $q->email_id
;
57 if ($email->find(TRUE)) {
65 1 => [$now, 'Timestamp'],
66 2 => [$email->email
, 'String'],
68 CRM_Core_DAO
::executeQuery($sql, $sqlParams);
72 $contact = new CRM_Contact_BAO_Contact();
73 $contact->id
= $q->contact_id
;
74 $contact->is_opt_out
= TRUE;
78 $ue = new CRM_Mailing_Event_BAO_Unsubscribe();
79 $ue->event_queue_id
= $queue_id;
80 $ue->org_unsubscribe
= 1;
81 $ue->time_stamp
= $now;
85 'contact_id' => $q->contact_id
,
87 'status' => 'Removed',
89 'tracking' => $ue->id
,
91 CRM_Contact_BAO_SubscriptionHistory
::create($shParams);
93 $transaction->commit();
99 * Unsubscribe a contact from all groups that received this mailing.
103 * @param int $queue_id
104 * The Queue Event ID of the recipient.
105 * @param string $hash
107 * @param bool $return
108 * If true return the list of groups.
111 * $groups Array of all groups from which the contact was removed, or null if the queue event could not be found.
113 * @throws \CiviCRM_API3_Exception
115 public static function &unsub_from_mailing($job_id, $queue_id, $hash, $return = FALSE) {
116 // First make sure there's a matching queue event.
118 $q = CRM_Mailing_Event_BAO_Queue
::verify($job_id, $queue_id, $hash);
124 $contact_id = $q->contact_id
;
125 $transaction = new CRM_Core_Transaction();
127 $do = new CRM_Core_DAO();
128 $mgObject = new CRM_Mailing_DAO_MailingGroup();
129 $mg = $mgObject->getTableName();
130 $jobObject = new CRM_Mailing_BAO_MailingJob();
131 $job = $jobObject->getTableName();
132 $mailingObject = new CRM_Mailing_BAO_Mailing();
133 $mailing = $mailingObject->getTableName();
134 $groupObject = new CRM_Contact_BAO_Group();
135 $group = $groupObject->getTableName();
136 $gcObject = new CRM_Contact_BAO_GroupContact();
137 $gc = $gcObject->getTableName();
138 $abObject = new CRM_Mailing_DAO_MailingAB();
139 $ab = $abObject->getTableName();
141 $mailing_id = civicrm_api3('MailingJob', 'getvalue', ['id' => $job_id, 'return' => 'mailing_id']);
142 $mailing_type = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_Mailing', $mailing_id, 'mailing_type', 'id');
143 $entity = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_MailingGroup', $mailing_id, 'entity_table', 'mailing_id');
145 // If $entity is null and $mailing_Type is either winner or experiment then we are deailing with an AB test
146 $abtest_types = ['experiment', 'winner'];
147 if (empty($entity) && in_array($mailing_type, $abtest_types)) {
148 $mailing_id_a = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_MailingAB', $mailing_id, 'mailing_id_a', 'mailing_id_b');
149 $field = 'mailing_id_b';
150 if (empty($mailing_id_a)) {
151 $mailing_id_a = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_MailingAB', $mailing_id, 'mailing_id_a', 'mailing_id_c');
152 $field = 'mailing_id_c';
154 $jobJoin = "INNER JOIN $ab ON $ab.mailing_id_a = $mg.mailing_id
155 INNER JOIN $job ON $job.mailing_id = $ab.$field";
156 $entity = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_MailingGroup', $mailing_id_a, 'entity_table', 'mailing_id');
159 $jobJoin = "INNER JOIN $job ON $job.mailing_id = $mg.mailing_id";
163 if ($entity == $group) {
164 $groupClause = "AND $group.is_hidden = 0";
168 SELECT $mg.entity_table as entity_table,
169 $mg.entity_id as entity_id,
170 $mg.group_type as group_type
174 ON $mg.entity_id = $entity.id
175 WHERE $job.id = " . CRM_Utils_Type
::escape($job_id, 'Integer') . "
176 AND $mg.group_type IN ('Include', 'Base') $groupClause"
179 // Make a list of groups and a list of prior mailings that received
186 while ($do->fetch()) {
187 if ($do->entity_table
== $group) {
188 if ($do->group_type
== 'Base') {
189 $base_groups[$do->entity_id
] = NULL;
192 $groups[$do->entity_id
] = NULL;
195 elseif ($do->entity_table
== $mailing) {
196 $mailings[] = $do->entity_id
;
200 // As long as we have prior mailings, find their groups and add to the
203 while (!empty($mailings)) {
204 $do = CRM_Core_DAO
::executeQuery("
205 SELECT $mg.entity_table as entity_table,
206 $mg.entity_id as entity_id
207 FROM civicrm_mailing_group $mg
208 WHERE $mg.mailing_id IN (" . implode(', ', $mailings) . ")
209 AND $mg.group_type = 'Include'");
213 while ($do->fetch()) {
214 if ($do->entity_table
== $group) {
215 $groups[$do->entity_id
] = TRUE;
217 elseif ($do->entity_table
== $mailing) {
218 $mailings[] = $do->entity_id
;
223 //Pass the groups to be unsubscribed from through a hook.
224 $groupIds = array_keys($groups);
225 //include child groups if any
226 $groupIds = array_merge($groupIds, CRM_Contact_BAO_Group
::getChildGroupIds($groupIds));
228 $baseGroupIds = array_keys($base_groups);
229 CRM_Utils_Hook
::unsubscribeGroups('unsubscribe', $mailing_id, $contact_id, $groupIds, $baseGroupIds);
231 // Now we have a complete list of recipient groups. Filter out all
232 // those except smart groups, those that the contact belongs to and
233 // base groups from search based mailings.
234 $baseGroupClause = '';
235 if (!empty($baseGroupIds)) {
236 $baseGroupClause = "OR $group.id IN(" . implode(', ', $baseGroupIds) . ")";
239 if ($groupIds ||
$baseGroupIds) {
240 $groupIdClause = "AND $group.id IN (" . implode(', ', array_merge($groupIds, $baseGroupIds)) . ")";
242 $do = CRM_Core_DAO
::executeQuery("
243 SELECT $group.id as group_id,
244 $group.title as title,
245 $group.description as description
246 FROM civicrm_group $group
247 LEFT JOIN civicrm_group_contact $gc
248 ON $gc.group_id = $group.id
249 WHERE $group.is_hidden = 0
251 AND ($group.saved_search_id is not null
252 OR ($gc.contact_id = $contact_id
253 AND $gc.status = 'Added')
259 while ($do->fetch()) {
260 $returnGroups[$do->group_id
] = [
261 'title' => $do->title
,
262 'description' => $do->description
,
265 return $returnGroups;
268 while ($do->fetch()) {
269 $groups[$do->group_id
] = $do->title
;
273 $contacts = [$contact_id];
274 foreach ($groups as $group_id => $group_name) {
277 if (in_array($group_id, $baseGroupIds)) {
278 list($total, $removed, $notremoved) = CRM_Contact_BAO_GroupContact
::addContactsToGroup($contacts, $group_id, 'Email', 'Removed');
281 list($total, $removed, $notremoved) = CRM_Contact_BAO_GroupContact
::removeContactsFromGroup($contacts, $group_id, 'Email');
285 unset($groups[$group_id]);
289 $ue = new CRM_Mailing_Event_BAO_Unsubscribe();
290 $ue->event_queue_id
= $queue_id;
291 $ue->org_unsubscribe
= 0;
292 $ue->time_stamp
= date('YmdHis');
295 $transaction->commit();
300 * Send a response email informing the contact of the groups from which he.
301 * has been unsubscribed.
303 * @param string $queue_id
304 * The queue event ID.
305 * @param array $groups
307 * @param bool $is_domain
308 * Is this domain-level?.
312 public static function send_unsub_response($queue_id, $groups, $is_domain = FALSE, $job) {
313 $config = CRM_Core_Config
::singleton();
314 $domain = CRM_Core_BAO_Domain
::getDomain();
316 $jobObject = new CRM_Mailing_BAO_MailingJob();
317 $jobTable = $jobObject->getTableName();
318 $mailingObject = new CRM_Mailing_DAO_Mailing();
319 $mailingTable = $mailingObject->getTableName();
320 $contactsObject = new CRM_Contact_DAO_Contact();
321 $contacts = $contactsObject->getTableName();
322 $emailObject = new CRM_Core_DAO_Email();
323 $email = $emailObject->getTableName();
324 $queueObject = new CRM_Mailing_Event_BAO_Queue();
325 $queue = $queueObject->getTableName();
327 //get the default domain email address.
328 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain
::getNameAndEmail();
330 $dao = new CRM_Mailing_BAO_Mailing();
331 $dao->query(" SELECT * FROM $mailingTable
332 INNER JOIN $jobTable ON
333 $jobTable.mailing_id = $mailingTable.id
334 WHERE $jobTable.id = $job");
337 $component = new CRM_Mailing_BAO_MailingComponent();
340 $component->id
= $dao->optout_id
;
343 $component->id
= $dao->unsubscribe_id
;
345 $component->find(TRUE);
347 $html = $component->body_html
;
348 if ($component->body_text
) {
349 $text = $component->body_text
;
352 $text = CRM_Utils_String
::htmlToText($component->body_html
);
355 $eq = new CRM_Core_DAO();
357 "SELECT $contacts.preferred_mail_format as format,
358 $contacts.id as contact_id,
359 $email.email as email,
362 INNER JOIN $queue ON $queue.contact_id = $contacts.id
363 INNER JOIN $email ON $queue.email_id = $email.id
364 WHERE $queue.id = " . CRM_Utils_Type
::escape($queue_id, 'Integer')
369 foreach ($groups as $key => $value) {
371 unset($groups[$key]);
376 $message = new Mail_mime("\n");
378 list($addresses, $urls) = CRM_Mailing_BAO_Mailing
::getVerpAndUrls($job, $queue_id, $eq->hash
, $eq->email
);
379 $bao = new CRM_Mailing_BAO_Mailing();
380 $bao->body_text
= $text;
381 $bao->body_html
= $html;
382 $tokens = $bao->getTokens();
383 if ($eq->format
== 'HTML' ||
$eq->format
== 'Both') {
384 $html = CRM_Utils_Token
::replaceDomainTokens($html, $domain, TRUE, $tokens['html']);
385 $html = CRM_Utils_Token
::replaceUnsubscribeTokens($html, $domain, $groups, TRUE, $eq->contact_id
, $eq->hash
);
386 $html = CRM_Utils_Token
::replaceActionTokens($html, $addresses, $urls, TRUE, $tokens['html']);
387 $html = CRM_Utils_Token
::replaceMailingTokens($html, $dao, NULL, $tokens['html']);
388 $message->setHTMLBody($html);
390 if (!$html ||
$eq->format
== 'Text' ||
$eq->format
== 'Both') {
391 $text = CRM_Utils_Token
::replaceDomainTokens($text, $domain, FALSE, $tokens['text']);
392 $text = CRM_Utils_Token
::replaceUnsubscribeTokens($text, $domain, $groups, FALSE, $eq->contact_id
, $eq->hash
);
393 $text = CRM_Utils_Token
::replaceActionTokens($text, $addresses, $urls, FALSE, $tokens['text']);
394 $text = CRM_Utils_Token
::replaceMailingTokens($text, $dao, NULL, $tokens['text']);
395 $message->setTxtBody($text);
398 $emailDomain = CRM_Core_BAO_MailSettings
::defaultDomain();
401 'Subject' => $component->subject
,
402 'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain
::getNoReplyEmailAddress() . '>',
404 'Reply-To' => CRM_Core_BAO_Domain
::getNoReplyEmailAddress(),
405 'Return-Path' => CRM_Core_BAO_Domain
::getNoReplyEmailAddress(),
407 CRM_Mailing_BAO_Mailing
::addMessageIdHeader($headers, 'u', $job, $queue_id, $eq->hash
);
409 $b = CRM_Utils_Mail
::setMimeParams($message);
410 $h = $message->headers($headers);
412 $mailer = \Civi
::service('pear_mail');
414 if (is_object($mailer)) {
415 $errorScope = CRM_Core_TemporaryErrorScope
::ignoreException();
416 $mailer->send($eq->email
, $h, $b);
422 * Get row count for the event selector.
424 * @param int $mailing_id
427 * Optional ID of a job to filter on.
428 * @param bool $is_distinct
429 * Group by queue ID?.
431 * @param string $org_unsubscribe
433 * @param string $toDate
436 * Number of rows in result set
438 public static function getTotalCount(
439 $mailing_id, $job_id = NULL,
440 $is_distinct = FALSE, $org_unsubscribe = NULL, $toDate = NULL
442 $dao = new CRM_Core_DAO();
444 $unsub = self
::$_tableName;
445 $queueObject = new CRM_Mailing_Event_BAO_Queue();
446 $queue = $queueObject->getTableName();
447 $mailingObject = new CRM_Mailing_BAO_Mailing();
448 $mailing = $mailingObject->getTableName();
449 $jobObject = new CRM_Mailing_BAO_MailingJob();
450 $job = $jobObject->getTableName();
453 SELECT COUNT($unsub.id) as unsubs
456 ON $unsub.event_queue_id = $queue.id
458 ON $queue.job_id = $job.id
460 ON $job.mailing_id = $mailing.id
462 WHERE $mailing.id = " . CRM_Utils_Type
::escape($mailing_id, 'Integer');
464 if (!empty($toDate)) {
465 $query .= " AND $unsub.time_stamp <= $toDate";
468 if (!empty($job_id)) {
469 $query .= " AND $job.id = " . CRM_Utils_Type
::escape($job_id, 'Integer');
472 if ($org_unsubscribe !== NULL) {
473 $query .= " AND $unsub.org_unsubscribe = " . ($org_unsubscribe ?
0 : 1);
477 $query .= " GROUP BY $queue.id ";
486 return $dao->unsubs ?
$dao->unsubs
: 0;
491 * Get rows for the event browser.
493 * @param int $mailing_id
496 * Optional ID of the job.
497 * @param bool $is_distinct
498 * Group by queue id?.
501 * @param int $rowCount
506 * @param null $org_unsubscribe
510 public static function &getRows(
511 $mailing_id, $job_id = NULL,
512 $is_distinct = FALSE, $offset = NULL, $rowCount = NULL, $sort = NULL,
513 $org_unsubscribe = NULL
516 $dao = new CRM_Core_DAO();
518 $unsub = self
::$_tableName;
519 $queueObject = new CRM_Mailing_Event_BAO_Queue();
520 $queue = $queueObject->getTableName();
521 $mailingObject = new CRM_Mailing_BAO_Mailing();
522 $mailing = $mailingObject->getTableName();
523 $jobObject = new CRM_Mailing_BAO_MailingJob();
524 $job = $jobObject->getTableName();
525 $contactObject = new CRM_Contact_BAO_Contact();
526 $contact = $contactObject->getTableName();
527 $emailObject = new CRM_Core_BAO_Email();
528 $email = $emailObject->getTableName();
531 SELECT $contact.display_name as display_name,
532 $contact.id as contact_id,
533 $email.email as email,
534 $unsub.time_stamp as date,
535 $unsub.org_unsubscribe as org_unsubscribe
538 ON $queue.contact_id = $contact.id
540 ON $queue.email_id = $email.id
542 ON $unsub.event_queue_id = $queue.id
544 ON $queue.job_id = $job.id
546 ON $job.mailing_id = $mailing.id
548 WHERE $mailing.id = " . CRM_Utils_Type
::escape($mailing_id, 'Integer');
550 if (!empty($job_id)) {
551 $query .= " AND $job.id = " . CRM_Utils_Type
::escape($job_id, 'Integer');
554 if ($org_unsubscribe !== NULL) {
555 $query .= " AND $unsub.org_unsubscribe = " . ($org_unsubscribe ?
0 : 1);
559 $query .= " GROUP BY $queue.id, $unsub.time_stamp, $unsub.org_unsubscribe";
562 $orderBy = "sort_name ASC, {$unsub}.time_stamp DESC";
564 if (is_string($sort)) {
565 $sort = CRM_Utils_Type
::escape($sort, 'String');
569 $orderBy = trim($sort->orderBy());
573 $query .= " ORDER BY {$orderBy} ";
575 if ($offset ||
$rowCount) {
576 //Added "||$rowCount" to avoid displaying all records on first page
577 $query .= ' LIMIT ' . CRM_Utils_Type
::escape($offset, 'Integer') . ', ' . CRM_Utils_Type
::escape($rowCount, 'Integer');
584 while ($dao->fetch()) {
585 $url = CRM_Utils_System
::url('civicrm/contact/view',
586 "reset=1&cid={$dao->contact_id}"
589 'name' => "<a href=\"$url\">{$dao->display_name}</a>",
590 'email' => $dao->email
,
591 // Next value displays in selector under either Unsubscribe OR Optout column header, so always s/b Yes.
592 'unsubOrOptout' => ts('Yes'),
593 'date' => CRM_Utils_Date
::customFormat($dao->date
),
600 * @param int $queueID
604 public static function getContactInfo($queueID) {
606 SELECT DISTINCT(civicrm_mailing_event_queue.contact_id) as contact_id,
607 civicrm_contact.display_name as display_name
608 civicrm_email.email as email
609 FROM civicrm_mailing_event_queue,
612 WHERE civicrm_mailing_event_queue.contact_id = civicrm_contact.id
613 AND civicrm_mailing_event_queue.email_id = civicrm_email.id
614 AND civicrm_mailing_event_queue.id = " . CRM_Utils_Type
::escape($queueID, 'Integer');
616 $dao = CRM_Core_DAO
::executeQuery($query);
618 $displayName = 'Unknown';
621 $displayName = $dao->display_name
;
622 $email = $dao->email
;
625 return [$displayName, $email];