* Evaluate whether a participant record is eligible for self-service transfer/cancellation. If so,
* return additional participant/event details.
*
- * TODO: BAO-level functions shouldn't set a redirect, and it should be possible to return "false" to the
- * calling function. The next refactor will add a fourth param $errors, which can be passed by reference
- * from the calling function. Instead of redirecting, we will return the error.
- * TODO: This function should always return FALSE when self-service has been disabled on an event.
- * TODO: This function fails when the "hours until self-service" is greater than 24 or less than zero.
+ * TODO: This function fails when the "hours until self-service" is less than zero.
* @param int $participantId
* @param string $url
* @param bool $isBackOffice
*/
- public static function getSelfServiceEligibility($participantId, $url, $isBackOffice) {
+ public static function getSelfServiceEligibility(int $participantId, string $url, bool $isBackOffice) : array {
$optionGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'participant_role', 'id', 'name');
$query = "
- SELECT cpst.name as status, cov.name as role, cp.fee_level, cp.fee_amount, cp.register_date, cp.status_id, ce.start_date, ce.title, cp.event_id
+ SELECT cpst.name as status, cov.name as role, cp.fee_level, cp.fee_amount, cp.register_date, cp.status_id, ce.start_date, ce.title, cp.event_id, ce.allow_selfcancelxfer
FROM civicrm_participant cp
LEFT JOIN civicrm_participant_status_type cpst ON cpst.id = cp.status_id
LEFT JOIN civicrm_option_value cov ON cov.value = cp.role_id and cov.option_group_id = {$optionGroupId}
WHERE cp.id = {$participantId}";
$dao = CRM_Core_DAO::executeQuery($query);
while ($dao->fetch()) {
+ $details['eligible'] = TRUE;
$details['status'] = $dao->status;
$details['role'] = $dao->role;
$details['fee_level'] = trim($dao->fee_level, CRM_Core_DAO::VALUE_SEPARATOR);
$details['fee_amount'] = $dao->fee_amount;
$details['register_date'] = $dao->register_date;
$details['event_start_date'] = $dao->start_date;
+ $details['allow_selfcancelxfer'] = $dao->allow_selfcancelxfer;
$eventTitle = $dao->title;
$eventId = $dao->event_id;
}
+ if (!$details['allow_selfcancelxfer']) {
+ $details['eligible'] = FALSE;
+ $details['ineligible_message'] = ts('This event registration can not be transferred or cancelled. Contact the event organizer if you have questions.');
+ return $details;
+ }
//verify participant status is still Registered
if ($details['status'] != 'Registered') {
- $status = "You cannot transfer or cancel your registration for " . $eventTitle . ' as you are not currently registered for this event.';
- CRM_Core_Session::setStatus($status, ts('Sorry'), 'alert');
- CRM_Utils_System::redirect($url);
+ $details['eligible'] = FALSE;
+ $details['ineligible_message'] = "You cannot transfer or cancel your registration for " . $eventTitle . ' as you are not currently registered for this event.';
+ return $details;
}
+ // Determine if it's too late to self-service cancel/transfer.
$query = "select start_date as start, selfcancelxfer_time as time from civicrm_event where id = " . $eventId;
$dao = CRM_Core_DAO::executeQuery($query);
while ($dao->fetch()) {
$time_limit = $dao->time;
$start_date = $dao->start;
}
- $start_time = new Datetime($start_date);
$timenow = new Datetime();
- if (!$isBackOffice && !empty($start_time) && $start_time < $timenow) {
- $status = ts('Registration for this event cannot be cancelled or transferred once the event has begun. Contact the event organizer if you have questions.');
- CRM_Core_Error::statusBounce($status, $url, ts('Sorry'));
- }
- if (!$isBackOffice && !empty($time_limit) && $time_limit > 0) {
- $interval = $timenow->diff($start_time);
- $days = $interval->format('%d');
- $hours = $interval->format('%h');
- if ($hours <= $time_limit && $days < 1) {
- $status = ts("Registration for this event cannot be cancelled or transferred less than %1 hours prior to the event's start time. Contact the event organizer if you have questions.", [1 => $time_limit]);
- CRM_Core_Error::statusBounce($status, $url, ts('Sorry'));
+ if (!$isBackOffice && !empty($time_limit)) {
+ $cancelHours = abs($time_limit);
+ $cancelInterval = new DateInterval("PT${cancelHours}H");
+ $cancelInterval->invert = $time_limit < 0 ? 1 : 0;
+ $cancelDeadline = (new Datetime($start_date))->sub($cancelInterval);
+ if ($timenow > $cancelDeadline) {
+ $details['eligible'] = FALSE;
+ $details['ineligible_message'] = ts("Registration for this event cannot be cancelled or transferred less than %1 hours prior to the event's start time. Contact the event organizer if you have questions.", [1 => $time_limit]);
}
}
return $details;
$this->_userContext = $session->readUserContext();
$this->_from_participant_id = CRM_Utils_Request::retrieve('pid', 'Positive', $this, FALSE, NULL, 'REQUEST');
$this->_userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE, NULL, 'REQUEST');
- $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, NULL, 'REQUEST');
+ $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, NULL, 'REQUEST') ?? FALSE;
$params = ['id' => $this->_from_participant_id];
$participant = $values = [];
$this->_participant = CRM_Event_BAO_Participant::getValues($params, $values, $participant);
$details = CRM_Event_BAO_Participant::participantDetails($this->_from_participant_id);
$selfServiceDetails = CRM_Event_BAO_Participant::getSelfServiceEligibility($this->_from_participant_id, $url, $this->isBackoffice);
+ if (!$selfServiceDetails['eligible']) {
+ CRM_Core_Error::statusBounce($selfServiceDetails['ineligible_message'], $url, ts('Sorry'));
+ }
$details = array_merge($details, $selfServiceDetails);
$this->assign('details', $details);
//This participant row will be cancelled. Get line item(s) to cancel
$participant = $values = [];
$this->_participant_id = CRM_Utils_Request::retrieve('pid', 'Positive', $this, FALSE, NULL, 'REQUEST');
$this->_userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE, NULL, 'REQUEST');
- $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, NULL, 'REQUEST');
+ $this->isBackoffice = CRM_Utils_Request::retrieve('is_backoffice', 'String', $this, FALSE, FALSE, 'REQUEST') ?? FALSE;
$params = ['id' => $this->_participant_id];
$this->_participant = CRM_Event_BAO_Participant::getValues($params, $values, $participant);
$this->_part_values = $values[$this->_participant_id];
$contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_participant_id, 'contribution_id', 'participant_id');
$this->assign('contributionId', $contributionId);
$selfServiceDetails = CRM_Event_BAO_Participant::getSelfServiceEligibility($this->_participant_id, $url, $this->isBackoffice);
+ if (!$selfServiceDetails['eligible']) {
+ CRM_Core_Error::statusBounce($selfServiceDetails['ineligible_message'], $url, ts('Sorry'));
+ }
$details = array_merge($details, $selfServiceDetails);
$this->assign('details', $details);
$this->selfsvcupdateUrl = CRM_Utils_System::url('civicrm/event/selfsvcupdate', "reset=1&id={$this->_participant_id}&id=0");
'start_date' => $startDate,
]);
$url = CRM_Utils_System::url('civicrm/event/info', "reset=1&id={$this->_eventId}");
- // If we return without an error, then success. But we don't always expect success.
- try {
- CRM_Event_BAO_Participant::getSelfServiceEligibility($participantId, $url, $isBackOffice);
- }
- catch (\Exception $e) {
- if ($successExpected === FALSE) {
- return;
- }
- else {
- $this->fail();
- }
- }
- if ($successExpected === FALSE) {
- $this->fail();
- }
+ $details = CRM_Event_BAO_Participant::getSelfServiceEligibility($participantId, $url, $isBackOffice);
+ $this->assertEquals($details['eligible'], $successExpected);
}
public function selfServiceScenarios() {
// Standard pass scenario
$scenarios[] = [
- 'selfSvcEnabled' => TRUE,
+ 'selfSvcEnabled' => 1,
'selfSvcHours' => 12,
'hoursToEvent' => 16,
'participantStatusId' => 1,
];
// Too late to self-service
$scenarios[] = [
- 'selfSvcEnabled' => TRUE,
+ 'selfSvcEnabled' => 1,
'selfSvcHours' => 12,
'hoursToEvent' => 8,
'participantStatusId' => 1,
];
// Participant status is other than "Registered".
$scenarios[] = [
- 'selfSvcEnabled' => TRUE,
+ 'selfSvcEnabled' => 1,
'selfSvcHours' => 12,
'hoursToEvent' => 16,
'participantStatusId' => 2,
'isBackOffice' => FALSE,
'successExpected' => FALSE,
];
+ // Event doesn't allow self-service
+ $scenarios[] = [
+ 'selfSvcEnabled' => 0,
+ 'selfSvcHours' => 12,
+ 'hoursToEvent' => 16,
+ 'participantStatusId' => 1,
+ 'isBackOffice' => FALSE,
+ 'successExpected' => FALSE,
+ ];
+ // Cancellation deadline is > 24 hours, still ok to cancel
+ $scenarios[] = [
+ 'selfSvcEnabled' => 1,
+ 'selfSvcHours' => 36,
+ 'hoursToEvent' => 46,
+ 'participantStatusId' => 1,
+ 'isBackOffice' => FALSE,
+ 'successExpected' => TRUE,
+ ];
+ // Cancellation deadline is > 24 hours, too late to cancel
+ $scenarios[] = [
+ 'selfSvcEnabled' => 1,
+ 'selfSvcHours' => 36,
+ 'hoursToEvent' => 25,
+ 'participantStatusId' => 1,
+ 'isBackOffice' => FALSE,
+ 'successExpected' => FALSE,
+ ];
return $scenarios;
}