Skip to content

Commit

Permalink
Merge pull request #20476 from agileware/CIVICRM-404
Browse files Browse the repository at this point in the history
dev/core#2122 Add timezone support for events
  • Loading branch information
demeritcowboy authored Jan 25, 2022
2 parents c4ed61e + b39946f commit a90c387
Show file tree
Hide file tree
Showing 53 changed files with 4,692 additions and 4,078 deletions.
5 changes: 5 additions & 0 deletions CRM/Contribute/BAO/Contribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -2561,6 +2561,8 @@ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText

CRM_Event_BAO_Event::retrieve($eventParams, $values['event']);

CRM_Event_BAO_Event::setOutputTimeZone($values['event']);

//get location details
$locationParams = [
'entity_id' => $this->_relatedObjects['participant']->event_id,
Expand Down Expand Up @@ -4723,6 +4725,9 @@ protected function loadEventMessageTemplateParams(int $eventID, int $participant
$values = ['event' => []];

CRM_Event_BAO_Event::retrieve($eventParams, $values['event']);

CRM_Event_BAO_Event::setOutputTimeZone($values['event']);

// add custom fields for event
$eventGroupTree = CRM_Core_BAO_CustomGroup::getTree('Event', NULL, $eventID);

Expand Down
18 changes: 18 additions & 0 deletions CRM/Core/SelectValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -1111,4 +1111,22 @@ public static function andOr() {
];
}

public static function timezone() {
$tzlist = &Civi::$statics[__CLASS__]['tzlist'];

if (is_null($tzlist)) {
$tzlist = [];
foreach (timezone_identifiers_list() as $tz) {
// Actual timezone keys for PHP are mapped to human parts.
$tzlist[$tz] = str_replace('_', ' ', $tz);
}

// Add 'Etc/UTC' specially, as timezone_identifiers_list() does
// not include it, but it is the IANA long name for 'UTC'
$tzlist['Etc/UTC'] = ts('Etc/UTC');
}

return $tzlist;
}

}
51 changes: 51 additions & 0 deletions CRM/Event/BAO/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
class CRM_Event_BAO_Event extends CRM_Event_DAO_Event {
const tz_fields = ['event_start_date', 'event_end_date', 'start_date', 'end_date', 'registration_start_date', 'registration_end_date'];

/**
* Fetch object based on array of properties.
Expand Down Expand Up @@ -779,6 +780,7 @@ public static function getCompleteInfo(
civicrm_email.email as email,
civicrm_event.title as title,
civicrm_event.summary as summary,
civicrm_event.event_tz as event_tz,
civicrm_event.start_date as start,
civicrm_event.end_date as end,
civicrm_event.description as description,
Expand Down Expand Up @@ -850,6 +852,7 @@ public static function getCompleteInfo(
$info['event_id'] = $dao->event_id;
$info['summary'] = $dao->summary;
$info['description'] = $dao->description;
$info['tz'] = $dao->event_tz ?? CRM_Core_Config::singleton()->userSystem->getTimeZoneString();
$info['start_date'] = $dao->start;
$info['end_date'] = $dao->end;
$info['contact_email'] = $dao->email;
Expand Down Expand Up @@ -2434,4 +2437,52 @@ public static function getICalLinks($eventId = NULL) {
return $return;
}

/**
* Changes timezone-enabled fields to the correct zone for output and add local
* & UTC variants
*
* @param array $params
* @param $to_tz
*
* @return void
*/
public static function setOutputTimeZone(array &$params, $to_tz = NULL) {
$to_tz = $to_tz ?? ($params['event_tz'] ?? NULL);

if (is_null($to_tz)) {
return;
}

foreach (CRM_Event_BAO_Event::tz_fields as $field) {
if (!empty($params[$field]) && empty($params[$field . '_local'])) {
$params[$field . '_utc'] = CRM_Utils_Date::convertTimeZone($params[$field], 'UTC');
$params[$field . '_local'] = $params[$field];
$params[$field] = CRM_Utils_Date::convertTimeZone($params[$field], $to_tz);
}
}

}

public static function setTimezones(CRM_Event_DAO_Event $event) {
// Pre-process time zoned fields into the PHP time zone, which should be the same as the database, to save as timestamp.
$timezone_event = ($event->event_tz ?: (!empty($event->id) ? CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $event->id, 'event_tz') : NULL));

foreach (self::tz_fields as $field) {
if (!empty($event->{$field})) {
$event->{$field} = CRM_Utils_Date::convertTimeZone($event->{$field}, NULL, $timezone_event);
}
}
}

