Merge pull request #23311 from colemanw/unusedEvent
[civicrm-core.git] / CRM / Event / Form / SelfSvcTransfer.php
CommitLineData
97d8187a
DG
1<?php
2
3/*
4 +--------------------------------------------------------------------+
bc77d7c0 5 | Copyright CiviCRM LLC. All rights reserved. |
97d8187a 6 | |
bc77d7c0
TO
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 |
97d8187a
DG
10 +--------------------------------------------------------------------+
11 */
12
13/**
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
97d8187a
DG
17 */
18
19/**
20 * This class generates form components to transfer an Event to another participant
21 *
22 */
23class CRM_Event_Form_SelfSvcTransfer extends CRM_Core_Form {
24 /**
fc02b60e 25 * from participant id
97d8187a
DG
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 /**
fc02b60e 39 * last name of the participant to transfer to
97d8187a
DG
40 *
41 * @var string
42 *
43 */
44 protected $_to_contact_last_name;
45 /**
fc02b60e 46 * first name of the participant to transfer to
97d8187a
DG
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 */
3a936dab 88 public $_action;
97d8187a
DG
89 /**
90 * participant object
91 *
92 * @var string
93 */
be2fb01f 94 protected $_participant = [];
97d8187a 95 /**
fc02b60e 96 * participant values
97d8187a 97 *
90b461f1 98 * @var string
97d8187a
DG
99 */
100 protected $_part_values;
101 /**
102 * details
103 *
90b461f1 104 * @var array
97d8187a 105 */
be2fb01f 106 protected $_details = [];
97d8187a
DG
107 /**
108 * line items
109 *
90b461f1 110 * @var array
97d8187a 111 */
be2fb01f 112 protected $_line_items = [];
97d8187a
DG
113 /**
114 * contact_id
115 *
90b461f1 116 * @var int
97d8187a
DG
117 */
118 protected $contact_id;
974bdb44 119
e219d53b 120 /**
121 * Is backoffice form?
122 *
90b461f1 123 * @var bool
e219d53b 124 */
125 protected $isBackoffice = FALSE;
126
97d8187a
DG
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
5db18d97 132 *
133 * @throws \CRM_Core_Exception
97d8187a
DG
134 */
135 public function preProcess() {
97d8187a
DG
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');
af29aaac 139 $this->_userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE, NULL, 'REQUEST');
c0fc0432 140 $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, NULL, 'REQUEST') ?? FALSE;
be2fb01f
CW
141 $params = ['id' => $this->_from_participant_id];
142 $participant = $values = [];
97d8187a
DG
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'];
af29aaac 147 $url = CRM_Utils_System::url('civicrm/event/info', "reset=1&id={$this->_event_id}");
97d8187a 148 $this->_from_contact_id = $this->_part_values['participant_contact_id'];
af29aaac 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 }
97d8187a
DG
153 $this->assign('action', $this->_action);
154 if ($this->_from_participant_id) {
155 $this->assign('participantId', $this->_from_participant_id);
156 }
5db18d97 157
97d8187a
DG
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);
96ea99c8 162 [$displayName, $email] = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_from_contact_id);
97d8187a
DG
163 $this->_contact_name = $displayName;
164 $this->_contact_email = $email;
5db18d97 165
97d8187a 166 $details = CRM_Event_BAO_Participant::participantDetails($this->_from_participant_id);
4a3451de 167 $selfServiceDetails = CRM_Event_BAO_Participant::getSelfServiceEligibility($this->_from_participant_id, $url, $this->isBackoffice);
c0fc0432
JG
168 if (!$selfServiceDetails['eligible']) {
169 CRM_Core_Error::statusBounce($selfServiceDetails['ineligible_message'], $url, ts('Sorry'));
170 }
4a3451de 171 $details = array_merge($details, $selfServiceDetails);
97d8187a
DG
172 $this->assign('details', $details);
173 //This participant row will be cancelled. Get line item(s) to cancel
174 $this->selfsvctransferUrl = CRM_Utils_System::url('civicrm/event/selfsvcupdate',
175 "reset=1&id={$this->_from_participant_id}&id=0");
176 $this->selfsvctransferText = ts('Update');
177 $this->selfsvctransferButtonText = ts('Update');
178 }
974bdb44 179
97d8187a
DG
180 /**
181 * Build form for input of transferree email, name
182 *
183 * return @void
184 */
185 public function buildQuickForm() {
e219d53b 186 // use entityRef select field for contact when this form is used by staff/admin user
187 if ($this->isBackoffice) {
be2fb01f 188 $this->addEntityRef("contact_id", ts('Select Contact'), ['create' => TRUE], TRUE);
e219d53b 189 }
190 // for front-end user show and use the basic three fields used to create a contact
191 else {
6dabf459
ML
192 $this->add('text', 'email', ts('To Email'), $this->_contact_email, TRUE);
193 $this->add('text', 'last_name', ts('To Last Name'), $this->_to_contact_last_name, TRUE);
194 $this->add('text', 'first_name', ts('To First Name'), $this->_to_contact_first_name, TRUE);
e219d53b 195 }
196
be2fb01f
CW
197 $this->addButtons([
198 [
97d8187a 199 'type' => 'submit',
be2fb01f
CW
200 'name' => ts('Transfer Registration'),
201 ],
202 ]);
203 $this->addFormRule(['CRM_Event_Form_SelfSvcTransfer', 'formRule'], $this);
97d8187a
DG
204 parent::buildQuickForm();
205 }
974bdb44 206
97d8187a
DG
207 /**
208 * Set defaults
209 *
210 * return @array _defaults
211 */
212 public function setDefaultValues() {
be2fb01f 213 $this->_defaults = [];
97d8187a
DG
214 return $this->_defaults;
215 }
974bdb44 216
97d8187a
DG
217 /**
218 * Validate email and name input
219 *
220 * return array $errors
221 */
222 public static function formRule($fields, $files, $self) {
be2fb01f 223 $errors = [];
e219d53b 224 if (!empty($fields['contact_id'])) {
225 $to_contact_id = $fields['contact_id'];
226 }
227 else {
228 //check that either an email or firstname+lastname is included in the form(CRM-9587)
229 $to_contact_id = self::checkProfileComplete($fields, $errors, $self);
230 }
97d8187a 231 //To check if the user is already registered for the event(CRM-2426)
e219d53b 232 if (!empty($to_contact_id)) {
e3b510fe 233 self::checkRegistration($fields, $self, $to_contact_id, $errors);
234 }
97d8187a
DG
235 //return parent::formrule($fields, $files, $self);
236 return empty($errors) ? TRUE : $errors;
237 }
974bdb44 238
97d8187a
DG
239 /**
240 * Check whether profile (name, email) is complete
241 *
242 * return $contact_id
243 */
244 public static function checkProfileComplete($fields, &$errors, $self) {
245 $email = '';
246 foreach ($fields as $fieldname => $fieldvalue) {
247 if (substr($fieldname, 0, 5) == 'email' && $fieldvalue) {
248 $email = $fieldvalue;
249 }
250 }
99afef64 251 if (empty($email) && (empty($fields['first_name']) || empty($fields['last_name']))) {
97d8187a
DG
252 $message = ts("Mandatory fields (first name and last name, OR email address) are missing from this form.");
253 $errors['_qf_default'] = $message;
254 }
255 $contact = CRM_Contact_BAO_Contact::matchContactOnEmail($email, "");
256 $contact_id = empty($contact->contact_id) ? NULL : $contact->contact_id;
e3b510fe 257 if (!CRM_Utils_Rule::email($fields['email'])) {
258 $errors['email'] = ts('Enter valid email address.');
259 }
260 if (empty($errors) && empty($contact_id)) {
be2fb01f 261 $params = [
6187cca4
CW
262 'email-Primary' => $fields['email'] ?? NULL,
263 'first_name' => $fields['first_name'] ?? NULL,
264 'last_name' => $fields['last_name'] ?? NULL,
265 'is_deleted' => $fields['is_deleted'] ?? FALSE,
be2fb01f 266 ];
97d8187a
DG
267 //create new contact for this name/email pair
268 //if new contact, no need to check for contact already registered
269 $contact_id = CRM_Contact_BAO_Contact::createProfileContact($params, $fields, $contact_id);
270 }
271 return $contact_id;
272 }
974bdb44 273
97d8187a
DG
274 /**
275 * Check contact details
276 *
277 * return @void
278 */
4abc4ee1 279 public static function checkRegistration($fields, $self, $contact_id, &$errors) {
97d8187a 280 // verify whether this contact already registered for this event
97d8187a
DG
281 $contact_details = CRM_Contact_BAO_Contact::getContactDetails($contact_id);
282 $display_name = $contact_details[0];
5db18d97 283 $query = 'select event_id from civicrm_participant where contact_id = ' . $contact_id;
33621c4f 284 $dao = CRM_Core_DAO::executeQuery($query);
97d8187a 285 while ($dao->fetch()) {
90b461f1 286 $to_event_id[] = $dao->event_id;
97d8187a
DG
287 }
288 if (!empty($to_event_id)) {
289 foreach ($to_event_id as $id) {
290 if ($id == $self->_event_id) {
4abc4ee1 291 $errors['email'] = $display_name . ts(" is already registered for this event");
97d8187a
DG
292 }
293 }
294 }
295 }
974bdb44 296
97d8187a
DG
297 /**
298 * Process transfer - first add the new participant to the event, then cancel
299 * source participant - send confirmation email to transferee
5db18d97 300 *
301 * @throws \CiviCRM_API3_Exception
97d8187a
DG
302 */
303 public function postProcess() {
304 //For transfer, process form to allow selection of transferree
305 $params = $this->controller->exportValues($this->_name);
e219d53b 306 if (!empty($params['contact_id'])) {
307 $contact_id = $params['contact_id'];
308 }
309 else {
310 //cancel 'from' participant row
be2fb01f 311 $contact_id_result = civicrm_api3('Contact', 'get', [
d4a64d9a 312 'sequential' => 1,
5db18d97 313 'return' => ['id'],
d4a64d9a 314 'email' => $params['email'],
be2fb01f
CW
315 'options' => ['limit' => 1],
316 ]);
d4a64d9a
AS
317 $contact_id_result = $contact_id_result['values'][0];
318 $contact_id = $contact_id_result['contact_id'];
319 $contact_is_deleted = $contact_id_result['contact_is_deleted'];
320 if ($contact_is_deleted || !is_numeric($contact_id)) {
2ddf0fcd 321 CRM_Core_Error::statusBounce(ts('Contact does not exist.'));
e219d53b 322 }
97d8187a 323 }
5db18d97 324
99afef64 325 $this->transferParticipantRegistration($contact_id, $this->_from_participant_id);
4720af1a 326
97d8187a
DG
327 $contact_details = CRM_Contact_BAO_Contact::getContactDetails($contact_id);
328 $display_name = current($contact_details);
329 $this->assign('to_participant', $display_name);
97d8187a
DG
330 $this->sendCancellation();
331 list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contact_id);
be2fb01f
CW
332 $statusMsg = ts('Event registration information for %1 has been updated.', [1 => $displayName]);
333 $statusMsg .= ' ' . ts('A confirmation email has been sent to %1.', [1 => $email]);
974bdb44 334 CRM_Core_Session::setStatus($statusMsg, ts('Registration Transferred'), 'success');
e219d53b 335 if ($this->isBackoffice) {
336 return;
337 }
e3b510fe 338 $url = CRM_Utils_System::url('civicrm/event/info', "reset=1&id={$this->_event_id}");
339 CRM_Utils_System::redirect($url);
97d8187a 340 }
974bdb44 341
97d8187a
DG
342 /**
343 * Based on input, create participant row for transferee and send email
344 *
fa3fdebc 345 * @param CRM_Event_BAO_Participant $participant
5db18d97 346 *
96ea99c8 347 * @throws \API_Exception
5db18d97 348 * @throws \CRM_Core_Exception
96ea99c8 349 * @throws \CiviCRM_API3_Exception
97d8187a 350 */
96ea99c8
EM
351 public function participantTransfer($participant): void {
352 $contactDetails = civicrm_api3('Contact', 'getsingle', ['id' => $participant->contact_id, 'return' => ['display_name', 'email']]);
353
97d8187a 354 $participantRoles = CRM_Event_PseudoConstant::participantRole();
be2fb01f 355 $participantDetails = [];
96ea99c8 356 $query = 'SELECT * FROM civicrm_participant WHERE id = ' . $participant->id;
97d8187a
DG
357 $dao = CRM_Core_DAO::executeQuery($query);
358 while ($dao->fetch()) {
be2fb01f 359 $participantDetails[$dao->id] = [
97d8187a
DG
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,
be2fb01f 369 ];
97d8187a 370 }
96ea99c8 371
be2fb01f
CW
372 $eventDetails = [];
373 $eventParams = ['id' => $participant->event_id];
e3b510fe 374 CRM_Event_BAO_Event::retrieve($eventParams, $eventDetails);
bdf8d7dd 375
97d8187a 376 //get default participant role.
9c1bc317 377 $eventDetails['participant_role'] = $participantRoles[$eventDetails['default_role_id']] ?? NULL;
97d8187a 378 //get the location info
be2fb01f 379 $locParams = [
97d8187a
DG
380 'entity_id' => $participant->event_id,
381 'entity_table' => 'civicrm_event',
be2fb01f 382 ];
e3b510fe 383 $eventDetails['location'] = CRM_Core_BAO_Location::getValues($locParams, TRUE);
96ea99c8 384 $toEmail = $contactDetails['email'] ?? NULL;
e3b510fe 385 if ($toEmail) {
386 //take a receipt from as event else domain.
96ea99c8
EM
387 $receiptFrom = CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE);
388 $receiptFrom = reset($receiptFrom);
e3b510fe 389 if (!empty($eventDetails['confirm_from_name']) && !empty($eventDetails['confirm_from_email'])) {
390 $receiptFrom = $eventDetails['confirm_from_name'] . ' <' . $eventDetails['confirm_from_email'] . '>';
391 }
96ea99c8 392 $participantName = $contactDetails['display_name'];
be2fb01f 393 $tplParams = [
e3b510fe 394 'event' => $eventDetails,
395 'participant' => $participantDetails[$participant->id],
396 'participantID' => $participant->id,
397 'participant_status' => 'Registered',
be2fb01f 398 ];
e3b510fe 399
be2fb01f 400 $sendTemplateParams = [
e3b510fe 401 'groupName' => 'msg_tpl_workflow_event',
402 'valueName' => 'event_online_receipt',
403 'contactId' => $participantDetails[$participant->id]['contact_id'],
404 'tplParams' => $tplParams,
405 'from' => $receiptFrom,
406 'toName' => $participantName,
407 'toEmail' => $toEmail,
6b409353
CW
408 'cc' => $eventDetails['cc_confirm'] ?? NULL,
409 'bcc' => $eventDetails['bcc_confirm'] ?? NULL,
be2fb01f 410 ];
e3b510fe 411 CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
412 }
97d8187a 413 }
974bdb44 414
97d8187a
DG
415 /**
416 * Send confirmation of cancellation to source participant
417 *
418 * return @ void
419 */
420 public function sendCancellation() {
97d8187a 421 $participantRoles = CRM_Event_PseudoConstant::participantRole();
be2fb01f 422 $participantDetails = [];
97d8187a
DG
423 $query = "SELECT * FROM civicrm_participant WHERE id = {$this->_from_participant_id}";
424 $dao = CRM_Core_DAO::executeQuery($query);
425 while ($dao->fetch()) {
be2fb01f 426 $participantDetails[$dao->id] = [
97d8187a
DG
427 'id' => $dao->id,
428 'role' => $participantRoles[$dao->role_id],
429 'is_test' => $dao->is_test,
430 'event_id' => $dao->event_id,
431 'status_id' => $dao->status_id,
432 'fee_amount' => $dao->fee_amount,
433 'contact_id' => $dao->contact_id,
434 'register_date' => $dao->register_date,
435 'registered_by_id' => $dao->registered_by_id,
be2fb01f 436 ];
97d8187a 437 }
be2fb01f
CW
438 $eventDetails = [];
439 $eventParams = ['id' => $this->_event_id];
97d8187a
DG
440 CRM_Event_BAO_Event::retrieve($eventParams, $eventDetails[$this->_event_id]);
441 //get default participant role.
9c1bc317 442 $eventDetails[$this->_event_id]['participant_role'] = $participantRoles[$eventDetails[$this->_event_id]['default_role_id']] ?? NULL;
97d8187a 443 //get the location info
be2fb01f 444 $locParams = ['entity_id' => $this->_event_id, 'entity_table' => 'civicrm_event'];
97d8187a 445 $eventDetails[$this->_event_id]['location'] = CRM_Core_BAO_Location::getValues($locParams, TRUE);
97d8187a 446 //send a 'cancelled' email to user, and cc the event's cc_confirm email
0d6fa54d 447 CRM_Event_BAO_Participant::sendTransitionParticipantMail($this->_from_participant_id,
97d8187a
DG
448 $participantDetails[$this->_from_participant_id],
449 $eventDetails[$this->_event_id],
0d6fa54d 450 NULL,
0d6fa54d 451 'Transferred'
97d8187a 452 );
be2fb01f
CW
453 $statusMsg = ts('Event registration information for %1 has been updated.', [1 => $this->_contact_name]);
454 $statusMsg .= ' ' . ts('A cancellation email has been sent to %1.', [1 => $this->_contact_email]);
8b9023b8 455 CRM_Core_Session::setStatus($statusMsg, ts('Thanks'), 'success');
97d8187a
DG
456 }
457
4720af1a 458 /**
99afef64 459 * Move Participant registration to new contact.
4720af1a 460 *
461 * @param int $toContactID
462 * @param int $fromParticipantID
4720af1a 463 *
464 * @throws \CRM_Core_Exception
465 * @throws \CiviCRM_API3_Exception
466 */
99afef64
JP
467 public function transferParticipantRegistration($toContactID, $fromParticipantID) {
468 $toParticipantValues = \Civi\Api4\Participant::get()
469 ->addWhere('id', '=', $fromParticipantID)
470 ->execute()
471 ->first();
da610957
EM
472 $participantPayments = civicrm_api3('ParticipantPayment', 'get', [
473 'return' => 'id',
474 'participant_id' => $fromParticipantID,
475 ])['values'];
99afef64
JP
476 unset($toParticipantValues['id']);
477 $toParticipantValues['contact_id'] = $toContactID;
478 $toParticipantValues['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Event_BAO_Participant', 'status_id', 'Registered');
479 $toParticipantValues['register_date'] = date("Y-m-d");
4720af1a 480 //first create the new participant row -don't set registered_by yet or email won't be sent
99afef64 481 $participant = CRM_Event_BAO_Participant::create($toParticipantValues);
da610957
EM
482 foreach ($participantPayments as $payment) {
483 civicrm_api3('ParticipantPayment', 'create', ['id' => $payment['id'], 'participant_id' => $participant->id]);
484 }
4720af1a 485 //send a confirmation email to the new participant
486 $this->participantTransfer($participant);
487 //now update registered_by_id
488 $query = "UPDATE civicrm_participant cp SET cp.registered_by_id = %1 WHERE cp.id = ({$participant->id})";
489 $params = [1 => [$fromParticipantID, 'Integer']];
99afef64 490 CRM_Core_DAO::executeQuery($query, $params);
4720af1a 491 //copy line items to new participant
99afef64
JP
492 $line_items = CRM_Price_BAO_LineItem::getLineItems($fromParticipantID);
493 foreach ($line_items as $id => $item) {
494 //Remove contribution id from older participant line item.
da610957
EM
495 CRM_Core_DAO::singleValueQuery('UPDATE civicrm_line_item SET contribution_id = NULL WHERE id = %1', [1 => [$id, 'Integer']]);
496
4720af1a 497 $item['entity_id'] = $participant->id;
498 $item['id'] = NULL;
499 $item['entity_table'] = "civicrm_participant";
99afef64
JP
500 $tolineItem = CRM_Price_BAO_LineItem::create($item);
501
502 //Update Financial Item for previous line item row.
503 $prevFinancialItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($id);
504 $prevFinancialItem['contact_id'] = $toContactID;
505 $prevFinancialItem['entity_id'] = $tolineItem->id;
506 CRM_Financial_BAO_FinancialItem::create($prevFinancialItem);
4720af1a 507 }
508 //now cancel the from participant record, leaving the original line-item(s)
509 $value_from = [];
510 $value_from['id'] = $fromParticipantID;
511 $tansferId = array_search('Transferred', CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Negative'"));
512 $value_from['status_id'] = $tansferId;
513 $value_from['transferred_to_contact_id'] = $toContactID;
514 CRM_Event_BAO_Participant::create($value_from);
515 }
516
97d8187a 517}