Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
16 | */ |
17 | ||
18 | /** | |
19 | * @file | |
20 | * API for event export in iCalendar format | |
21 | * as outlined in Internet Calendaring and | |
22 | * Scheduling Core Object Specification | |
6a488035 TO |
23 | */ |
24 | class CRM_Utils_ICalendar { | |
25 | ||
26 | /** | |
fe482240 | 27 | * Escape text elements for safe ICalendar use. |
6a488035 | 28 | * |
046dd6c2 | 29 | * @param string $text |
77855840 | 30 | * Text to escape. |
a2cadd75 FW |
31 | * @param bool $keep_html |
32 | * Flag to retain HTML formatting | |
33 | * @param int $position | |
34 | * Column number of the start of the string in the ICal output - used to | |
35 | * determine allowable length of the first line | |
6a488035 | 36 | * |
72b3a70c | 37 | * @return string |
6a488035 | 38 | */ |
a2cadd75 FW |
39 | public static function formatText($text, $keep_html = FALSE, int $position = 0) { |
40 | if (!$keep_html) { | |
41 | $text = preg_replace( | |
42 | '{ <a [^>]+ \\b href=(?: "( [^"]+ )" | \'( [^\']+ )\' ) [^>]* > ( [^<]* ) </a> }xi', | |
43 | '$3 ($1$2)', | |
44 | $text | |
45 | ); | |
46 | $text = preg_replace( | |
47 | '{ < / [^>]+ > \s* }', | |
48 | "\$0 ", | |
49 | $text | |
50 | ); | |
51 | $text = preg_replace( | |
52 | '{ <(br|/tr|/div|/h[1-6]) (\s [^>]*)? > (\s* \n)? }xi', | |
53 | "\$0\n", | |
54 | $text | |
55 | ); | |
56 | $text = preg_replace( | |
57 | '{ </p> (\s* \n)? }xi', | |
58 | "\$0\n\n", | |
59 | $text | |
60 | ); | |
61 | $text = strip_tags($text); | |
62 | $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML401, 'UTF-8'); | |
63 | } | |
64 | ||
6a488035 | 65 | $text = str_replace("\\", "\\\\", $text); |
046dd6c2 CW |
66 | $text = str_replace(',', '\,', $text); |
67 | $text = str_replace(';', '\;', $text); | |
be2fb01f | 68 | $text = str_replace(["\r\n", "\n", "\r"], "\\n ", $text); |
a2cadd75 | 69 | |
977d1c85 ML |
70 | // Remove this check after PHP 7.4 becomes a minimum requirement |
71 | $str_split = function_exists('mb_str_split') ? 'mb_str_split' : 'str_split'; | |
a2cadd75 FW |
72 | |
73 | if ($keep_html) { | |
74 | $text = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"><html><body>' . $text . '</body></html>'; | |
75 | } | |
76 | $prefix = ''; | |
77 | if ($position) { | |
78 | $prefixlen = max(50 - $position, 0); | |
79 | $prefix = mb_substr($text, 0, $prefixlen) . "\n "; | |
80 | $text = mb_substr($text, $prefixlen); | |
81 | } | |
82 | $text = $prefix . implode("\n ", $str_split($text, 50)); | |
6a488035 TO |
83 | return $text; |
84 | } | |
85 | ||
046dd6c2 CW |
86 | /** |
87 | * Restore iCal formatted text to normal. | |
88 | * | |
89 | * @param string $text | |
90 | * Text to unescape. | |
91 | * | |
92 | * @return string | |
93 | */ | |
94 | public static function unformatText($text) { | |
977d1c85 | 95 | $text = str_replace("\n ", "", $text); |
046dd6c2 CW |
96 | $text = str_replace('\n ', "\n", $text); |
97 | $text = str_replace('\;', ';', $text); | |
98 | $text = str_replace('\,', ',', $text); | |
99 | $text = str_replace("\\\\", "\\", $text); | |
100 | $text = str_replace("DQUOTE", "\"", $text); | |
101 | return $text; | |
102 | } | |
103 | ||
6a488035 | 104 | /** |
fe482240 | 105 | * Escape date elements for safe ICalendar use. |
6a488035 | 106 | * |
fa3fdebc | 107 | * @param string $date |
77855840 | 108 | * Date to escape. |
6a488035 | 109 | * |
f4aaa82a | 110 | * @param bool $gdata |
6a488035 | 111 | * |
72b3a70c CW |
112 | * @return string |
113 | * Escaped date | |
6a488035 | 114 | */ |
00be9182 | 115 | public static function formatDate($date, $gdata = FALSE) { |
6a488035 TO |
116 | |
117 | if ($gdata) { | |
118 | return date("Y-m-d\TH:i:s.000", | |
119 | strtotime($date) | |
120 | ); | |
121 | } | |
122 | else { | |
123 | return date("Ymd\THis", | |
124 | strtotime($date) | |
125 | ); | |
126 | } | |
127 | } | |
128 | ||
129 | /** | |
6a488035 | 130 | * Send the ICalendar to the browser with the specified content type |
cb087737 | 131 | * - 'text/calendar' : used for iCal formatted feed |
6a488035 TO |
132 | * - 'text/xml' : used for gData or rss formatted feeds |
133 | * | |
6a488035 | 134 | * |
77855840 TO |
135 | * @param string $calendar |
136 | * The calendar data to be published. | |
f4aaa82a | 137 | * @param string $content_type |
77855840 TO |
138 | * @param string $charset |
139 | * The character set to use, defaults to 'us-ascii'. | |
140 | * @param string $fileName | |
141 | * The file name (for downloads). | |
142 | * @param string $disposition | |
143 | * How the file should be sent ('attachment' for downloads). | |
6a488035 | 144 | */ |
00be9182 | 145 | public static function send($calendar, $content_type = 'text/calendar', $charset = 'us-ascii', $fileName = NULL, $disposition = NULL) { |
6a488035 TO |
146 | $config = CRM_Core_Config::singleton(); |
147 | $lang = $config->lcMessages; | |
d42a224c CW |
148 | CRM_Utils_System::setHttpHeader("Content-Language", $lang); |
149 | CRM_Utils_System::setHttpHeader("Content-Type", "$content_type; charset=$charset"); | |
6a488035 | 150 | |
cb087737 | 151 | if ($fileName) { |
d42a224c CW |
152 | CRM_Utils_System::setHttpHeader('Content-Length', strlen($calendar)); |
153 | CRM_Utils_System::setHttpHeader("Content-Disposition", "$disposition; filename=\"$fileName\""); | |
154 | CRM_Utils_System::setHttpHeader("Pragma", "no-cache"); | |
155 | CRM_Utils_System::setHttpHeader("Expires", "0"); | |
156 | CRM_Utils_System::setHttpHeader("Cache-Control", "no-cache, must-revalidate"); | |
6a488035 TO |
157 | } |
158 | ||
159 | echo $calendar; | |
160 | } | |
96025800 | 161 | |
a2cadd75 FW |
162 | /** |
163 | * @param array $timezones - Timezone strings | |
164 | * @param $date_min | |
165 | * @param $date_max | |
166 | * | |
167 | * @return array | |
168 | */ | |
169 | public static function generate_timezones(array $timezones, $date_min, $date_max) { | |
170 | if (empty($timezones)) { | |
171 | return []; | |
172 | } | |
173 | ||
174 | $tz_items = []; | |
175 | ||
176 | foreach ($timezones as $tzstr) { | |
177 | $timezone = new DateTimeZone($tzstr); | |
178 | ||
179 | $transitions = $timezone->getTransitions($date_min, $date_max); | |
180 | ||
181 | if (count($transitions) === 1) { | |
182 | $transitions[] = array_values($transitions)[0]; | |
183 | } | |
184 | ||
185 | $item = [ | |
186 | 'id' => $timezone->getName(), | |
187 | 'transitions' => [], | |
188 | ]; | |
189 | ||
190 | $last_transition = array_shift($transitions); | |
191 | ||
192 | foreach ($transitions as $transition) { | |
193 | $item['transitions'][] = [ | |
194 | 'type' => $transition['isdst'] ? 'DAYLIGHT' : 'STANDARD', | |
195 | 'offset_from' => self::format_tz_offset($last_transition['offset']), | |
196 | 'offset_to' => self::format_tz_offset($transition['offset']), | |
197 | 'abbr' => $transition['abbr'], | |
198 | 'dtstart' => date_create($transition['time'], $timezone)->format("Ymd\THis"), | |
199 | ]; | |
200 | ||
201 | $last_transition = $transition; | |
202 | } | |
203 | ||
204 | $tz_items[] = $item; | |
205 | } | |
206 | ||
207 | return $tz_items; | |
208 | } | |
209 | ||
210 | protected static function format_tz_offset($offset) { | |
211 | $offset /= 60; | |
212 | $hours = intval($offset / 60); | |
213 | $minutes = abs(intval($offset % 60)); | |
214 | ||
215 | return sprintf('%+03d%02d', $hours, $minutes); | |
216 | } | |
217 | ||
6a488035 | 218 | } |