Merge pull request #17655 from samuelsov/lab1827
[civicrm-core.git] / CRM / Event / Form / SelfSvcTransfer.php
1 <?php
2
3 /*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
13 /**
14 *
15 * @package CRM
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 */
18
19 /**
20 * This class generates form components to transfer an Event to another participant
21 *
22 */
23 class CRM_Event_Form_SelfSvcTransfer extends CRM_Core_Form {
24 /**
25 * from particpant id
26 *
27 * @var string
28 *
29 */
30 protected $_from_participant_id;
31 /**
32 * from contact id
33 *
34 * @var string
35 *
36 */
37 protected $_from_contact_id;
38 /**
39 * last name of the particpant to transfer to
40 *
41 * @var string
42 *
43 */
44 protected $_to_contact_last_name;
45 /**
46 * first name of the particpant to transfer to
47 *
48 * @var string
49 *
50 */
51 protected $_to_contact_first_name;
52 /**
53 * email of participant
54 *
55 *
56 * @var string
57 */
58 protected $_to_contact_email;
59 /**
60 * _to_contact_id
61 *
62 * @var string
63 */
64 protected $_to_contact_id;
65 /**
66 * event to be cancelled/transferred
67 *
68 * @var string
69 */
70 protected $_event_id;
71 /**
72 * event title
73 *
74 * @var string
75 */
76 protected $_event_title;
77 /**
78 * event title
79 *
80 * @var string
81 */
82 protected $_event_start_date;
83 /**
84 * action
85 *
86 * @var string
87 */
88 public $_action;
89 /**
90 * participant object
91 *
92 * @var string
93 */
94 protected $_participant = [];
95 /**
96 * particpant values
97 *
98 * @var string
99 */
100 protected $_part_values;
101 /**
102 * details
103 *
104 * @var array
105 */
106 protected $_details = [];
107 /**
108 * line items
109 *
110 * @var array
111 */
112 protected $_line_items = [];
113 /**
114 * contact_id
115 *
116 * @var int
117 */
118 protected $contact_id;
119
120 /**
121 * Is backoffice form?
122 *
123 * @var bool
124 */
125 protected $isBackoffice = FALSE;
126
127 /**
128 * Get source values for transfer based on participant id in URL. Line items will
129 * be transferred to this participant - at this point no transaction changes processed
130 *
131 * return @void
132 *
133 * @throws \CRM_Core_Exception
134 */
135 public function preProcess() {
136 $session = CRM_Core_Session::singleton();
137 $this->_userContext = $session->readUserContext();
138 $this->_from_participant_id = CRM_Utils_Request::retrieve('pid', 'Positive', $this, FALSE, NULL, 'REQUEST');
139 $this->_userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE, NULL, 'REQUEST');
140 $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, NULL, 'REQUEST');
141 $params = ['id' => $this->_from_participant_id];
142 $participant = $values = [];
143 $this->_participant = CRM_Event_BAO_Participant::getValues($params, $values, $participant);
144 $this->_part_values = $values[$this->_from_participant_id];
145 $this->set('values', $this->_part_values);
146 $this->_event_id = $this->_part_values['event_id'];
147 $url = CRM_Utils_System::url('civicrm/event/info', "reset=1&id={$this->_event_id}");
148 $this->_from_contact_id = $this->_part_values['participant_contact_id'];
149 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($this->_from_contact_id, $this->_userChecksum);
150 if (!$validUser && !CRM_Core_Permission::check('edit all events')) {
151 CRM_Core_Error::statusBounce(ts('You do not have sufficient permission to transfer/cancel this participant.'), $url);
152 }
153 $this->assign('action', $this->_action);
154 if ($this->_from_participant_id) {
155 $this->assign('participantId', $this->_from_participant_id);
156 }
157
158 $daoName = 'title';
159 $this->_event_title = CRM_Event_BAO_Event::getFieldValue('CRM_Event_DAO_Event', $this->_event_id, $daoName);
160 $daoName = 'start_date';
161 $this->_event_start_date = CRM_Event_BAO_Event::getFieldValue('CRM_Event_DAO_Event', $this->_event_id, $daoName);
162 list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_from_contact_id);
163 $this->_contact_name = $displayName;
164 $this->_contact_email = $email;
165
166 $details = CRM_Event_BAO_Participant::participantDetails($this->_from_participant_id);
167 $selfServiceDetails = CRM_Event_BAO_Participant::getSelfServiceEligibility($this->_from_participant_id, $url, $this->isBackoffice);
168 $details = array_merge($details, $selfServiceDetails);
169 $this->assign('details', $details);
170 //This participant row will be cancelled. Get line item(s) to cancel
171 $this->selfsvctransferUrl = CRM_Utils_System::url('civicrm/event/selfsvcupdate',
172 "reset=1&id={$this->_from_participant_id}&id=0");
173 $this->selfsvctransferText = ts('Update');
174 $this->selfsvctransferButtonText = ts('Update');
175 }
176
177 /**
178 * Build form for input of transferree email, name
179 *
180 * return @void
181 */
182 public function buildQuickForm() {
183 // use entityRef select field for contact when this form is used by staff/admin user
184 if ($this->isBackoffice) {
185 $this->addEntityRef("contact_id", ts('Select Contact'), ['create' => TRUE], TRUE);
186 }
187 // for front-end user show and use the basic three fields used to create a contact
188 else {
189 $this->add('text', 'email', ts('To Email'), $this->_contact_email, TRUE);
190 $this->add('text', 'last_name', ts('To Last Name'), $this->_to_contact_last_name, TRUE);
191 $this->add('text', 'first_name', ts('To First Name'), $this->_to_contact_first_name, TRUE);
192 }
193
194 $this->addButtons([
195 [
196 'type' => 'submit',
197 'name' => ts('Transfer Registration'),
198 ],
199 ]);
200 $this->addFormRule(['CRM_Event_Form_SelfSvcTransfer', 'formRule'], $this);
201 parent::buildQuickForm();
202 }
203
204 /**
205 * Set defaults
206 *
207 * return @array _defaults
208 */
209 public function setDefaultValues() {
210 $this->_defaults = [];
211 return $this->_defaults;
212 }
213
214 /**
215 * Validate email and name input
216 *
217 * return array $errors
218 */
219 public static function formRule($fields, $files, $self) {
220 $errors = [];
221 if (!empty($fields['contact_id'])) {
222 $to_contact_id = $fields['contact_id'];
223 }
224 else {
225 //check that either an email or firstname+lastname is included in the form(CRM-9587)
226 $to_contact_id = self::checkProfileComplete($fields, $errors, $self);
227 }
228 //To check if the user is already registered for the event(CRM-2426)
229 if (!empty($to_contact_id)) {
230 self::checkRegistration($fields, $self, $to_contact_id, $errors);
231 }
232 //return parent::formrule($fields, $files, $self);
233 return empty($errors) ? TRUE : $errors;
234 }
235
236 /**
237 * Check whether profile (name, email) is complete
238 *
239 * return $contact_id
240 */
241 public static function checkProfileComplete($fields, &$errors, $self) {
242 $email = '';
243 foreach ($fields as $fieldname => $fieldvalue) {
244 if (substr($fieldname, 0, 5) == 'email' && $fieldvalue) {
245 $email = $fieldvalue;
246 }
247 }
248 if (empty($email) && (empty($fields['first_name']) || empty($fields['last_name']))) {
249 $message = ts("Mandatory fields (first name and last name, OR email address) are missing from this form.");
250 $errors['_qf_default'] = $message;
251 }
252 $contact = CRM_Contact_BAO_Contact::matchContactOnEmail($email, "");
253 $contact_id = empty($contact->contact_id) ? NULL : $contact->contact_id;
254 if (!CRM_Utils_Rule::email($fields['email'])) {
255 $errors['email'] = ts('Enter valid email address.');
256 }
257 if (empty($errors) && empty($contact_id)) {
258 $params = [
259 'email-Primary' => $fields['email'] ?? NULL,
260 'first_name' => $fields['first_name'] ?? NULL,
261 'last_name' => $fields['last_name'] ?? NULL,
262 'is_deleted' => $fields['is_deleted'] ?? FALSE,
263 ];
264 //create new contact for this name/email pair
265 //if new contact, no need to check for contact already registered
266 $contact_id = CRM_Contact_BAO_Contact::createProfileContact($params, $fields, $contact_id);
267 }
268 return $contact_id;
269 }
270
271 /**
272 * Check contact details
273 *
274 * return @void
275 */
276 public static function checkRegistration($fields, $self, $contact_id, &$errors) {
277 // verify whether this contact already registered for this event
278 $contact_details = CRM_Contact_BAO_Contact::getContactDetails($contact_id);
279 $display_name = $contact_details[0];
280 $query = 'select event_id from civicrm_participant where contact_id = ' . $contact_id;
281 $dao = CRM_Core_DAO::executeQuery($query);
282 while ($dao->fetch()) {
283 $to_event_id[] = $dao->event_id;
284 }
285 if (!empty($to_event_id)) {
286 foreach ($to_event_id as $id) {
287 if ($id == $self->_event_id) {
288 $errors['email'] = $display_name . ts(" is already registered for this event");
289 }
290 }
291 }
292 }
293
294 /**
295 * Process transfer - first add the new participant to the event, then cancel
296 * source participant - send confirmation email to transferee
297 *
298 * @throws \CiviCRM_API3_Exception
299 */
300 public function postProcess() {
301 //For transfer, process form to allow selection of transferree
302 $params = $this->controller->exportValues($this->_name);
303 if (!empty($params['contact_id'])) {
304 $contact_id = $params['contact_id'];
305 }
306 else {
307 //cancel 'from' participant row
308 $contact_id_result = civicrm_api3('Contact', 'get', [
309 'sequential' => 1,
310 'return' => ['id'],
311 'email' => $params['email'],
312 'options' => ['limit' => 1],
313 ]);
314 $contact_id_result = $contact_id_result['values'][0];
315 $contact_id = $contact_id_result['contact_id'];
316 $contact_is_deleted = $contact_id_result['contact_is_deleted'];
317 if ($contact_is_deleted || !is_numeric($contact_id)) {
318 CRM_Core_Error::statusBounce(ts('Contact does not exist.'));
319 }
320 }
321
322 $this->transferParticipantRegistration($contact_id, $this->_from_participant_id);
323
324 $contact_details = CRM_Contact_BAO_Contact::getContactDetails($contact_id);
325 $display_name = current($contact_details);
326 $this->assign('to_participant', $display_name);
327 $this->sendCancellation();
328 list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contact_id);
329 $statusMsg = ts('Event registration information for %1 has been updated.', [1 => $displayName]);
330 $statusMsg .= ' ' . ts('A confirmation email has been sent to %1.', [1 => $email]);
331 CRM_Core_Session::setStatus($statusMsg, ts('Registration Transferred'), 'success');
332 if ($this->isBackoffice) {
333 return;
334 }
335 $url = CRM_Utils_System::url('civicrm/event/info', "reset=1&id={$this->_event_id}");
336 CRM_Utils_System::redirect($url);
337 }
338
339 /**
340 * Based on input, create participant row for transferee and send email
341 *
342 * return @ void
343 *
344 * @throws \CRM_Core_Exception
345 */
346 public function participantTransfer($participant) {
347 $contactDetails = [];
348 $contactIds[] = $participant->contact_id;
349 list($currentContactDetails) = CRM_Utils_Token::getTokenDetails($contactIds, NULL,
350 FALSE, FALSE, NULL, [], 'CRM_Event_BAO_Participant');
351 foreach ($currentContactDetails as $contactId => $contactValues) {
352 $contactDetails[$contactId] = $contactValues;
353 }
354 $participantRoles = CRM_Event_PseudoConstant::participantRole();
355 $participantDetails = [];
356 $query = "SELECT * FROM civicrm_participant WHERE id = " . $participant->id;
357 $dao = CRM_Core_DAO::executeQuery($query);
358 while ($dao->fetch()) {
359 $participantDetails[$dao->id] = [
360 'id' => $dao->id,
361 'role' => $participantRoles[$dao->role_id],
362 'is_test' => $dao->is_test,
363 'event_id' => $dao->event_id,
364 'status_id' => $dao->status_id,
365 'fee_amount' => $dao->fee_amount,
366 'contact_id' => $dao->contact_id,
367 'register_date' => $dao->register_date,
368 'registered_by_id' => $dao->registered_by_id,
369 ];
370 }
371 $domainValues = [];
372 if (empty($domainValues)) {
373 $domain = CRM_Core_BAO_Domain::getDomain();
374 $tokens = [
375 'domain' =>
376 [
377 'name',
378 'phone',
379 'address',
380 'email',
381 ],
382 'contact' => CRM_Core_SelectValues::contactTokens(),
383 ];
384 foreach ($tokens['domain'] as $token) {
385 $domainValues[$token] = CRM_Utils_Token::getDomainTokenReplacement($token, $domain);
386 }
387 }
388 $eventDetails = [];
389 $eventParams = ['id' => $participant->event_id];
390 CRM_Event_BAO_Event::retrieve($eventParams, $eventDetails);
391 //get default participant role.
392 $eventDetails['participant_role'] = $participantRoles[$eventDetails['default_role_id']] ?? NULL;
393 //get the location info
394 $locParams = [
395 'entity_id' => $participant->event_id,
396 'entity_table' => 'civicrm_event',
397 ];
398 $eventDetails['location'] = CRM_Core_BAO_Location::getValues($locParams, TRUE);
399 $toEmail = $contactDetails[$participant->contact_id]['email'] ?? NULL;
400 if ($toEmail) {
401 //take a receipt from as event else domain.
402 $receiptFrom = $domainValues['name'] . ' <' . $domainValues['email'] . '>';
403 if (!empty($eventDetails['confirm_from_name']) && !empty($eventDetails['confirm_from_email'])) {
404 $receiptFrom = $eventDetails['confirm_from_name'] . ' <' . $eventDetails['confirm_from_email'] . '>';
405 }
406 $participantName = $contactDetails[$participant->contact_id]['display_name'];
407 $tplParams = [
408 'event' => $eventDetails,
409 'participant' => $participantDetails[$participant->id],
410 'participantID' => $participant->id,
411 'participant_status' => 'Registered',
412 ];
413
414 $sendTemplateParams = [
415 'groupName' => 'msg_tpl_workflow_event',
416 'valueName' => 'event_online_receipt',
417 'contactId' => $participantDetails[$participant->id]['contact_id'],
418 'tplParams' => $tplParams,
419 'from' => $receiptFrom,
420 'toName' => $participantName,
421 'toEmail' => $toEmail,
422 'cc' => $eventDetails['cc_confirm'] ?? NULL,
423 'bcc' => $eventDetails['bcc_confirm'] ?? NULL,
424 ];
425 CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
426 }
427 }
428
429 /**
430 * Send confirmation of cancellation to source participant
431 *
432 * return @ void
433 */
434 public function sendCancellation() {
435 $domainValues = [];
436 $domain = CRM_Core_BAO_Domain::getDomain();
437 $tokens = [
438 'domain' =>
439 [
440 'name',
441 'phone',
442 'address',
443 'email',
444 ],
445 'contact' => CRM_Core_SelectValues::contactTokens(),
446 ];
447 foreach ($tokens['domain'] as $token) {
448 $domainValues[$token] = CRM_Utils_Token::getDomainTokenReplacement($token, $domain);
449 }
450
451 $participantRoles = CRM_Event_PseudoConstant::participantRole();
452 $participantDetails = [];
453 $query = "SELECT * FROM civicrm_participant WHERE id = {$this->_from_participant_id}";
454 $dao = CRM_Core_DAO::executeQuery($query);
455 while ($dao->fetch()) {
456 $participantDetails[$dao->id] = [
457 'id' => $dao->id,
458 'role' => $participantRoles[$dao->role_id],
459 'is_test' => $dao->is_test,
460 'event_id' => $dao->event_id,
461 'status_id' => $dao->status_id,
462 'fee_amount' => $dao->fee_amount,
463 'contact_id' => $dao->contact_id,
464 'register_date' => $dao->register_date,
465 'registered_by_id' => $dao->registered_by_id,
466 ];
467 }
468 $eventDetails = [];
469 $eventParams = ['id' => $this->_event_id];
470 CRM_Event_BAO_Event::retrieve($eventParams, $eventDetails[$this->_event_id]);
471 //get default participant role.
472 $eventDetails[$this->_event_id]['participant_role'] = $participantRoles[$eventDetails[$this->_event_id]['default_role_id']] ?? NULL;
473 //get the location info
474 $locParams = ['entity_id' => $this->_event_id, 'entity_table' => 'civicrm_event'];
475 $eventDetails[$this->_event_id]['location'] = CRM_Core_BAO_Location::getValues($locParams, TRUE);
476 //get contact details
477 $contactIds[$this->_from_contact_id] = $this->_from_contact_id;
478 list($currentContactDetails) = CRM_Utils_Token::getTokenDetails($contactIds, NULL,
479 FALSE, FALSE, NULL, [],
480 'CRM_Event_BAO_Participant'
481 );
482 foreach ($currentContactDetails as $contactId => $contactValues) {
483 $contactDetails[$this->_from_contact_id] = $contactValues;
484 }
485 //send a 'cancelled' email to user, and cc the event's cc_confirm email
486 $mail = CRM_Event_BAO_Participant::sendTransitionParticipantMail($this->_from_participant_id,
487 $participantDetails[$this->_from_participant_id],
488 $eventDetails[$this->_event_id],
489 $contactDetails[$this->_from_contact_id],
490 $domainValues,
491 "Transferred",
492 ""
493 );
494 $statusMsg = ts('Event registration information for %1 has been updated.', [1 => $this->_contact_name]);
495 $statusMsg .= ' ' . ts('A cancellation email has been sent to %1.', [1 => $this->_contact_email]);
496 CRM_Core_Session::setStatus($statusMsg, ts('Thanks'), 'success');
497 }
498
499 /**
500 * Move Participant registration to new contact.
501 *
502 * @param int $toContactID
503 * @param int $fromParticipantID
504 *
505 * @throws \CRM_Core_Exception
506 * @throws \CiviCRM_API3_Exception
507 */
508 public function transferParticipantRegistration($toContactID, $fromParticipantID) {
509 $toParticipantValues = \Civi\Api4\Participant::get()
510 ->addWhere('id', '=', $fromParticipantID)
511 ->execute()
512 ->first();
513
514 unset($toParticipantValues['id']);
515 $toParticipantValues['contact_id'] = $toContactID;
516 $toParticipantValues['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Event_BAO_Participant', 'status_id', 'Registered');
517 $toParticipantValues['register_date'] = date("Y-m-d");
518 //first create the new participant row -don't set registered_by yet or email won't be sent
519 $participant = CRM_Event_BAO_Participant::create($toParticipantValues);
520
521 //send a confirmation email to the new participant
522 $this->participantTransfer($participant);
523 //now update registered_by_id
524 $query = "UPDATE civicrm_participant cp SET cp.registered_by_id = %1 WHERE cp.id = ({$participant->id})";
525 $params = [1 => [$fromParticipantID, 'Integer']];
526 CRM_Core_DAO::executeQuery($query, $params);
527 //copy line items to new participant
528 $line_items = CRM_Price_BAO_LineItem::getLineItems($fromParticipantID);
529 foreach ($line_items as $id => $item) {
530 //Remove contribution id from older participant line item.
531 CRM_Core_DAO::singleValueQuery("UPDATE civicrm_line_item SET contribution_id = NULL WHERE id = %1", [1 => [$id, 'Integer']]);
532 $item['entity_id'] = $participant->id;
533 $item['id'] = NULL;
534 $item['entity_table'] = "civicrm_participant";
535 $tolineItem = CRM_Price_BAO_LineItem::create($item);
536
537 //Update Financial Item for previous line item row.
538 $prevFinancialItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($id);
539 $prevFinancialItem['contact_id'] = $toContactID;
540 $prevFinancialItem['entity_id'] = $tolineItem->id;
541 CRM_Financial_BAO_FinancialItem::create($prevFinancialItem);
542 }
543 //now cancel the from participant record, leaving the original line-item(s)
544 $value_from = [];
545 $value_from['id'] = $fromParticipantID;
546 $tansferId = array_search('Transferred', CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Negative'"));
547 $value_from['status_id'] = $tansferId;
548 $value_from['transferred_to_contact_id'] = $toContactID;
549 CRM_Event_BAO_Participant::create($value_from);
550 }
551
552 }