public static function resetTimezones(CRM_Event_DAO_Event $event) {
// Process time zoned fields into their own time zone
$timezone_event = ($event->event_tz ?: (!empty($event->id) ? CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $event->id, 'event_tz') : NULL));

foreach (self::tz_fields as $field) {
if (!empty($event->{$field})) {
$event->{$field} = CRM_Utils_Date::convertTimeZone($event->{$field}, $timezone_event);
}
}
}

}
3 changes: 3 additions & 0 deletions CRM/Event/BAO/Participant.php
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,9 @@ public static function sendTransitionParticipantMail(
) {
return $mailSent;
}

CRM_Event_BAO_Event::setOutputTimeZone($eventDetails);

$toEmail = $contactDetails['email'] ?? NULL;
if ($toEmail) {

Expand Down
64 changes: 51 additions & 13 deletions CRM/Event/DAO/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* Generated from xml/schema/CRM/Event/Event.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
* (GenCodeChecksum:894f53608aa8036de5fab1dc90a407d5)
* (GenCodeChecksum:142b4991959919f73cea72a40b3f4a1d)
*/

/**
Expand Down Expand Up @@ -121,17 +121,17 @@ class CRM_Event_DAO_Event extends CRM_Core_DAO {
/**
* Date and time that event starts.
*
* @var string|null
* (SQL type: datetime)
* @var string
* (SQL type: timestamp)
* Note that values will be retrieved from the database as a string.
*/
public $start_date;

/**
* Date and time that event ends. May be NULL if no defined end date/time
*
* @var string|null
* (SQL type: datetime)
* @var string
* (SQL type: timestamp)
* Note that values will be retrieved from the database as a string.
*/
public $end_date;
Expand All @@ -157,17 +157,17 @@ class CRM_Event_DAO_Event extends CRM_Core_DAO {
/**
* Date and time that online registration starts.
*
* @var string|null
* (SQL type: datetime)
* @var string
* (SQL type: timestamp)
* Note that values will be retrieved from the database as a string.
*/
public $registration_start_date;

/**
* Date and time that online registration ends.
*
* @var string|null
* (SQL type: datetime)
* @var string
* (SQL type: timestamp)
* Note that values will be retrieved from the database as a string.
*/
public $registration_end_date;
Expand Down Expand Up @@ -674,6 +674,15 @@ class CRM_Event_DAO_Event extends CRM_Core_DAO {
*/
public $is_billing_required;

/**
* Event's native time zone
*
* @var string|null
* (SQL type: text)
* Note that values will be retrieved from the database as a string.
*/
public $event_tz;

/**
* Class constructor.
*/
Expand Down Expand Up @@ -856,13 +865,15 @@ public static function &fields() {
],
'event_start_date' => [
'name' => 'start_date',
'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Event Start Date'),
'description' => ts('Date and time that event starts.'),
'required' => FALSE,
'import' => TRUE,
'where' => 'civicrm_event.start_date',
'headerPattern' => '/^start|(s(tart\s)?date)$/i',
'export' => TRUE,
'default' => NULL,
'table_name' => 'civicrm_event',
'entity' => 'Event',
'bao' => 'CRM_Event_BAO_Event',
Expand All @@ -875,13 +886,15 @@ public static function &fields() {
],
'event_end_date' => [
'name' => 'end_date',
'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Event End Date'),
'description' => ts('Date and time that event ends. May be NULL if no defined end date/time'),
'required' => FALSE,
'import' => TRUE,
'where' => 'civicrm_event.end_date',
'headerPattern' => '/^end|(e(nd\s)?date)$/i',
'export' => TRUE,
'default' => NULL,
'table_name' => 'civicrm_event',
'entity' => 'Event',
'bao' => 'CRM_Event_BAO_Event',
Expand Down Expand Up @@ -927,10 +940,12 @@ public static function &fields() {
],
'registration_start_date' => [
'name' => 'registration_start_date',
'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Registration Start Date'),
'description' => ts('Date and time that online registration starts.'),
'required' => FALSE,
'where' => 'civicrm_event.registration_start_date',
'default' => NULL,
'table_name' => 'civicrm_event',
'entity' => 'Event',
'bao' => 'CRM_Event_BAO_Event',
Expand All @@ -944,10 +959,12 @@ public static function &fields() {
],
'registration_end_date' => [
'name' => 'registration_end_date',
'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Registration End Date'),
'description' => ts('Date and time that online registration ends.'),
'required' => FALSE,
'where' => 'civicrm_event.registration_end_date',
'default' => NULL,
'table_name' => 'civicrm_event',
'entity' => 'Event',
'bao' => 'CRM_Event_BAO_Event',
Expand Down Expand Up @@ -1918,6 +1935,27 @@ public static function &fields() {
],
'add' => '4.6',
],
'event_tz' => [
'name' => 'event_tz',
'type' => CRM_Utils_Type::T_TEXT,
'title' => ts('Event Time Zone'),
'description' => ts('Event\'s native time zone'),
'import' => TRUE,
'where' => 'civicrm_event.event_tz',
'export' => TRUE,
'default' => NULL,
'table_name' => 'civicrm_event',
'entity' => 'Event',
'bao' => 'CRM_Event_BAO_Event',
'localizable' => 0,
'html' => [
'type' => 'Select',
],
'pseudoconstant' => [
'callback' => 'CRM_Core_SelectValues::timezone',
],
'add' => '5.43',
],
];
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
}
Expand Down
17 changes: 15 additions & 2 deletions CRM/Event/Form/ManageEvent/EventInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ public function setDefaultValues() {

$defaults['waitlist_text'] = CRM_Utils_Array::value('waitlist_text', $defaults, ts('This event is currently full. However you can register now and get added to a waiting list. You will be notified if spaces become available.'));
$defaults['template_id'] = $this->_templateId;

$defaults['event_tz'] = CRM_Utils_Array::value('event_tz', $defaults, CRM_Core_Config::singleton()->userSystem->getTimeZoneString());

// Convert start and end date defaults to event time zone.
if (!empty($defaults['start_date'])) {
$defaults['start_date'] = CRM_Utils_Date::convertTimeZone($defaults['start_date'], $defaults['event_tz']);
}
if (!empty($defaults['end_date'])) {
$defaults['end_date'] = CRM_Utils_Date::convertTimeZone($defaults['end_date'], $defaults['event_tz']);
}

return $defaults;
}

