Merge pull request #20059 from samuelsov/dev/core#2479
[civicrm-core.git] / CRM / Campaign / Form / Petition / Signature.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
ce064e4f 19 * This class generates form components for processing a petition signature.
6a488035
TO
20 */
21class CRM_Campaign_Form_Petition_Signature extends CRM_Core_Form {
7da04cde 22 const EMAIL_THANK = 1, EMAIL_CONFIRM = 2, MODE_CREATE = 4;
6a488035
TO
23
24 protected $_mode;
25
26 /**
100fef9d 27 * The id of the contact associated with this signature
6a488035
TO
28 *
29 * @var int
6a488035
TO
30 */
31 public $_contactId;
32
33 /**
34 * Is this a logged in user
35 *
36 * @var int
37 */
38 protected $_loggedIn = FALSE;
39
40 /**
41 * The contact type
42 *
51dda21e
SL
43 * @var string
44 * ("Individual"/"Household"/"Organization"). Never been tested for something else than Individual
6a488035
TO
45 */
46 protected $_ctype = 'Individual';
47
48 /**
49 * The contact profile id attached with this petition
50 *
51 * @var int
52 */
53 protected $_contactProfileId;
54
55 /**
100fef9d 56 * The contact profile fields used for this petition
6a488035
TO
57 *
58 * @var array
59 */
60 public $_contactProfileFields;
61
62 /**
63 * The activity profile id attached with this petition
64 *
65 * @var int
66 */
67 protected $_activityProfileId;
68
69 /**
100fef9d 70 * The activity profile fields used for this petition
6a488035
TO
71 *
72 * @var array
73 */
74 public $_activityProfileFields;
75
76 /**
73543110 77 * The id of the survey (petition) we are processing
6a488035
TO
78 *
79 * @var int
6a488035
TO
80 */
81 public $_surveyId;
82
83 /**
84 * The tag id used to set against contacts with unconfirmed email
85 *
86 * @var int
87 */
88 protected $_tagId;
89
90 /**
100fef9d 91 * Values to use for custom profiles
6a488035
TO
92 *
93 * @var array
6a488035
TO
94 */
95 public $_values;
96
97 /**
98 * The params submitted by the form
99 *
100 * @var array
6a488035
TO
101 */
102 protected $_params;
103
104 /**
100fef9d 105 * Which email send mode do we use
6a488035
TO
106 *
107 * @var int
108 * EMAIL_THANK = 1,
109 * connected user via login/pwd - thank you
110 * or dedupe contact matched who doesn't have a tag CIVICRM_TAG_UNCONFIRMED - thank you
111 * or login using fb connect - thank you + click to add msg to fb wall
112 * EMAIL_CONFIRM = 2;
113 * send a confirmation request email
114 */
115 protected $_sendEmailMode;
116
117 protected $_image_URL;
118
30c4e065 119 /**
30c4e065 120 */
00be9182 121 public function __construct() {
6a488035
TO
122 parent::__construct();
123 // this property used by civicrm_fb module and if true, forces thank you email to be sent
124 // for users signing in via Facebook connect; also sets Fb email to check against
125 $this->forceEmailConfirmed['flag'] = FALSE;
126 $this->forceEmailConfirmed['email'] = '';
127 }
128
30c4e065
EM
129 /**
130 * @return mixed
131 */
00be9182 132 public function getContactID() {
6a488035
TO
133 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
134
135 // force to ignore the authenticated user
136 if ($tempID === '0') {
137 return $tempID;
138 }
139
140 //check if this is a checksum authentication
141 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
142 if ($userChecksum) {
143 //check for anonymous user.
144 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
145 if ($validUser) {
146 return $tempID;
147 }
148 }
149
150 // check if the user is registered and we have a contact ID
151 $session = CRM_Core_Session::singleton();
152 return $session->get('userID');
153 }
154
155 public function preProcess() {
156 $this->bao = new CRM_Campaign_BAO_Petition();
157 $this->_mode = self::MODE_CREATE;
158
159 //get the survey id
160 $this->_surveyId = CRM_Utils_Request::retrieve('sid', 'Positive', $this);
161
162 //some sanity checks
163 if (!$this->_surveyId) {
0082383f 164 CRM_Core_Error::statusBounce('Petition id is not valid. (it needs a "sid" in the url).');
6a488035
TO
165 return;
166 }
167 //check petition is valid and active
168 $params['id'] = $this->_surveyId;
be2fb01f 169 $this->petition = [];
6a488035
TO
170 CRM_Campaign_BAO_Survey::retrieve($params, $this->petition);
171 if (empty($this->petition)) {
0082383f 172 CRM_Core_Error::statusBounce('Petition doesn\'t exist.');
6a488035
TO
173 }
174 if ($this->petition['is_active'] == 0) {
beb414cc 175 CRM_Core_Error::statusBounce('Petition is no longer active.');
6a488035
TO
176 }
177
178 //get userID from session
179 $session = CRM_Core_Session::singleton();
180
181 //get the contact id for this user if logged in
182 $this->_contactId = $this->getContactId();
183 if (isset($this->_contactId)) {
184 $this->_loggedIn = TRUE;
185 }
186
187 // add the custom contact and activity profile fields to the signature form
188
be2fb01f 189 $ufJoinParams = [
6a488035
TO
190 'entity_id' => $this->_surveyId,
191 'entity_table' => 'civicrm_survey',
192 'module' => 'CiviCampaign',
193 'weight' => 2,
be2fb01f 194 ];
6a488035
TO
195
196 $this->_contactProfileId = CRM_Core_BAO_UFJoin::findUFGroupId($ufJoinParams);
197 if ($this->_contactProfileId) {
198 $this->_contactProfileFields = CRM_Core_BAO_UFGroup::getFields($this->_contactProfileId, FALSE, CRM_Core_Action::ADD);
199 }
200 if (!isset($this->_contactProfileFields['email-Primary'])) {
0082383f 201 CRM_Core_Error::statusBounce('The contact profile needs to contain the primary email address field');
6a488035
TO
202 }
203
6a488035
TO
204 $ufJoinParams['weight'] = 1;
205 $this->_activityProfileId = CRM_Core_BAO_UFJoin::findUFGroupId($ufJoinParams);
206
207 if ($this->_activityProfileId) {
208 $this->_activityProfileFields = CRM_Core_BAO_UFGroup::getFields($this->_activityProfileId, FALSE, CRM_Core_Action::ADD);
209 }
210
211 $this->setDefaultValues();
212 CRM_Utils_System::setTitle($this->petition['title']);
213 }
214
215 /**
c490a46a 216 * Set default values for the form.
6a488035 217 */
00be9182 218 public function setDefaultValues() {
be2fb01f 219 $this->_defaults = [];
6a488035
TO
220 if ($this->_contactId) {
221 CRM_Core_BAO_UFGroup::setProfileDefaults($this->_contactId, $this->_contactProfileFields, $this->_defaults, TRUE);
222 if ($this->_activityProfileId) {
223 CRM_Core_BAO_UFGroup::setProfileDefaults($this->_contactId, $this->_activityProfileFields, $this->_defaults, TRUE);
224 }
225 }
226
227 //set custom field defaults
228
229 foreach ($this->_contactProfileFields as $name => $field) {
230 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) {
231 $htmlType = $field['html_type'];
232
233 if (!isset($this->_defaults[$name])) {
234 CRM_Core_BAO_CustomField::setProfileDefaults($customFieldID,
235 $name,
236 $this->_defaults,
237 $this->_contactId,
238 $this->_mode
239 );
240 }
241 }
242 }
243
244 if ($this->_activityProfileFields) {
245 foreach ($this->_activityProfileFields as $name => $field) {
246 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) {
247 $htmlType = $field['html_type'];
248
249 if (!isset($this->_defaults[$name])) {
250 CRM_Core_BAO_CustomField::setProfileDefaults($customFieldID,
251 $name,
252 $this->_defaults,
253 $this->_contactId,
254 $this->_mode
255 );
256 }
257 }
258 }
259 }
260
261 $this->setDefaults($this->_defaults);
262 }
263
264 public function buildQuickForm() {
265 $this->assign('survey_id', $this->_surveyId);
266 $this->assign('petitionTitle', $this->petition['title']);
267 if (isset($_COOKIE['signed_' . $this->_surveyId])) {
268 if (isset($_COOKIE['confirmed_' . $this->_surveyId])) {
269 $this->assign('duplicate', "confirmed");
270 }
271 else {
272 $this->assign('duplicate', "unconfirmed");
273 }
274 return;
275 }
276
277 $this->applyFilter('__ALL__', 'trim');
278
279 $this->buildCustom($this->_contactProfileId, 'petitionContactProfile');
280 if ($this->_activityProfileId) {
281 $this->buildCustom($this->_activityProfileId, 'petitionActivityProfile');
282 }
283 // add buttons
be2fb01f 284 $this->addButtons([
5d4fcf54
TO
285 [
286 'type' => 'upload',
287 'name' => ts('Sign the Petition'),
288 'isDefault' => TRUE,
289 ],
290 ]);
6a488035
TO
291 }
292
293 /**
dc195289 294 * add the rules (mainly global rules) for form.
6a488035
TO
295 * All local rules are added near the element
296 *
da6b46f4
EM
297 * @param $fields
298 * @param $files
299 * @param $errors
300 *
6a488035 301 * @see valid_date
ce064e4f 302 * @return array|bool
6a488035 303 */
00be9182 304 public static function formRule($fields, $files, $errors) {
be2fb01f 305 $errors = [];
6a488035
TO
306
307 return empty($errors) ? TRUE : $errors;
308 }
309
310 /**
fe482240 311 * Form submission of petition signature.
6a488035
TO
312 */
313 public function postProcess() {
aaffa79f 314 $tag_name = Civi::settings()->get('tag_unconfirmed');
6a488035 315
6a488035
TO
316 if ($tag_name) {
317 // Check if contact 'email confirmed' tag exists, else create one
318 // This should be in the petition module initialise code to create a default tag for this
319 $tag_params['name'] = $tag_name;
320 $tag_params['version'] = 3;
321 $tag = civicrm_api('tag', 'get', $tag_params);
322 if ($tag['count'] == 0) {
323 //create tag
324 $tag_params['description'] = $tag_name;
325 $tag_params['is_reserved'] = 1;
326 $tag_params['used_for'] = 'civicrm_contact';
327 $tag = civicrm_api('tag', 'create', $tag_params);
328 }
329 $this->_tagId = $tag['id'];
330 }
331
332 // export the field values to be used for saving the profile form
333 $params = $this->controller->exportValues($this->_name);
334
335 $session = CRM_Core_Session::singleton();
336 // format params
337 $params['last_modified_id'] = $session->get('userID');
338 $params['last_modified_date'] = date('YmdHis');
339
340 if ($this->_action & CRM_Core_Action::ADD) {
341 $params['created_id'] = $session->get('userID');
342 $params['created_date'] = date('YmdHis');
343 }
344
345 if (isset($this->_surveyId)) {
346 $params['sid'] = $this->_surveyId;
347 }
348
349 if (isset($this->_contactId)) {
350 $params['contactId'] = $this->_contactId;
351 }
352
353 // if logged in user, skip dedupe
354 if ($this->_loggedIn) {
355 $ids[0] = $this->_contactId;
356 }
357 else {
be2fb01f 358 $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $this->_ctype, 'Unsupervised', [], FALSE);
6a488035
TO
359 }
360
353ffa53 361 $petition_params['id'] = $this->_surveyId;
be2fb01f 362 $petition = [];
353ffa53 363 CRM_Campaign_BAO_Survey::retrieve($petition_params, $petition);
6a488035
TO
364
365 switch (count($ids)) {
366 case 0:
367 //no matching contacts - create a new contact
368 // Add a source for this new contact
369 $params['source'] = ts('Petition Signature') . ' ' . $this->petition['title'];
370
371 if ($this->petition['bypass_confirm']) {
372 // send thank you email directly, bypassing confirmation
373 $this->_sendEmailMode = self::EMAIL_THANK;
374 // Set status for signature activity to completed
375 $params['statusId'] = 2;
376 }
377 else {
353ffa53 378 $this->_sendEmailMode = self::EMAIL_CONFIRM;
6a488035 379
353ffa53
TO
380 // Set status for signature activity to scheduled until email is verified
381 $params['statusId'] = 1;
6a488035
TO
382 }
383 break;
384
385 case 1:
386 $this->_contactId = $params['contactId'] = $ids[0];
387
388 // check if user has already signed this petition - redirects to Thank You if true
389 $this->redirectIfSigned($params);
390
391 if ($this->petition['bypass_confirm']) {
392 // send thank you email directly, bypassing confirmation
393 $this->_sendEmailMode = self::EMAIL_THANK;
394 // Set status for signature activity to completed
395 $params['statusId'] = 2;
396 break;
397 }
398
399 // dedupe matched single contact, check for 'unconfirmed' tag
400 if ($tag_name) {
353ffa53 401 $tag = new CRM_Core_DAO_EntityTag();
6a488035 402 $tag->entity_id = $this->_contactId;
353ffa53 403 $tag->tag_id = $this->_tagId;
6a488035
TO
404
405 if (!($tag->find())) {
406 // send thank you email directly, the user is known and validated
407 $this->_sendEmailMode = self::EMAIL_THANK;
408 // Set status for signature activity to completed
409 $params['statusId'] = 2;
410 }
411 else {
412 // send email verification email
413 $this->_sendEmailMode = self::EMAIL_CONFIRM;
414 // Set status for signature activity to scheduled until email is verified
415 $params['statusId'] = 1;
416 }
417 }
418 break;
419
420 default:
421 // more than 1 matching contact
422 // for time being, take the first matching contact (not sure that's the best strategy, but better than creating another duplicate)
423 $this->_contactId = $params['contactId'] = $ids[0];
424
425 // check if user has already signed this petition - redirects to Thank You if true
426 $this->redirectIfSigned($params);
427
428 if ($this->petition['bypass_confirm']) {
429 // send thank you email directly, bypassing confirmation
430 $this->_sendEmailMode = self::EMAIL_THANK;
431 // Set status for signature activity to completed
432 $params['statusId'] = 2;
433 break;
434 }
435
436 if ($tag_name) {
353ffa53 437 $tag = new CRM_Core_DAO_EntityTag();
6a488035 438 $tag->entity_id = $this->_contactId;
353ffa53 439 $tag->tag_id = $this->_tagId;
6a488035
TO
440
441 if (!($tag->find())) {
442 // send thank you email
443 $this->_sendEmailMode = self::EMAIL_THANK;
444 // Set status for signature activity to completed
445 $params['statusId'] = 2;
446 }
447 else {
448 // send email verification email
449 $this->_sendEmailMode = self::EMAIL_CONFIRM;
450 // Set status for signature activity to scheduled until email is verified
451 $params['statusId'] = 1;
452 }
453 }
454 break;
455 }
456
6a488035
TO
457 $transaction = new CRM_Core_Transaction();
458
32ad9152 459 // CRM-17029 - get the add_to_group_id from the _contactProfileFields array.
26016861
J
460 // There's a much more elegant solution with
461 // array_values($this->_contactProfileFields)[0] but it's PHP 5.4+ only.
462 $slice = array_slice($this->_contactProfileFields, 0, 1);
463 $firstField = array_shift($slice);
77c21b32 464 $addToGroupID = $firstField['add_to_group_id'] ?? NULL;
6a488035
TO
465 $this->_contactId = CRM_Contact_BAO_Contact::createProfileContact($params, $this->_contactProfileFields,
466 $this->_contactId, $addToGroupID,
467 $this->_contactProfileId, $this->_ctype,
468 TRUE
469 );
470
471 // get additional custom activity profile field data
472 // to save with new signature activity record
473 $surveyInfo = $this->bao->getSurveyInfo($this->_surveyId);
474 $customActivityFields = CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
475 $surveyInfo['activity_type_id']
476 );
477 $customActivityFields = CRM_Utils_Array::crmArrayMerge($customActivityFields,
478 CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
479 NULL, NULL, TRUE
480 )
481 );
482
483 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
6a488035
TO
484 NULL,
485 'Activity'
486 );
487
488 // create the signature activity record
489 $params['contactId'] = $this->_contactId;
9c1bc317 490 $params['activity_campaign_id'] = $this->petition['campaign_id'] ?? NULL;
6a488035
TO
491 $result = $this->bao->createSignature($params);
492
493 // send thank you or email verification emails
494
495 // if logged in using Facebook connect and email on form matches Fb email,
496 // no need for email confirmation, send thank you email
497 if ($this->forceEmailConfirmed['flag'] &&
498 ($this->forceEmailConfirmed['email'] == $params['email-Primary'])
499 ) {
500 $this->_sendEmailMode = self::EMAIL_THANK;
501 }
502
503 switch ($this->_sendEmailMode) {
504 case self::EMAIL_THANK:
505 // mark the signature activity as completed and set confirmed cookie
506 $this->bao->confirmSignature($result->id, $this->_contactId, $this->_surveyId);
507 break;
508
509 case self::EMAIL_CONFIRM:
510 // set 'Unconfirmed' tag for this new contact
511 if ($tag_name) {
512 unset($tag_params);
513 $tag_params['contact_id'] = $this->_contactId;
514 $tag_params['tag_id'] = $this->_tagId;
83b0a3cc 515 $tag_params['version'] = 3;
6a488035
TO
516 $tag_value = civicrm_api('entity_tag', 'create', $tag_params);
517 }
518 break;
519 }
520
521 //send email
522 $params['activityId'] = $result->id;
523 $params['tagId'] = $this->_tagId;
6a488035
TO
524
525 $transaction->commit();
526
83b0a3cc 527 $this->bao->sendEmail($params, $this->_sendEmailMode);
528
6a488035
TO
529 if ($result) {
530 // call the hook before we redirect
531 $this->postProcessHook();
532
533 // set the template to thank you
8d7a9d07
CB
534 $url = CRM_Utils_System::url(
535 'civicrm/petition/thankyou',
536 'pid=' . $this->_surveyId . '&id=' . $this->_sendEmailMode . '&reset=1'
537 );
6a488035
TO
538 CRM_Utils_System::redirect($url);
539 }
540 }
541
542 /**
fe482240 543 * Build the petition profile form.
6a488035 544 *
100fef9d
CW
545 * @param int $id
546 * @param string $name
6a488035 547 */
c3aeac8a 548 public function buildCustom($id, $name) {
6a488035
TO
549 if ($id) {
550 $session = CRM_Core_Session::singleton();
551 $this->assign("petition", $this->petition);
552 //$contactID = $this->_contactId;
553 $contactID = NULL;
554 $this->assign('contact_id', $this->_contactId);
555
556 $fields = NULL;
557 // TODO: contactID is never set (commented above)
558 if ($contactID) {
559 if (CRM_Core_BAO_UFGroup::filterUFGroups($id, $contactID)) {
560 $fields = CRM_Core_BAO_UFGroup::getFields($id, FALSE, CRM_Core_Action::ADD);
561 }
562 }
563 else {
564 $fields = CRM_Core_BAO_UFGroup::getFields($id, FALSE, CRM_Core_Action::ADD);
565 }
566
567 if ($fields) {
6a488035
TO
568 $this->assign($name, $fields);
569
570 $addCaptcha = FALSE;
571 foreach ($fields as $key => $field) {
2efcf0c2 572 // if state or country in the profile, create map
2c6b0601 573 list($prefixName, $index) = CRM_Utils_System::explode('-', $key, 2);
6a488035
TO
574
575 CRM_Core_BAO_UFGroup::buildProfile($this, $field, CRM_Profile_Form::MODE_CREATE, $contactID, TRUE);
576 $this->_fields[$key] = $field;
71fc6ea4
DG
577 // CRM-11316 Is ReCAPTCHA enabled for this profile AND is this an anonymous visitor
578 if ($field['add_captcha'] && !$this->_contactId) {
6a488035
TO
579 $addCaptcha = TRUE;
580 }
581 }
582
c3aeac8a 583 if ($addCaptcha) {
268ff0e8 584 CRM_Utils_ReCAPTCHA::enableCaptchaOnForm($this);
6a488035
TO
585 }
586 }
587 }
588 }
589
30c4e065
EM
590 /**
591 * @return string
592 */
00be9182 593 public function getTemplateFileName() {
6a488035
TO
594 if (isset($this->thankyou)) {
595 return ('CRM/Campaign/Page/Petition/ThankYou.tpl');
596 }
353ffa53
TO
597 else {
598 }
6a488035
TO
599 return parent::getTemplateFileName();
600 }
601
30c4e065 602 /**
fe482240 603 * check if user has already signed this petition.
c490a46a 604 * @param array $params
30c4e065 605 */
00be9182 606 public function redirectIfSigned($params) {
6a488035
TO
607 $signature = $this->bao->checkSignature($this->_surveyId, $this->_contactId);
608 //TODO: error case when more than one signature found for this petition and this contact
609 if (!empty($signature) && (count($signature) == 1)) {
610 $signature_id = array_keys($signature);
611 switch ($signature[$signature_id[0]]['status_id']) {
612 case 1:
613 //status is scheduled - email is unconfirmed
614 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/petition/thankyou', 'pid=' . $this->_surveyId . '&id=4&reset=1'));
615 break;
616
617 case 2:
618 //status is completed
619 $this->bao->sendEmail($params, 1);
620 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/petition/thankyou', 'pid=' . $this->_surveyId . '&id=5&reset=1'));
621 break;
622 }
623 }
624 }
96025800 625
6a488035 626}