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