dolibarr 25.0.0-alpha
ical.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006 Roman Ozana <ozana@omdesign.cz>
3 * Copyright (C) 2011 Juanjo Menent <jmenent@2byte.es>
4 * Copyright (C) 2013-2014 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2012 Regis Houssin <regis.houssin@inodbox.com>
6 * Copyright (C) 2019-2024 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
8 * Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
9 * Copyright (C) 2026 Joachim Kueter <git-jk@bloxera.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
30require_once DOL_DOCUMENT_ROOT.'/core/lib/xcal.lib.php';
31require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
32
33
37class ICal
38{
42 public $file;
43
47 public $file_text;
48
52 public $cal;
53
57 public $event_count;
58
62 public $todo_count;
63
67 public $freebusy_count;
68
72 public $last_key;
73
77 public $error;
78
79
83 public function __construct()
84 {
85 }
86
87 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
94 public function read_file($file)
95 {
96 // phpcs:enable
97 $this->file = $file;
98 $file_text = '';
99
100 //$tmpresult = getURLContent($file, 'GET', '', 1, [], ['http', 'https'], 2, 0); // To test with any URL
101 $localip = 0;
102 $sslverify = -1;
103 if (getDolGlobalString('AGENDA_EXT_CALENDAR_IP_MODE')) {
104 $localip = intval(getDolGlobalString('AGENDA_EXT_CALENDAR_IP_MODE'));
105 }
106 if (getDolGlobalString('AGENDA_EXT_CALENDAR_SSLVERIFY_MODE')) {
107 $sslverify = intval(getDolGlobalString('AGENDA_EXT_CALENDAR_SSLVERIFY_MODE'));
108 }
109 // See documentation of getURLContent function for $localip and $sslverify possible values
110 $tmpresult = getURLContent($file, 'GET', '', 1, [], ['http', 'https'], $localip, $sslverify);
111 if ($tmpresult['http_code'] != 200) {
112 $file_text = null;
113 $this->error = 'Error: '.$tmpresult['http_code'].' '.$tmpresult['content'];
114 } else {
115 $file_text = preg_replace("/[\r\n]{1,} /", "", $tmpresult['content']);
116 }
117 //var_dump($tmpresult);
118
119 return $file_text; // return all text
120 }
121
122 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
128 public function get_event_count()
129 {
130 // phpcs:enable
131 return $this->event_count;
132 }
133
134 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
140 public function get_todo_count()
141 {
142 // phpcs:enable
143 return $this->todo_count;
144 }
145
154 public function parse($uri, $usecachefile = '', $delaycache = 3600)
155 {
156 $this->cal = array(); // new empty array
157
158 $this->event_count = -1;
159 $this->file_text = '';
160
161 // Save file into a cache
162 if ($usecachefile) {
163 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
164 $datefile = dol_filemtime($usecachefile);
165 $now = dol_now('gmt');
166 //print $datefile.' '.$now.' ...';
167 if ($datefile && $datefile > ($now - $delaycache)) {
168 // We reuse the cache file
169 $this->file_text = file_get_contents($usecachefile);
170 }
171 }
172
173 // read FILE text
174 if (empty($this->file_text)) {
175 $this->file_text = $this->read_file($uri);
176
177 if ($usecachefile && !is_null($this->file_text)) {
178 // Save the file content into cache file
179 file_put_contents($usecachefile, $this->file_text, LOCK_EX);
180 dolChmod($usecachefile);
181 }
182 }
183
184 $file_text_array = preg_split("[\n]", $this->file_text);
185
186 // is this text vcalendar standard text ? on line 1 is BEGIN:VCALENDAR
187 if (!stristr($file_text_array[0], 'BEGIN:VCALENDAR')) {
188 return 'error not VCALENDAR';
189 }
190
191 $insidealarm = 0;
192 $tmpkey = '';
193 $tmpvalue = '';
194 $type = '';
195 foreach ($file_text_array as $text) {
196 $text = trim($text); // trim one line
197 if (!empty($text)) {
198 // get Key and Value VCALENDAR:Begin -> Key = VCALENDAR, Value = begin
199 list($key, $value) = $this->retun_key_value($text);
200 //var_dump($text.' -> '.$key.' - '.$value);
201
202 switch ($text) { // search special string
203 case "BEGIN:VTODO":
204 $this->todo_count += 1; // new to do begin
205 $type = "VTODO";
206 break;
207
208 case "BEGIN:VEVENT":
209 $this->event_count += 1; // new event begin
210 $type = "VEVENT";
211 break;
212
213 case "BEGIN:VFREEBUSY":
214 $this->freebusy_count += 1; // new event begin
215 $type = "VFREEBUSY";
216 break;
217
218 case "BEGIN:VCALENDAR": // all other special string
219 case "BEGIN:DAYLIGHT":
220 case "BEGIN:VTIMEZONE":
221 case "BEGIN:STANDARD":
222 $type = $value; // save array under value key
223 break;
224
225 case "END:VTODO": // end special text - goto VCALENDAR key
226 case "END:VEVENT":
227 case "END:VFREEBUSY":
228 case "END:VCALENDAR":
229 case "END:DAYLIGHT":
230 case "END:VTIMEZONE":
231 case "END:STANDARD":
232 $type = "VCALENDAR";
233 break;
234
235 // Manage VALARM that are inside a VEVENT to avoid fields of VALARM to overwrites fields of VEVENT
236 case "BEGIN:VALARM":
237 $insidealarm = 1;
238 break;
239 case "END:VALARM":
240 $insidealarm = 0;
241 break;
242
243 default: // no special string (SUMMARY, DESCRIPTION, ...)
244 if ($tmpvalue) {
245 $tmpvalue .= $text;
246 if (!preg_match('/=$/', $text)) { // No more lines
247 $key = $tmpkey;
248 $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue));
249 $tmpkey = '';
250 $tmpvalue = '';
251 }
252 } elseif (preg_match('/^ENCODING=QUOTED-PRINTABLE:/i', $value)) {
253 if (preg_match('/=$/', $value)) {
254 $tmpkey = $key;
255 $tmpvalue .= preg_replace('/=$/', "", $value); // We must wait to have next line to have complete message
256 } else {
257 $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue.$value));
258 }
259 } //$value=quotedPrintDecode($tmpvalue.$value);
260 if (!$insidealarm && !$tmpkey) {
261 $this->add_to_array($type, $key, $value); // add to array
262 }
263 break;
264 }
265 }
266 }
267
268 //var_dump($this->cal);
269 return $this->cal;
270 }
271
272 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
281 public function add_to_array($type, $key, $value)
282 {
283 // phpcs:enable
284
285 //print 'type='.$type.' key='.$key.' value='.$value.'<br>'."\n";
286
287 if (empty($key)) {
288 $key = $this->last_key;
289 switch ($type) {
290 case 'VEVENT':
291 // @phan-suppress-next-line PhanTypeMismatchDimFetch
292 $value = $this->cal[$type][$this->event_count][$key].$value;
293 break;
294 case 'VFREEBUSY':
295 // @phan-suppress-next-line PhanTypeMismatchDimFetch
296 $value = $this->cal[$type][$this->freebusy_count][$key].$value;
297 break;
298 case 'VTODO':
299 // @phan-suppress-next-line PhanTypeMismatchDimFetch
300 $value = $this->cal[$type][$this->todo_count][$key].$value;
301 break;
302 }
303 }
304
305 if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED")) {
306 $value = $this->ical_date_to_unix($value);
307 }
308 //if ($key == "RRULE" ) $value = $this->ical_rrule($value);
309
310 if (stristr($key, "DTSTART") || stristr($key, "DTEND") || stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
311 if (stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
312 list($key, $value) = array($key, $value);
313 } else {
314 list($key, $value) = $this->ical_dt_date($key, $value);
315 }
316 }
317
318 switch ($type) {
319 case "VTODO":
320 // @phan-suppress-next-line PhanTypeMismatchReturn
321 $this->cal[$type][$this->todo_count][$key] = $value;
322 break;
323
324 case "VEVENT":
325 // @phan-suppress-next-line PhanTypeMismatchReturn
326 $this->cal[$type][$this->event_count][$key] = $value;
327 break;
328
329 case "VFREEBUSY":
330 // @phan-suppress-next-line PhanTypeMismatchReturn
331 $this->cal[$type][$this->freebusy_count][$key] = $value;
332 break;
333
334 default:
335 // @phan-suppress-next-line PhanTypeMismatchProperty
336 $this->cal[$type][$key] = $value;
337 break;
338 }
339 $this->last_key = $key;
340 }
341
342 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
349 public function retun_key_value($text)
350 {
351 // phpcs:enable
352 /*
353 preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
354
355 if (empty($matches))
356 {
357 return array(false,$text);
358 }
359 else
360 {
361 $matches = array_splice($matches, 1, 2);
362 return $matches;
363 }*/
364 return explode(':', $text, 2);
365 }
366
367 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
374 public function ical_rrule($value)
375 {
376 // phpcs:enable
377 $result = array();
378 $rrule = explode(';', $value);
379 foreach ($rrule as $line) {
380 $rcontent = explode('=', $line);
381 $result[$rcontent[0]] = $rcontent[1];
382 }
383 return $result;
384 }
385
386 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
393 public function ical_date_to_unix($ical_date)
394 {
395 // phpcs:enable
396 $ical_date = str_replace('T', '', $ical_date);
397 $ical_date = str_replace('Z', '', $ical_date);
398
399 $ntime = 0;
400 // TIME LIMITED EVENT
401 $date = array();
402 if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})([0-9]{0,2})/', $ical_date, $date)) {
403 $ntime = dol_mktime((int) $date[4], (int) $date[5], (int) $date[6], (int) $date[2], (int) $date[3], (int) $date[1], true);
404 }
405
406 //if (empty($date[4])) print 'Error bad date: '.$ical_date.' - date1='.$date[1];
407 //print dol_print_date($ntime,'dayhour');exit;
408 return $ntime; // ntime is a GTM time
409 }
410
411 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
419 public function ical_dt_date($key, $value)
420 {
421 // phpcs:enable
422 $return_value = array();
423
424 // Analyse TZID
425 $temp = explode(";", $key);
426
427 if (empty($temp[1])) { // not TZID in key
428 $value = $this->ical_date_to_unix($value);
429 return array($key, $value);
430 }
431
432 $value = str_replace('T', '', $value);
433
434 $key = $temp[0];
435 $temp = explode("=", $temp[1]);
436 $return_value[$temp[0]] = trim($temp[1], '"'); // RFC 5545 allows quoted parameter values (TZID="Europe/Berlin")
437 $return_value['unixtime'] = $this->ical_date_to_unix($value); // Local time as GMT timestamp; TZ correction is done by caller (AGENDA_EXT_BUGGEDFILEx)
438
439 return array($key, $return_value);
440 }
441
442 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
448 public function get_sort_event_list()
449 {
450 // phpcs:enable
451 $temp = $this->get_event_list();
452 if (!empty($temp)) {
453 usort($temp, array($this, "ical_dtstart_compare")); // false-positive @phpstan-ignore-line
454 return $temp;
455 } else {
456 return false;
457 }
458 }
459
460 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
468 public function ical_dtstart_compare($a, $b)
469 {
470 // phpcs:enable
471 return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
472 }
473
474 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
480 public function get_event_list()
481 {
482 // phpcs:enable
483 return (empty($this->cal['VEVENT']) ? array() : $this->cal['VEVENT']);
484 }
485
486 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
492 public function get_freebusy_list()
493 {
494 // phpcs:enable
495 return (empty($this->cal['VFREEBUSY']) ? array() : $this->cal['VFREEBUSY']);
496 }
497
498 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
504 public function get_todo_list()
505 {
506 // phpcs:enable
507 return $this->cal['VTODO'];
508 }
509
510 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
516 public function get_calender_data()
517 {
518 // phpcs:enable
519 return $this->cal['VCALENDAR'];
520 }
521
522 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
528 public function get_all_data()
529 {
530 // phpcs:enable
531 return $this->cal;
532 }
533}
Class to read/parse ICal calendars.
get_all_data()
Return array with all data.
get_event_list()
Return eventlist array (not sorted eventlist array)
parse($uri, $usecachefile='', $delaycache=3600)
Translate Calendar.
ical_dt_date($key, $value)
Return unix date from iCal date format.
ical_rrule($value)
Parse RRULE return array.
retun_key_value($text)
Parse text "XXXX:value text some with : " and return array($key = "XXXX", $value="value");.
get_todo_count()
Returns the number of to do.
ical_dtstart_compare($a, $b)
Compare two unix timestamp.
get_event_count()
Returns the number of calendar events.
__construct()
Constructor.
add_to_array($type, $key, $value)
Add to $this->ical array one value and key.
read_file($file)
Read text file, icalender text file.
get_sort_event_list()
Return sorted eventlist as array or false if calendar is empty.
get_freebusy_list()
Return freebusy array (not sort eventlist array)
get_todo_list()
Return to do array (not sorted todo array)
get_calender_data()
Return base calendar data.
ical_date_to_unix($ical_date)
Return Unix time from ical date time format (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
dol_filemtime($pathoffile)
Return time of a file.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dolChmod($filepath, $newmask='')
Change mod of a file.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
getURLContent($url, $postorget='GET', $param='', $followlocation=1, $addheaders=array(), $allowedschemes=array('http', 'https'), $localurl=0, $ssl_verifypeer=-1, $timeoutconnect=0, $timeoutresponse=0, $otherCurlOptions=array(), $morelogsuffix='')
Function to get a content from an URL (use proxy if proxy defined).
quotedPrintDecode($str)
Decode vcal format.
Definition xcal.lib.php:640