\Civi - Add facade accessing system services
[civicrm-core.git] / CRM / Mailing / Event / BAO / Unsubscribe.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
e7112fa7 31 * @copyright CiviCRM LLC (c) 2004-2015
6a488035
TO
32 * $Id$
33 *
34 */
35
36require_once 'Mail/mime.php';
4c6ce474
EM
37
38/**
39 * Class CRM_Mailing_Event_BAO_Unsubscribe
40 */
6a488035
TO
41class CRM_Mailing_Event_BAO_Unsubscribe extends CRM_Mailing_Event_DAO_Unsubscribe {
42
43 /**
fe482240 44 * Class constructor.
6a488035 45 */
00be9182 46 public function __construct() {
6a488035
TO
47 parent::__construct();
48 }
49
50 /**
fe482240 51 * Unsubscribe a contact from the domain.
6a488035 52 *
90c8230e
TO
53 * @param int $job_id
54 * The job ID.
55 * @param int $queue_id
56 * The Queue Event ID of the recipient.
57 * @param string $hash
58 * The hash.
6a488035 59 *
ae5ffbb7 60 * @return bool
a6c01b45 61 * Was the contact successfully unsubscribed?
6a488035
TO
62 */
63 public static function unsub_from_domain($job_id, $queue_id, $hash) {
64 $q = CRM_Mailing_Event_BAO_Queue::verify($job_id, $queue_id, $hash);
65 if (!$q) {
66 return FALSE;
67 }
68
69 $transaction = new CRM_Core_Transaction();
70
71 $now = date('YmdHis');
72 if (CRM_Core_BAO_Email::isMultipleBulkMail()) {
73 $email = new CRM_Core_BAO_Email();
74 $email->id = $q->email_id;
75 if ($email->find(TRUE)) {
76 $sql = "
77UPDATE civicrm_email
78SET on_hold = 2,
79 hold_date = %1
80WHERE email = %2
81";
35f7561f 82 $sqlParams = array(
353ffa53 83 1 => array($now, 'Timestamp'),
6a488035
TO
84 2 => array($email->email, 'String'),
85 );
86 CRM_Core_DAO::executeQuery($sql, $sqlParams);
87 }
88 }
89 else {
90 $contact = new CRM_Contact_BAO_Contact();
91 $contact->id = $q->contact_id;
92 $contact->is_opt_out = TRUE;
93 $contact->save();
94 }
95
96 $ue = new CRM_Mailing_Event_BAO_Unsubscribe();
97 $ue->event_queue_id = $queue_id;
98 $ue->org_unsubscribe = 1;
99 $ue->time_stamp = $now;
100 $ue->save();
101
102 $shParams = array(
103 'contact_id' => $q->contact_id,
104 'group_id' => NULL,
105 'status' => 'Removed',
106 'method' => 'Email',
107 'tracking' => $ue->id,
108 );
109 CRM_Contact_BAO_SubscriptionHistory::create($shParams);
110
111 $transaction->commit();
112
113 return TRUE;
114 }
115
116 /**
fe482240 117 * Unsubscribe a contact from all groups that received this mailing.
6a488035 118 *
90c8230e
TO
119 * @param int $job_id
120 * The job ID.
121 * @param int $queue_id
122 * The Queue Event ID of the recipient.
123 * @param string $hash
124 * The hash.
125 * @param bool $return
126 * If true return the list of groups.
6a488035 127 *
72b3a70c
CW
128 * @return array|null
129 * $groups Array of all groups from which the contact was removed, or null if the queue event could not be found.
6a488035
TO
130 */
131 public static function &unsub_from_mailing($job_id, $queue_id, $hash, $return = FALSE) {
132 /* First make sure there's a matching queue event */
133
134 $q = CRM_Mailing_Event_BAO_Queue::verify($job_id, $queue_id, $hash);
135 $success = NULL;
136 if (!$q) {
137 return $success;
138 }
139
140 $contact_id = $q->contact_id;
141 $transaction = new CRM_Core_Transaction();
142
143 $do = new CRM_Core_DAO();
04124b30 144 $mgObject = new CRM_Mailing_DAO_MailingGroup();
6a488035 145 $mg = $mgObject->getTableName();
9da8dc8c 146 $jobObject = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
147 $job = $jobObject->getTableName();
148 $mailingObject = new CRM_Mailing_BAO_Mailing();
149 $mailing = $mailingObject->getTableName();
150 $groupObject = new CRM_Contact_BAO_Group();
151 $group = $groupObject->getTableName();
152 $gcObject = new CRM_Contact_BAO_GroupContact();
153 $gc = $gcObject->getTableName();
154
155 //We Need the mailing Id for the hook...
ceb10dc7
DL
156 $do->query("SELECT $job.mailing_id as mailing_id
157 FROM $job
6a488035
TO
158 WHERE $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer'));
159 $do->fetch();
160 $mailing_id = $do->mailing_id;
161
162 $do->query("
163 SELECT $mg.entity_table as entity_table,
164 $mg.entity_id as entity_id,
165 $mg.group_type as group_type
166 FROM $mg
167 INNER JOIN $job
168 ON $job.mailing_id = $mg.mailing_id
169 INNER JOIN $group
170 ON $mg.entity_id = $group.id
171 WHERE $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer') . "
ceb10dc7 172 AND $mg.group_type IN ('Include', 'Base')
6a488035
TO
173 AND $group.is_hidden = 0"
174 );
175
ceb10dc7 176 /* Make a list of groups and a list of prior mailings that received
e70a7fc0 177 * this mailing */
6a488035 178
6a488035
TO
179 $groups = array();
180 $base_groups = array();
181 $mailings = array();
182
183 while ($do->fetch()) {
184 if ($do->entity_table == $group) {
185 if ($do->group_type == 'Base') {
186 $base_groups[$do->entity_id] = NULL;
187 }
188 else {
189 $groups[$do->entity_id] = NULL;
190 }
191 }
192 elseif ($do->entity_table == $mailing) {
193 $mailings[] = $do->entity_id;
194 }
195 }
196
197 /* As long as we have prior mailings, find their groups and add to the
e70a7fc0 198 * list */
6a488035
TO
199
200 while (!empty($mailings)) {
201 $do->query("
202 SELECT $mg.entity_table as entity_table,
203 $mg.entity_id as entity_id
204 FROM $mg
205 WHERE $mg.mailing_id IN (" . implode(', ', $mailings) . ")
206 AND $mg.group_type = 'Include'");
207
208 $mailings = array();
209
210 while ($do->fetch()) {
211 if ($do->entity_table == $group) {
212 $groups[$do->entity_id] = TRUE;
213 }
214 elseif ($do->entity_table == $mailing) {
215 $mailings[] = $do->entity_id;
216 }
217 }
218 }
219
220 //Pass the groups to be unsubscribed from through a hook.
221 $group_ids = array_keys($groups);
222 $base_group_ids = array_keys($base_groups);
223 CRM_Utils_Hook::unsubscribeGroups('unsubscribe', $mailing_id, $contact_id, $group_ids, $base_group_ids);
224
225 /* Now we have a complete list of recipient groups. Filter out all
e70a7fc0
TO
226 * those except smart groups, those that the contact belongs to and
227 * base groups from search based mailings */
6a488035
TO
228
229 $baseGroupClause = '';
230 if (!empty($base_group_ids)) {
231 $baseGroupClause = "OR $group.id IN(" . implode(', ', $base_group_ids) . ")";
232 }
3921b55d
CW
233 $groupIdClause = '';
234 if ($group_ids || $base_group_ids) {
235 $groupIdClause = "AND $group.id IN (" . implode(', ', array_merge($group_ids, $base_group_ids)) . ")";
236 }
6a488035
TO
237 $do->query("
238 SELECT $group.id as group_id,
239 $group.title as title,
240 $group.description as description
241 FROM $group
242 LEFT JOIN $gc
243 ON $gc.group_id = $group.id
3921b55d
CW
244 WHERE $group.is_hidden = 0
245 $groupIdClause
6a488035
TO
246 AND ($group.saved_search_id is not null
247 OR ($gc.contact_id = $contact_id
248 AND $gc.status = 'Added')
249 $baseGroupClause
250 )");
251
252 if ($return) {
253 $returnGroups = array();
254 while ($do->fetch()) {
255 $returnGroups[$do->group_id] = array(
256 'title' => $do->title,
257 'description' => $do->description,
258 );
259 }
260 return $returnGroups;
261 }
262 else {
263 while ($do->fetch()) {
264 $groups[$do->group_id] = $do->title;
265 }
266 }
267
268 $contacts = array($contact_id);
269 foreach ($groups as $group_id => $group_name) {
270 $notremoved = FALSE;
271 if ($group_name) {
272 if (in_array($group_id, $base_group_ids)) {
273 list($total, $removed, $notremoved) = CRM_Contact_BAO_GroupContact::addContactsToGroup($contacts, $group_id, 'Email', 'Removed');
274 }
275 else {
276 list($total, $removed, $notremoved) = CRM_Contact_BAO_GroupContact::removeContactsFromGroup($contacts, $group_id, 'Email');
277 }
278 }
279 if ($notremoved) {
280 unset($groups[$group_id]);
281 }
282 }
283
284 $ue = new CRM_Mailing_Event_BAO_Unsubscribe();
285 $ue->event_queue_id = $queue_id;
286 $ue->org_unsubscribe = 0;
287 $ue->time_stamp = date('YmdHis');
288 $ue->save();
289
290 $transaction->commit();
291 return $groups;
292 }
293
294 /**
b44e3f84 295 * Send a response email informing the contact of the groups from which he.
6a488035
TO
296 * has been unsubscribed.
297 *
90c8230e
TO
298 * @param string $queue_id
299 * The queue event ID.
300 * @param array $groups
301 * List of group IDs.
302 * @param bool $is_domain
303 * Is this domain-level?.
304 * @param int $job
305 * The job ID.
6a488035
TO
306 *
307 * @return void
6a488035
TO
308 */
309 public static function send_unsub_response($queue_id, $groups, $is_domain = FALSE, $job) {
310 $config = CRM_Core_Config::singleton();
311 $domain = CRM_Core_BAO_Domain::getDomain();
312
9da8dc8c 313 $jobObject = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
314 $jobTable = $jobObject->getTableName();
315 $mailingObject = new CRM_Mailing_DAO_Mailing();
316 $mailingTable = $mailingObject->getTableName();
317 $contactsObject = new CRM_Contact_DAO_Contact();
318 $contacts = $contactsObject->getTableName();
319 $emailObject = new CRM_Core_DAO_Email();
320 $email = $emailObject->getTableName();
321 $queueObject = new CRM_Mailing_Event_BAO_Queue();
322 $queue = $queueObject->getTableName();
323
324 //get the default domain email address.
325 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain::getNameAndEmail();
326
327 $dao = new CRM_Mailing_BAO_Mailing();
ceb10dc7 328 $dao->query(" SELECT * FROM $mailingTable
6a488035 329 INNER JOIN $jobTable ON
ceb10dc7 330 $jobTable.mailing_id = $mailingTable.id
6a488035
TO
331 WHERE $jobTable.id = $job");
332 $dao->fetch();
333
334 $component = new CRM_Mailing_BAO_Component();
335
336 if ($is_domain) {
337 $component->id = $dao->optout_id;
338 }
339 else {
340 $component->id = $dao->unsubscribe_id;
341 }
342 $component->find(TRUE);
343
344 $html = $component->body_html;
345 if ($component->body_text) {
346 $text = $component->body_text;
347 }
348 else {
349 $text = CRM_Utils_String::htmlToText($component->body_html);
350 }
351
352 $eq = new CRM_Core_DAO();
353 $eq->query(
354 "SELECT $contacts.preferred_mail_format as format,
355 $contacts.id as contact_id,
356 $email.email as email,
357 $queue.hash as hash
358 FROM $contacts
359 INNER JOIN $queue ON $queue.contact_id = $contacts.id
360 INNER JOIN $email ON $queue.email_id = $email.id
361 WHERE $queue.id = " . CRM_Utils_Type::escape($queue_id, 'Integer')
362 );
363 $eq->fetch();
364
365 if ($groups) {
366 foreach ($groups as $key => $value) {
367 if (!$value) {
368 unset($groups[$key]);
369 }
370 }
371 }
372
373 $message = new Mail_mime("\n");
374
375 list($addresses, $urls) = CRM_Mailing_BAO_Mailing::getVerpAndUrls($job, $queue_id, $eq->hash, $eq->email);
376 $bao = new CRM_Mailing_BAO_Mailing();
377 $bao->body_text = $text;
378 $bao->body_html = $html;
379 $tokens = $bao->getTokens();
380 if ($eq->format == 'HTML' || $eq->format == 'Both') {
381 $html = CRM_Utils_Token::replaceDomainTokens($html, $domain, TRUE, $tokens['html']);
382 $html = CRM_Utils_Token::replaceUnsubscribeTokens($html, $domain, $groups, TRUE, $eq->contact_id, $eq->hash);
383 $html = CRM_Utils_Token::replaceActionTokens($html, $addresses, $urls, TRUE, $tokens['html']);
384 $html = CRM_Utils_Token::replaceMailingTokens($html, $dao, NULL, $tokens['html']);
385 $message->setHTMLBody($html);
386 }
387 if (!$html || $eq->format == 'Text' || $eq->format == 'Both') {
388 $text = CRM_Utils_Token::replaceDomainTokens($text, $domain, FALSE, $tokens['text']);
389 $text = CRM_Utils_Token::replaceUnsubscribeTokens($text, $domain, $groups, FALSE, $eq->contact_id, $eq->hash);
390 $text = CRM_Utils_Token::replaceActionTokens($text, $addresses, $urls, FALSE, $tokens['text']);
391 $text = CRM_Utils_Token::replaceMailingTokens($text, $dao, NULL, $tokens['text']);
392 $message->setTxtBody($text);
393 }
394
395 $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
396
397 $headers = array(
398 'Subject' => $component->subject,
399 'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
400 'To' => $eq->email,
401 'Reply-To' => "do-not-reply@$emailDomain",
402 'Return-Path' => "do-not-reply@$emailDomain",
403 );
404 CRM_Mailing_BAO_Mailing::addMessageIdHeader($headers, 'u', $job, $queue_id, $eq->hash);
405
406 $b = CRM_Utils_Mail::setMimeParams($message);
407 $h = $message->headers($headers);
408
247eb841 409 $mailer = \Civi\Core\Container::singleton()->get('pear_mail');
6a488035 410
6a488035 411 if (is_object($mailer)) {
6a4257d4 412 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
6a488035 413 $mailer->send($eq->email, $h, $b);
6a4257d4 414 unset($errorScope);
6a488035
TO
415 }
416 }
417
418 /**
fe482240 419 * Get row count for the event selector.
6a488035 420 *
90c8230e
TO
421 * @param int $mailing_id
422 * ID of the mailing.
423 * @param int $job_id
424 * Optional ID of a job to filter on.
425 * @param bool $is_distinct
426 * Group by queue ID?.
2a6da8d7
EM
427 *
428 * @param null $org_unsubscribe
6a488035 429 *
a6c01b45
CW
430 * @return int
431 * Number of rows in result set
6a488035 432 */
a3d7e8ee
TO
433 public static function getTotalCount(
434 $mailing_id, $job_id = NULL,
7811a84b 435 $is_distinct = FALSE, $org_unsubscribe = NULL, $toDate = NULL
6a488035
TO
436 ) {
437 $dao = new CRM_Core_DAO();
438
439 $unsub = self::$_tableName;
440 $queueObject = new CRM_Mailing_Event_BAO_Queue();
441 $queue = $queueObject->getTableName();
442 $mailingObject = new CRM_Mailing_BAO_Mailing();
443 $mailing = $mailingObject->getTableName();
9da8dc8c 444 $jobObject = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
445 $job = $jobObject->getTableName();
446
447 $query = "
448 SELECT COUNT($unsub.id) as unsubs
449 FROM $unsub
450 INNER JOIN $queue
451 ON $unsub.event_queue_id = $queue.id
452 INNER JOIN $job
453 ON $queue.job_id = $job.id
454 INNER JOIN $mailing
455 ON $job.mailing_id = $mailing.id
456 AND $job.is_test = 0
457 WHERE $mailing.id = " . CRM_Utils_Type::escape($mailing_id, 'Integer');
458
7811a84b 459 if (!empty($toDate)) {
460 $query .= " AND $unsub.time_stamp <= $toDate";
461 }
462
6a488035
TO
463 if (!empty($job_id)) {
464 $query .= " AND $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer');
465 }
466
467 if ($org_unsubscribe !== NULL) {
468 $query .= " AND $unsub.org_unsubscribe = " . ($org_unsubscribe ? 0 : 1);
469 }
470
471 if ($is_distinct) {
472 $query .= " GROUP BY $queue.id ";
473 }
474
475 $dao->query($query);
476 $dao->fetch();
477 if ($is_distinct) {
478 return $dao->N;
479 }
480 else {
481 return $dao->unsubs ? $dao->unsubs : 0;
482 }
483 }
484
485 /**
fe482240 486 * Get rows for the event browser.
6a488035 487 *
90c8230e
TO
488 * @param int $mailing_id
489 * ID of the mailing.
490 * @param int $job_id
491 * Optional ID of the job.
492 * @param bool $is_distinct
493 * Group by queue id?.
494 * @param int $offset
495 * Offset.
496 * @param int $rowCount
497 * Number of rows.
498 * @param array $sort
499 * Sort array.
6a488035 500 *
2a6da8d7 501 * @param null $org_unsubscribe
a6c01b45
CW
502 * @return array
503 * Result set
6a488035 504 */
a3d7e8ee
TO
505 public static function &getRows(
506 $mailing_id, $job_id = NULL,
6a488035
TO
507 $is_distinct = FALSE, $offset = NULL, $rowCount = NULL, $sort = NULL,
508 $org_unsubscribe = NULL
509 ) {
510
511 $dao = new CRM_Core_Dao();
512
513 $unsub = self::$_tableName;
514 $queueObject = new CRM_Mailing_Event_BAO_Queue();
515 $queue = $queueObject->getTableName();
516 $mailingObject = new CRM_Mailing_BAO_Mailing();
517 $mailing = $mailingObject->getTableName();
9da8dc8c 518 $jobObject = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
519 $job = $jobObject->getTableName();
520 $contactObject = new CRM_Contact_BAO_Contact();
521 $contact = $contactObject->getTableName();
522 $emailObject = new CRM_Core_BAO_Email();
523 $email = $emailObject->getTableName();
524
525 $query = "
526 SELECT $contact.display_name as display_name,
527 $contact.id as contact_id,
528 $email.email as email,
529 $unsub.time_stamp as date,
530 $unsub.org_unsubscribe as org_unsubscribe
531 FROM $contact
532 INNER JOIN $queue
533 ON $queue.contact_id = $contact.id
534 INNER JOIN $email
535 ON $queue.email_id = $email.id
536 INNER JOIN $unsub
537 ON $unsub.event_queue_id = $queue.id
538 INNER JOIN $job
539 ON $queue.job_id = $job.id
540 INNER JOIN $mailing
541 ON $job.mailing_id = $mailing.id
542 AND $job.is_test = 0
543 WHERE $mailing.id = " . CRM_Utils_Type::escape($mailing_id, 'Integer');
544
545 if (!empty($job_id)) {
546 $query .= " AND $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer');
547 }
548
549 if ($org_unsubscribe !== NULL) {
550 $query .= " AND $unsub.org_unsubscribe = " . ($org_unsubscribe ? 0 : 1);
551 }
552
553 if ($is_distinct) {
554 $query .= " GROUP BY $queue.id ";
555 }
556
557 $orderBy = "sort_name ASC, {$unsub}.time_stamp DESC";
558 if ($sort) {
559 if (is_string($sort)) {
21d32567 560 $sort = CRM_Utils_Type::escape($sort, 'String');
6a488035
TO
561 $orderBy = $sort;
562 }
563 else {
564 $orderBy = trim($sort->orderBy());
565 }
566 }
567
568 $query .= " ORDER BY {$orderBy} ";
569
570 if ($offset || $rowCount) {
571 //Added "||$rowCount" to avoid displaying all records on first page
572 $query .= ' LIMIT ' . CRM_Utils_Type::escape($offset, 'Integer') . ', ' . CRM_Utils_Type::escape($rowCount, 'Integer');
573 }
574
575 $dao->query($query);
576
577 $results = array();
578
579 while ($dao->fetch()) {
580 $url = CRM_Utils_System::url('civicrm/contact/view',
581 "reset=1&cid={$dao->contact_id}"
582 );
583 $results[] = array(
584 'name' => "<a href=\"$url\">{$dao->display_name}</a>",
585 'email' => $dao->email,
4b95f713
DG
586 // Next value displays in selector under either Unsubscribe OR Optout column header, so always s/b Yes.
587 'unsubOrOptout' => ts('Yes'),
6a488035
TO
588 'date' => CRM_Utils_Date::customFormat($dao->date),
589 );
590 }
591 return $results;
592 }
593
e0ef6999 594 /**
100fef9d 595 * @param int $queueID
e0ef6999
EM
596 *
597 * @return array
598 */
6a488035
TO
599 public static function getContactInfo($queueID) {
600 $query = "
601SELECT DISTINCT(civicrm_mailing_event_queue.contact_id) as contact_id,
602 civicrm_contact.display_name as display_name
603 civicrm_email.email as email
604 FROM civicrm_mailing_event_queue,
605 civicrm_contact,
606 civicrm_email
607 WHERE civicrm_mailing_event_queue.contact_id = civicrm_contact.id
608 AND civicrm_mailing_event_queue.email_id = civicrm_email.id
609 AND civicrm_mailing_event_queue.id = " . CRM_Utils_Type::escape($queueID, 'Integer');
610
611 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
612
613 $displayName = 'Unknown';
614 $email = 'Unknown';
615 if ($dao->fetch()) {
616 $displayName = $dao->display_name;
617 $email = $dao->email;
618 }
619
620 return array($displayName, $email);
621 }
96025800 622
6a488035 623}