Expand Down Expand Up @@ -159,6 +170,8 @@ public function buildQuickForm() {
$this->addElement('checkbox', 'is_share', ts('Add footer region with Twitter, Facebook and LinkedIn share buttons and scripts?'));
$this->addElement('checkbox', 'is_map', ts('Include Map to Event Location'));

$this->addSelect('event_tz', ['placeholder' => ts('- Select time zone -')], TRUE);

$this->add('datepicker', 'start_date', ts('Start'), [], !$this->_isTemplate, ['time' => TRUE]);
$this->add('datepicker', 'end_date', ts('End'), [], FALSE, ['time' => TRUE]);

Expand Down Expand Up @@ -215,8 +228,8 @@ public function postProcess() {
$params = array_merge($this->controller->exportValues($this->_name), $this->_submitValues);

//format params
$params['start_date'] = $params['start_date'] ?? NULL;
$params['end_date'] = $params['end_date'] ?? NULL;
$params['start_date'] = !empty($params['start_date']) ? CRM_Utils_Date::convertTimeZone($params['start_date'], NULL, $params['event_tz'] ?? NULL) : $params['start_date'];
$params['end_date'] = !empty($params['end_date']) ? CRM_Utils_Date::convertTimeZone($params['end_date'], NULL, $params['event_tz'] ?? NULL) : $params['end_date'];
$params['has_waitlist'] = CRM_Utils_Array::value('has_waitlist', $params, FALSE);
$params['is_map'] = CRM_Utils_Array::value('is_map', $params, FALSE);
$params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE);
Expand Down
19 changes: 19 additions & 0 deletions CRM/Event/Form/ManageEvent/Registration.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class CRM_Event_Form_ManageEvent_Registration extends CRM_Event_Form_ManageEvent
protected $_profilePostMultiple = [];
protected $_profilePostMultipleAdd = [];

protected $_tz;

/**
* Set variables up before form is built.
*/
Expand Down Expand Up @@ -166,6 +168,14 @@ public function setDefaultValues() {
$defaults['thankyou_title'] = CRM_Utils_Array::value('thankyou_title', $defaults, ts('Thank You for Registering'));
$defaults['approval_req_text'] = CRM_Utils_Array::value('approval_req_text', $defaults, ts('Participation in this event requires approval. Submit your registration request here. Once approved, you will receive an email with a link to a web page where you can complete the registration process.'));

// Convert start and end date defaults to event time zone.
if (!empty($defaults['registration_start_date'])) {
$defaults['registration_start_date'] = CRM_Utils_Date::convertTimeZone($defaults['registration_start_date'], $this->_tz ?? NULL);
}
if (!empty($defaults['registration_end_date'])) {
$defaults['registration_end_date'] = CRM_Utils_Date::convertTimeZone($defaults['registration_end_date'], $this->_tz ?? NULL);
}

return $defaults;
}

Expand Down Expand Up @@ -228,6 +238,9 @@ public function buildQuickForm() {
$this->add('text', 'registration_link_text', ts('Registration Link Text'));

if (!$this->_isTemplate) {
$this->_tz = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->_id, 'event_tz');
$tz = CRM_Core_SelectValues::timezone()[$this->_tz];
$this->assign('event_tz', CRM_Core_SelectValues::timezone()[$tz] ?? '<span class="error-message">' . ts('%1 No timezone set', [1 => '<i class="crm-i fa-warning"></i>']) . '</span>');
$this->add('datepicker', 'registration_start_date', ts('Registration Start Date'), [], FALSE, ['time' => TRUE]);
$this->add('datepicker', 'registration_end_date', ts('Registration End Date'), [], FALSE, ['time' => TRUE]);
}
Expand Down Expand Up @@ -776,13 +789,19 @@ public function postProcess() {

$params['id'] = $this->_id;

if (!isset($this->_tz)) {
$this->_tz = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->_id, 'event_tz') ?? CRM_Core_Config::singleton()->userSystem->getTimeZoneString();
}

// format params
$params['is_online_registration'] = CRM_Utils_Array::value('is_online_registration', $params, FALSE);
// CRM-11182
$params['is_confirm_enabled'] = CRM_Utils_Array::value('is_confirm_enabled', $params, FALSE);
$params['is_multiple_registrations'] = CRM_Utils_Array::value('is_multiple_registrations', $params, FALSE);
$params['allow_same_participant_emails'] = CRM_Utils_Array::value('allow_same_participant_emails', $params, FALSE);
$params['requires_approval'] = CRM_Utils_Array::value('requires_approval', $params, FALSE);
$params['registration_start_date'] = !empty($params['registration_start_date']) ? CRM_Utils_Date::convertTimeZone($params['registration_start_date'], NULL, $this->_tz ?? NULL) : $params['registration_start_date'];
$params['registration_end_date'] = !empty($params['registration_end_date']) ? CRM_Utils_Date::convertTimeZone($params['registration_end_date'], NULL, $this->_tz ?? NULL) : $params['registration_end_date'];

// reset is_email confirm if not online reg
if (!$params['is_online_registration']) {
Expand Down
4 changes: 3 additions & 1 deletion CRM/Event/Form/Participant.php
Original file line number Diff line number Diff line change
Expand Up @@ -1892,14 +1892,16 @@ protected function preparePaidEventProcessing($params): array {
protected function assignEventDetailsToTpl($eventID, $participantRoles, $receiptText, $isPaidEvent) {
//use of the message template below requires variables in different format
$events = [];
$returnProperties = ['event_type_id', 'fee_label', 'start_date', 'end_date', 'is_show_location', 'title'];
$returnProperties = ['event_type_id', 'fee_label', 'start_date', 'end_date', 'event_tz', 'is_show_location', 'title'];

//get all event details.
CRM_Core_DAO::commonRetrieveAll('CRM_Event_DAO_Event', 'id', $eventID, $events, $returnProperties);
$event = $events[$eventID];
unset($event['start_date']);
unset($event['end_date']);

CRM_Event_BAO_Event::setOutputTimeZone($event);

$role = CRM_Event_PseudoConstant::participantRole();

if (is_array($participantRoles)) {
Expand Down
Loading

0 comments on commit a90c387

Please sign in to comment.