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