Skip to content

Commit

Permalink
CIVIIB-85: CiviEvent daylight saving time issue
Browse files Browse the repository at this point in the history
Included in CiviCRM 5.52.2
PR: civicrm#23808
  • Loading branch information
kuldip-compuco committed Mar 6, 2023
1 parent 2b844a9 commit 84ecc1c
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 37 deletions.
4 changes: 2 additions & 2 deletions CRM/Core/Smarty/plugins/modifier.crmICalText.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
* @return string
* formatted text
*/
function smarty_modifier_crmICalText($str) {
return CRM_Utils_ICalendar::formatText($str);
function smarty_modifier_crmICalText($str, $keep_html = FALSE, $position = 0) {
return CRM_Utils_ICalendar::formatText($str, $keep_html, $position);
}
33 changes: 15 additions & 18 deletions CRM/Event/BAO/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -2416,18 +2416,7 @@ public static function getEntityRefFilters() {
* All of the icons to show.
*/
public static function getICalLinks($eventId = NULL) {
$return = $eventId ? [] : [
[
'url' => CRM_Utils_System::url('civicrm/event/ical', 'reset=1&list=1&html=1', TRUE, NULL, TRUE),
'text' => ts('HTML listing of current and future public events.'),
'icon' => 'fa-th-list',
],
[
'url' => CRM_Utils_System::url('civicrm/event/ical', 'reset=1&list=1&rss=1', TRUE, NULL, TRUE),
'text' => ts('Get RSS 2.0 feed for current and future public events.'),
'icon' => 'fa-rss',
],
];
$return = [];
$query = [
'reset' => 1,
];
Expand All @@ -2439,12 +2428,20 @@ public static function getICalLinks($eventId = NULL) {
'text' => $eventId ? ts('Download iCalendar entry for this event.') : ts('Download iCalendar entry for current and future public events.'),
'icon' => 'fa-download',
];
$query['list'] = 1;
$return[] = [
'url' => CRM_Utils_System::url('civicrm/event/ical', $query, TRUE, NULL, TRUE),
'text' => $eventId ? ts('iCalendar feed for this event.') : ts('iCalendar feed for current and future public events.'),
'icon' => 'fa-link',
];
if ($eventId) {
$return[] = [
'url' => CRM_Utils_System::url('civicrm/event/ical', ['gCalendar' => 1] + $query, TRUE, NULL, TRUE),
'text' => ts('Add event to Google Calendar'),
'icon' => 'fa-share',
];
}
else {
$return[] = [
'url' => CRM_Utils_System::url('civicrm/event/ical', $query, TRUE, NULL, TRUE),
'text' => ts('iCalendar feed for current and future public events'),
'icon' => 'fa-link',
];
}
return $return;
}

Expand Down
76 changes: 73 additions & 3 deletions CRM/Event/ICalendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,22 @@ public static function run() {
$iCalPage = CRM_Utils_Request::retrieveValue('list', 'Positive', 0);
$gData = CRM_Utils_Request::retrieveValue('gData', 'Positive', 0);
$rss = CRM_Utils_Request::retrieveValue('rss', 'Positive', 0);
$gCalendar = CRM_Utils_Request::retrieveValue('gCalendar', 'Positive', 0);

$info = CRM_Event_BAO_Event::getCompleteInfo($start, $type, $id, $end);

if ($gCalendar) {
return self::gCalRedirect($info);
}

$template = CRM_Core_Smarty::singleton();
$config = CRM_Core_Config::singleton();

$info = CRM_Event_BAO_Event::getCompleteInfo($start, $type, $id, $end);

$template->assign('events', $info);
$template->assign('timezone', @date_default_timezone_get());

$timezones = [@date_default_timezone_get()];

$template->assign('timezone', $timezones[0]);

// Send data to the correct template for formatting (iCal vs. gData)
if ($rss) {
Expand All @@ -61,6 +69,17 @@ public static function run() {
$calendar = $template->fetch('CRM/Core/Calendar/GData.tpl');
}
else {
$date_min = min(
array_map(function ($event) {
return strtotime($event['start_date']);
}, $info)
);
$date_max = max(
array_map(function ($event) {
return strtotime($event['end_date'] ?? $event['start_date']);
}, $info)
);
$template->assign('timezones', CRM_Utils_ICalendar::generate_timezones($timezones, $date_min, $date_max));
$calendar = $template->fetch('CRM/Core/Calendar/ICal.tpl');
$calendar = preg_replace('/(?<!\r)\n/', "\r\n", $calendar);
}
Expand All @@ -80,4 +99,55 @@ public static function run() {
CRM_Utils_System::civiExit();
}

protected static function gCalRedirect(array $events) {
if (count($events) != 1) {
throw new CRM_Core_Exception(ts('Expected one %1, found %2', [1 => ts('Event'), 2 => count($events)]));
}

$event = reset($events);

// Fetch the required Date TimeStamps
$start_date = date_create($event['start_date']);

// Google Requires that a Full Day event end day happens on the next Day
$end_date = ($event['end_date']
? date_create($event['end_date'])
: date_create($event['start_date'])
->add(DateInterval::createFromDateString('1 day'))
->setTime(0, 0, 0)
);

$dates = $start_date->format('Ymd\THis') . '/' . $end_date->format('Ymd\THis');

$event_details = $event['description'];

// Add space after paragraph
$event_details = str_replace('</p>', '</p> ', $event_details);
$event_details = strip_tags($event_details);

// Truncate Event Description and add permalink if greater than 996 characters
if (strlen($event_details) > 996) {
if (preg_match('/^.{0,996}(?=\s|$_)/', $event_details, $m)) {
$event_details = $m[0] . '...';
}
}

$event_details .= "\n\n<a href=\"{$event['url']}\">" . ts('View %1 Details', [1 => $event['event_type']]) . '</a>';

$params = [
'action' => 'TEMPLATE',
'text' => strip_tags($event['title']),
'dates' => $dates,
'details' => $event_details,
'location' => str_replace("\n", "\t", $event['location']),
'trp' => 'false',
'sprop' => 'website:' . CRM_Utils_System::baseCMSURL(),
'ctz' => @date_default_timezone_get(),
];

$url = 'https://www.google.com/calendar/event?' . CRM_Utils_System::makeQueryString($params);

CRM_Utils_System::redirect($url);
}

}
104 changes: 101 additions & 3 deletions CRM/Utils/ICalendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,58 @@ class CRM_Utils_ICalendar {
*
* @param string $text
* Text to escape.
* @param bool $keep_html
* Flag to retain HTML formatting
* @param int $position
* Column number of the start of the string in the ICal output - used to
* determine allowable length of the first line
*
* @return string
*/
public static function formatText($text) {
$text = strip_tags($text);
public static function formatText($text, $keep_html = FALSE, int $position = 0) {
if (!$keep_html) {
$text = preg_replace(
'{ <a [^>]+ \\b href=(?: "( [^"]+ )" | \'( [^\']+ )\' ) [^>]* > ( [^<]* ) </a> }xi',
'$3 ($1$2)',
$text
);
$text = preg_replace(
'{ < / [^>]+ > \s* }',
"\$0 ",
$text
);
$text = preg_replace(
'{ <(br|/tr|/div|/h[1-6]) (\s [^>]*)? > (\s* \n)? }xi',
"\$0\n",
$text
);
$text = preg_replace(
'{ </p> (\s* \n)? }xi',
"\$0\n\n",
$text
);
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML401, 'UTF-8');
}

$text = str_replace("\\", "\\\\", $text);
$text = str_replace(',', '\,', $text);
$text = str_replace(';', '\;', $text);
$text = str_replace(["\r\n", "\n", "\r"], "\\n ", $text);
$text = implode("\n ", str_split($text, 50));

// Remove this check after PHP 7.4 becomes a minimum requirement
$str_split = function_exists('mb_str_split') ? 'mb_str_split' : 'str_split';

if ($keep_html) {
$text = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"><html><body>' . $text . '</body></html>';
}
$prefix = '';
if ($position) {
$prefixlen = max(50 - $position, 0);
$prefix = mb_substr($text, 0, $prefixlen) . "\n ";
$text = mb_substr($text, $prefixlen);
}
$text = $prefix . implode("\n ", $str_split($text, 50));
return $text;
}

Expand Down Expand Up @@ -116,4 +158,60 @@ public static function send($calendar, $content_type = 'text/calendar', $charset
echo $calendar;
}

/**
* @param array $timezones - Timezone strings
* @param $date_min
* @param $date_max
*
* @return array
*/
public static function generate_timezones(array $timezones, $date_min, $date_max) {
if (empty($timezones)) {
return [];
}

$tz_items = [];

foreach ($timezones as $tzstr) {
$timezone = new DateTimeZone($tzstr);

$transitions = $timezone->getTransitions($date_min, $date_max);

if (count($transitions) === 1) {
$transitions[] = array_values($transitions)[0];
}

$item = [
'id' => $timezone->getName(),
'transitions' => [],
];

$last_transition = array_shift($transitions);

foreach ($transitions as $transition) {
$item['transitions'][] = [
'type' => $transition['isdst'] ? 'DAYLIGHT' : 'STANDARD',
'offset_from' => self::format_tz_offset($last_transition['offset']),
'offset_to' => self::format_tz_offset($transition['offset']),
'abbr' => $transition['abbr'],
'dtstart' => date_create($transition['time'], $timezone)->format("Ymd\THis"),
];

$last_transition = $transition;
}

$tz_items[] = $item;
}

return $tz_items;
}

protected static function format_tz_offset($offset) {
$offset /= 60;
$hours = intval($offset / 60);
$minutes = abs(intval($offset % 60));

return sprintf('%+03d%02d', $hours, $minutes);
}

}
16 changes: 16 additions & 0 deletions templates/CRM/Core/Calendar/ICal.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@ VERSION:2.0
PRODID:-//CiviCRM//NONSGML CiviEvent iCal//EN
X-WR-TIMEZONE:{$timezone}
METHOD:PUBLISH
{foreach from=$timezones item=tzItem}
BEGIN:VTIMEZONE
TZID:{$tzItem.id}
{foreach from=$tzItem.transitions item=tzTr}
BEGIN:{$tzTr.type}
TZOFFSETFROM:{$tzTr.offset_from}
TZOFFSETTO:{$tzTr.offset_to}
TZNAME:{$tzTr.abbr}
{if $tzTr.dtstart}
DTSTART:{$tzTr.dtstart|crmICalDate}
{/if}
END:{$tzTr.type}
{/foreach}
END:VTIMEZONE
{/foreach}
{foreach from=$events key=uid item=event}
BEGIN:VEVENT
UID:{$event.uid}
SUMMARY:{$event.title|crmICalText}
{if $event.description}
X-ALT-DESC;FMTTYPE=text/html:{$event.description|crmICalText:true:29}
DESCRIPTION:{$event.description|crmICalText}
{/if}
{if $event.event_type}
Expand Down
4 changes: 2 additions & 2 deletions templates/CRM/Event/Page/iCalLinks.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*}
{* Display icons / links for ical download and feed for EventInfo.tpl, ThankYou.tpl, DashBoard.tpl, and ManageEvent.tpl *}
{foreach from=$iCal item="iCalItem"}
<a href="{$iCalItem.url}" title="{$iCalItem.text}"{if !empty($event)} class="crm-event-feed-link"{/if}>
<a href="{$iCalItem.url}" {if !empty($event)} class="crm-event-feed-link"{/if}>
<span class="fa-stack" aria-hidden="true"><i class="crm-i fa-calendar-o fa-stack-2x"></i><i style="top: 15%;" class="crm-i {$iCalItem.icon} fa-stack-1x"></i></span>
<span class="sr-only">{$iCalItem.text}</span>
<span class="label">{$iCalItem.text}</span>
</a>
{/foreach}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,13 @@
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$icalFeed}">{ts}Download iCalendar File{/ts}</a>
<a href="{$icalFeed}">{ts}Download iCalendar entry for this event.{/ts}</a>
</td>
</tr>
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=gCalendar}{crmURL p='civicrm/event/ical' q="gCalendar=1&reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$gCalendar}">{ts}Add event to Google Calendar{/ts}</a>
</td>
</tr>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@

{if !empty($event.is_public)}
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
{ts}Download iCalendar File:{/ts} {$icalFeed}
{ts}Download iCalendar entry for this event.{/ts} {$icalFeed}
{capture assign=gCalendar}{crmURL p='civicrm/event/ical' q="gCalendar=1&reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
{ts}Add event to Google Calendar{/ts} {$gCalendar}
{/if}

{if !empty($email)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,13 @@
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$icalFeed}">{ts}Download iCalendar File{/ts}</a>
<a href="{$icalFeed}">{ts}Download iCalendar entry for this event.{/ts}</a>
</td>
</tr>
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=gCalendar}{crmURL p='civicrm/event/ical' q="gCalendar=1&reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$gCalendar}">{ts}Add event to Google Calendar{/ts}</a>
</td>
</tr>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@

{if !empty($event.is_public)}
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
{ts}Download iCalendar File:{/ts} {$icalFeed}
{ts}Download iCalendar entry for this event.{/ts} {$icalFeed}
{capture assign=gCalendar}{crmURL p='civicrm/event/ical' q="gCalendar=1&reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
{ts}Add event to Google Calendar{/ts} {$gCalendar}
{/if}

{if !empty($payer.name)}
Expand Down
14 changes: 10 additions & 4 deletions xml/templates/message_templates/participant_confirm_html.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,18 @@
{/if}

{if $event.is_public}
<tr>
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$icalFeed}">{ts}Download iCalendar File{/ts}</a>
{capture assign=icalFeed}{crmURL p='civicrm/event/ical' q="reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$icalFeed}">{ts}Download iCalendar entry for this event.{/ts}</a>
</td>
</tr>
</tr>
<tr>
<td colspan="2" {$valueStyle}>
{capture assign=gCalendar}{crmURL p='civicrm/event/ical' q="gCalendar=1&reset=1&id=`$event.id`" h=0 a=1 fe=1}{/capture}
<a href="{$gCalendar}">{ts}Add event to Google Calendar{/ts}</a>
</td>
</tr>
{/if}

{if '{contact.email}'}
Expand Down
Loading

0 comments on commit 84ecc1c

Please sign in to comment.