Merge pull request #12828 from seamuslee001/lab_core_357
[civicrm-core.git] / CRM / Mailing / Event / BAO / Subscribe.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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
31 * @copyright CiviCRM LLC (c) 2004-2018
32 */
33
34
35 require_once 'Mail/mime.php';
36
37 /**
38 * Class CRM_Mailing_Event_BAO_Subscribe
39 */
40 class CRM_Mailing_Event_BAO_Subscribe extends CRM_Mailing_Event_DAO_Subscribe {
41
42 /**
43 * Class constructor.
44 */
45 public function __construct() {
46 parent::__construct();
47 }
48
49 /**
50 * Register a subscription event. Create a new contact if one does not
51 * already exist.
52 *
53 * @param int $group_id
54 * The group id to subscribe to.
55 * @param string $email
56 * The email address of the (new) contact.
57 * @param int $contactId
58 * Currently used during event registration/contribution.
59 * Specifically to avoid linking group to wrong duplicate contact
60 * during event registration.
61 * @param string $context
62 *
63 * @return int|null
64 * $se_id The id of the subscription event, null on failure
65 */
66 public static function &subscribe($group_id, $email, $contactId = NULL, $context = NULL) {
67 // CRM-1797 - allow subscription only to public groups
68 $params = array('id' => (int) $group_id);
69 $defaults = array();
70 $contact_id = NULL;
71 $success = NULL;
72
73 $bao = CRM_Contact_BAO_Group::retrieve($params, $defaults);
74 if ($bao && substr($bao->visibility, 0, 6) != 'Public' && $context != 'profile') {
75 return $success;
76 }
77
78 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
79 $email = $strtolower($email);
80
81 // process the query only if no contactId
82 if ($contactId) {
83 $contact_id = $contactId;
84 }
85 else {
86 // First, find out if the contact already exists.
87
88 $query = "
89 SELECT DISTINCT contact_a.id as contact_id
90 FROM civicrm_contact contact_a
91 LEFT JOIN civicrm_email ON contact_a.id = civicrm_email.contact_id
92 WHERE civicrm_email.email = %1 AND contact_a.is_deleted = 0";
93
94 $params = array(1 => array($email, 'String'));
95 $dao = CRM_Core_DAO::executeQuery($query, $params);
96 // lets just use the first contact id we got
97 if ($dao->fetch()) {
98 $contact_id = $dao->contact_id;
99 }
100 $dao->free();
101 }
102
103 $transaction = new CRM_Core_Transaction();
104
105 if (!$contact_id) {
106 $locationType = CRM_Core_BAO_LocationType::getDefault();
107 $formatted = array(
108 'contact_type' => 'Individual',
109 'email' => $email,
110 'location_type_id' => $locationType->id,
111 );
112
113 $formatted['onDuplicate'] = CRM_Import_Parser::DUPLICATE_SKIP;
114 $formatted['fixAddress'] = TRUE;
115 $contact = civicrm_api3('contact', 'create', $formatted);
116 if (civicrm_error($contact)) {
117 return $success;
118 }
119 $contact_id = $contact['id'];
120 }
121 elseif (!is_numeric($contact_id) &&
122 (int ) $contact_id > 0
123 ) {
124 // make sure contact_id is numeric
125 return $success;
126 }
127
128 // Get the primary email id from the contact to use as a hash input.
129 $query = "
130 SELECT civicrm_email.id as email_id
131 FROM civicrm_email
132 WHERE civicrm_email.email = %1
133 AND civicrm_email.contact_id = %2";
134 $params = array(
135 1 => array($email, 'String'),
136 2 => array($contact_id, 'Integer'),
137 );
138 $dao = CRM_Core_DAO::executeQuery($query, $params);
139
140 if (!$dao->fetch()) {
141 CRM_Core_Error::fatal('Please file an issue with the backtrace');
142 return $success;
143 }
144
145 $se = new CRM_Mailing_Event_BAO_Subscribe();
146 $se->group_id = $group_id;
147 $se->contact_id = $contact_id;
148 $se->time_stamp = date('YmdHis');
149 $se->hash = substr(sha1("{$group_id}:{$contact_id}:{$dao->email_id}:" . time()),
150 0, 16
151 );
152 $se->save();
153
154 $contacts = array($contact_id);
155 CRM_Contact_BAO_GroupContact::addContactsToGroup($contacts, $group_id,
156 'Email', 'Pending', $se->id
157 );
158
159 $transaction->commit();
160 return $se;
161 }
162
163 /**
164 * Verify the hash of a subscription event.
165 *
166 * @param int $contact_id
167 * ID of the contact.
168 * @param int $subscribe_id
169 * ID of the subscription event.
170 * @param string $hash
171 * Hash to verify.
172 *
173 * @return object|null
174 * The subscribe event object, or null on failure
175 */
176 public static function &verify($contact_id, $subscribe_id, $hash) {
177 $success = NULL;
178 $se = new CRM_Mailing_Event_BAO_Subscribe();
179 $se->contact_id = $contact_id;
180 $se->id = $subscribe_id;
181 $se->hash = $hash;
182 if ($se->find(TRUE)) {
183 $success = $se;
184 }
185 return $success;
186 }
187
188 /**
189 * Ask a contact for subscription confirmation (opt-in)
190 *
191 * @param string $email
192 * The email address.
193 */
194 public function send_confirm_request($email) {
195 $config = CRM_Core_Config::singleton();
196
197 $domain = CRM_Core_BAO_Domain::getDomain();
198
199 //get the default domain email address.
200 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain::getNameAndEmail();
201
202 $localpart = CRM_Core_BAO_MailSettings::defaultLocalpart();
203 $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
204
205 $confirm = implode($config->verpSeparator,
206 array(
207 $localpart . 'c',
208 $this->contact_id,
209 $this->id,
210 $this->hash,
211 )
212 ) . "@$emailDomain";
213
214 $group = new CRM_Contact_BAO_Group();
215 $group->id = $this->group_id;
216 $group->find(TRUE);
217
218 $component = new CRM_Mailing_BAO_MailingComponent();
219 $component->is_default = 1;
220 $component->is_active = 1;
221 $component->component_type = 'Subscribe';
222
223 $component->find(TRUE);
224
225 $headers = array(
226 'Subject' => $component->subject,
227 'From' => "\"{$domainEmailName}\" <{$domainEmailAddress}>",
228 'To' => $email,
229 'Reply-To' => $confirm,
230 'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
231 );
232
233 $url = CRM_Utils_System::url('civicrm/mailing/confirm',
234 "reset=1&cid={$this->contact_id}&sid={$this->id}&h={$this->hash}",
235 TRUE
236 );
237
238 $html = $component->body_html;
239
240 if ($component->body_text) {
241 $text = $component->body_text;
242 }
243 else {
244 $text = CRM_Utils_String::htmlToText($component->body_html);
245 }
246
247 $bao = new CRM_Mailing_BAO_Mailing();
248 $bao->body_text = $text;
249 $bao->body_html = $html;
250 $tokens = $bao->getTokens();
251
252 $html = CRM_Utils_Token::replaceDomainTokens($html, $domain, TRUE, $tokens['html']);
253 $html = CRM_Utils_Token::replaceSubscribeTokens($html,
254 $group->title,
255 $url, TRUE
256 );
257
258 $text = CRM_Utils_Token::replaceDomainTokens($text, $domain, FALSE, $tokens['text']);
259 $text = CRM_Utils_Token::replaceSubscribeTokens($text,
260 $group->title,
261 $url, FALSE
262 );
263 // render the &amp; entities in text mode, so that the links work
264 $text = str_replace('&amp;', '&', $text);
265
266 $message = new Mail_mime("\n");
267
268 $message->setHTMLBody($html);
269 $message->setTxtBody($text);
270 $b = CRM_Utils_Mail::setMimeParams($message);
271 $h = $message->headers($headers);
272 CRM_Mailing_BAO_Mailing::addMessageIdHeader($h, 's',
273 $this->contact_id,
274 $this->id,
275 $this->hash
276 );
277 $mailer = \Civi::service('pear_mail');
278
279 if (is_object($mailer)) {
280 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
281 $mailer->send($email, $h, $b);
282 unset($errorScope);
283 }
284 }
285
286 /**
287 * Get the domain object given a subscribe event.
288 *
289 * @param int $subscribe_id
290 * ID of the subscribe event.
291 *
292 * @return object
293 * $domain The domain owning the event
294 */
295 public static function &getDomain($subscribe_id) {
296 return CRM_Core_BAO_Domain::getDomain();
297 }
298
299 /**
300 * Get the group details to which given email belongs.
301 *
302 * @param string $email
303 * Email of the contact.
304 * @param int $contactID
305 * ContactID if we want an exact match.
306 *
307 * @return array
308 * array of group ids
309 */
310 public static function getContactGroups($email, $contactID = NULL) {
311 if ($contactID) {
312 $query = "
313 SELECT DISTINCT group_a.group_id, group_a.status, civicrm_group.title
314 FROM civicrm_group_contact group_a
315 LEFT JOIN civicrm_group ON civicrm_group.id = group_a.group_id
316 LEFT JOIN civicrm_contact ON ( group_a.contact_id = civicrm_contact.id )
317 WHERE civicrm_contact.id = %1";
318
319 $params = array(1 => array($contactID, 'Integer'));
320 }
321 else {
322 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
323 $email = $strtolower($email);
324
325 $query = "
326 SELECT DISTINCT group_a.group_id, group_a.status, civicrm_group.title
327 FROM civicrm_group_contact group_a
328 LEFT JOIN civicrm_group ON civicrm_group.id = group_a.group_id
329 LEFT JOIN civicrm_contact ON ( group_a.contact_id = civicrm_contact.id ) AND civicrm_contact.is_deleted = 0
330 LEFT JOIN civicrm_email ON civicrm_contact.id = civicrm_email.contact_id
331 WHERE civicrm_email.email = %1";
332
333 $params = array(1 => array($email, 'String'));
334 }
335
336 $dao = CRM_Core_DAO::executeQuery($query, $params);
337 $groups = array();
338 while ($dao->fetch()) {
339 $groups[$dao->group_id] = array(
340 'id' => $dao->group_id,
341 'title' => $dao->title,
342 'status' => $dao->status,
343 );
344 }
345
346 $dao->free();
347 return $groups;
348 }
349
350 /**
351 * Send subscribe mail.
352 *
353 * @param array $groups
354 * The list of group ids for subscribe.
355 * @param array $params
356 * The list of email.
357 * @param int $contactId
358 * Currently used during event registration/contribution.
359 * Specifically to avoid linking group to wrong duplicate contact
360 * during event registration.
361 * @param string $context
362 */
363 public static function commonSubscribe(&$groups, &$params, $contactId = NULL, $context = NULL) {
364 $contactGroups = CRM_Mailing_Event_BAO_Subscribe::getContactGroups($params['email'], $contactId);
365 $group = array();
366 $success = NULL;
367 foreach ($groups as $groupID) {
368 $title = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $groupID, 'title');
369 if (array_key_exists($groupID, $contactGroups) && $contactGroups[$groupID]['status'] != 'Removed') {
370 $group[$groupID]['title'] = $contactGroups[$groupID]['title'];
371
372 $group[$groupID]['status'] = $contactGroups[$groupID]['status'];
373 $status = ts('You are already subscribed in %1, your subscription is %2.', array(
374 1 => $group[$groupID]['title'],
375 2 => ts($group[$groupID]['status']),
376 ));
377 CRM_Utils_System::setUFMessage($status);
378 continue;
379 }
380
381 $se = self::subscribe($groupID,
382 $params['email'], $contactId, $context
383 );
384 if ($se !== NULL) {
385 $success = TRUE;
386 $groupAdded[] = $title;
387
388 // Ask the contact for confirmation
389 $se->send_confirm_request($params['email']);
390 }
391 else {
392 $success = FALSE;
393 $groupFailed[] = $title;
394 }
395 }
396 if ($success) {
397 $groupTitle = implode(', ', $groupAdded);
398 CRM_Utils_System::setUFMessage(ts('Your subscription request has been submitted for %1. Check your inbox shortly for the confirmation email(s). If you do not see a confirmation email, please check your spam/junk mail folder.', array(1 => $groupTitle)));
399 }
400 elseif ($success === FALSE) {
401 $groupTitle = implode(',', $groupFailed);
402 CRM_Utils_System::setUFMessage(ts('We had a problem processing your subscription request for %1. You have tried to subscribe to a private group and/or we encountered a database error. Please contact the site administrator.', array(1 => $groupTitle)));
403 }
404 }
405
406 }