* @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);
}
* 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,
];
'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;
}
$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) {
$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);
}
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);
+ }
+
}
*
* @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);
- $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML401, 'UTF-8');
+ 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);
+
// Remove this check after PHP 7.4 becomes a minimum requirement
$str_split = function_exists('mb_str_split') ? 'mb_str_split' : 'str_split';
- $text = implode("\n ", $str_split($text, 50));
+
+ 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;
}
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);
+ }
+
}
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}
*}
{* 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}
<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}
{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)}
<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}
{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)}
{/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}'}
{if $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 '{contact.email}'}