dolibarr 24.0.0-beta
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 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
29require_once DOL_DOCUMENT_ROOT.'/core/lib/xcal.lib.php';
30require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
31
32
36class ICal
37{
41 public $file;
42
46 public $file_text;
47
51 public $cal;
52
56 public $event_count;
57
61 public $todo_count;
62
66 public $freebusy_count;
67
71 public $last_key;
72
76 public $error;
77
78
82 public function __construct()
83 {
84 }
85
86 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
93 public function read_file($file)
94 {
95 // phpcs:enable
96 $this->file = $file;
97 $file_text = '';
98
99 //$tmpresult = getURLContent($file, 'GET', '', 1, [], ['http', 'https'], 2, 0); // To test with any URL
100 $localip = 0;
101 $sslverify = -1;
102 if (getDolGlobalString('AGENDA_EXT_CALENDAR_IP_MODE')) {
103 $localip = intval(getDolGlobalString('AGENDA_EXT_CALENDAR_IP_MODE'));
104 }
105 if (getDolGlobalString('AGENDA_EXT_CALENDAR_SSLVERIFY_MODE')) {
106 $sslverify = intval(getDolGlobalString('AGENDA_EXT_CALENDAR_SSLVERIFY_MODE'));
107 }
108 // See documentation of getURLContent function for $localip and $sslverify possible values
109 $tmpresult = getURLContent($file, 'GET', '', 1, [], ['http', 'https'], $localip, $sslverify);
110 if ($tmpresult['http_code'] != 200) {
111 $file_text = null;
112 $this->error = 'Error: '.$tmpresult['http_code'].' '.$tmpresult['content'];
113 } else {
114 $file_text = preg_replace("/[\r\n]{1,} /", "", $tmpresult['content']);
115 }
116 //var_dump($tmpresult);
117
118 return $file_text; // return all text
119 }
120
121 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
127 public function get_event_count()
128 {
129 // phpcs:enable
130 return $this->event_count;
131 }
132
133 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
139 public function get_todo_count()
140 {
141 // phpcs:enable
142 return $this->todo_count;
143 }
144
153 public function parse($uri, $usecachefile = '', $delaycache = 3600)
154 {
155 $this->cal = array(); // new empty array
156
157 $this->event_count = -1;
158 $this->file_text = '';
159
160 // Save file into a cache
161 if ($usecachefile) {
162 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
163 $datefile = dol_filemtime($usecachefile);
164 $now = dol_now('gmt');
165 //print $datefile.' '.$now.' ...';
166 if ($datefile && $datefile > ($now - $delaycache)) {
167 // We reuse the cache file
168 $this->file_text = file_get_contents($usecachefile);
169 }
170 }
171
172 // read FILE text
173 if (empty($this->file_text)) {
174 $this->file_text = $this->read_file($uri);
175
176 if ($usecachefile && !is_null($this->file_text)) {
177 // Save the file content into cache file
178 file_put_contents($usecachefile, $this->file_text, LOCK_EX);
179 dolChmod($usecachefile);
180 }
181 }
182
183 $file_text_array = preg_split("[\n]", $this->file_text);
184
185 // is this text vcalendar standard text ? on line 1 is BEGIN:VCALENDAR
186 if (!stristr($file_text_array[0], 'BEGIN:VCALENDAR')) {
187 return 'error not VCALENDAR';
188 }
189
190 $insidealarm = 0;
191 $tmpkey = '';
192 $tmpvalue = '';
193 $type = '';
194 foreach ($file_text_array as $text) {
195 $text = trim($text); // trim one line
196 if (!empty($text)) {
197 // get Key and Value VCALENDAR:Begin -> Key = VCALENDAR, Value = begin
198 list($key, $value) = $this->retun_key_value($text);
199 //var_dump($text.' -> '.$key.' - '.$value);
200
201 switch ($text) { // search special string
202 case "BEGIN:VTODO":
203 $this->todo_count += 1; // new to do begin
204 $type = "VTODO";
205 break;
206
207 case "BEGIN:VEVENT":
208 $this->event_count += 1; // new event begin
209 $type = "VEVENT";
210 break;
211
212 case "BEGIN:VFREEBUSY":
213 $this->freebusy_count += 1; // new event begin
214 $type = "VFREEBUSY";
215 break;
216
217 case "BEGIN:VCALENDAR": // all other special string
218 case "BEGIN:DAYLIGHT":
219 case "BEGIN:VTIMEZONE":
220 case "BEGIN:STANDARD":
221 $type = $value; // save array under value key
222 break;
223
224 case "END:VTODO": // end special text - goto VCALENDAR key
225 case "END:VEVENT":
226 case "END:VFREEBUSY":
227 case "END:VCALENDAR":
228 case "END:DAYLIGHT":
229 case "END:VTIMEZONE":
230 case "END:STANDARD":
231 $type = "VCALENDAR";
232 break;
233
234 // Manage VALARM that are inside a VEVENT to avoid fields of VALARM to overwrites fields of VEVENT
235 case "BEGIN:VALARM":
236 $insidealarm = 1;
237 break;
238 case "END:VALARM":
239 $insidealarm = 0;
240 break;
241
242 default: // no special string (SUMMARY, DESCRIPTION, ...)
243 if ($tmpvalue) {
244 $tmpvalue .= $text;
245 if (!preg_match('/=$/', $text)) { // No more lines
246 $key = $tmpkey;
247 $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue));
248 $tmpkey = '';
249 $tmpvalue = '';
250 }
251 } elseif (preg_match('/^ENCODING=QUOTED-PRINTABLE:/i', $value)) {
252 if (preg_match('/=$/', $value)) {
253 $tmpkey = $key;
254 $tmpvalue .= preg_replace('/=$/', "", $value); // We must wait to have next line to have complete message
255 } else {
256 $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue.$value));
257 }
258 } //$value=quotedPrintDecode($tmpvalue.$value);
259 if (!$insidealarm && !$tmpkey) {
260 $this->add_to_array($type, $key, $value); // add to array
261 }
262 break;
263 }
264 }
265 }
266
267 //var_dump($this->cal);
268 return $this->cal;
269 }
270
271 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
280 public function add_to_array($type, $key, $value)
281 {
282 // phpcs:enable
283
284 //print 'type='.$type.' key='.$key.' value='.$value.'<br>'."\n";
285
286 if (empty($key)) {
287 $key = $this->last_key;
288 switch ($type) {
289 case 'VEVENT':
290 // @phan-suppress-next-line PhanTypeMismatchDimFetch
291 $value = $this->cal[$type][$this->event_count][$key].$value;
292 break;
293 case 'VFREEBUSY':
294 // @phan-suppress-next-line PhanTypeMismatchDimFetch
295 $value = $this->cal[$type][$this->freebusy_count][$key].$value;
296 break;
297 case 'VTODO':
298 // @phan-suppress-next-line PhanTypeMismatchDimFetch
299 $value = $this->cal[$type][$this->todo_count][$key].$value;
300 break;
301 }
302 }
303
304 if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED")) {
305 $value = $this->ical_date_to_unix($value);
306 }
307 //if ($key == "RRULE" ) $value = $this->ical_rrule($value);
308
309 if (stristr($key, "DTSTART") || stristr($key, "DTEND") || stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
310 if (stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
311 list($key, $value) = array($key, $value);
312 } else {
313 list($key, $value) = $this->ical_dt_date($key, $value);
314 }
315 }
316
317 switch ($type) {
318 case "VTODO":
319 // @phan-suppress-next-line PhanTypeMismatchReturn
320 $this->cal[$type][$this->todo_count][$key] = $value;
321 break;
322
323 case "VEVENT":
324 // @phan-suppress-next-line PhanTypeMismatchReturn
325 $this->cal[$type][$this->event_count][$key] = $value;
326 break;
327
328 case "VFREEBUSY":
329 // @phan-suppress-next-line PhanTypeMismatchReturn
330 $this->cal[$type][$this->freebusy_count][$key] = $value;
331 break;
332
333 default:
334 // @phan-suppress-next-line PhanTypeMismatchProperty
335 $this->cal[$type][$key] = $value;
336 break;
337 }
338 $this->last_key = $key;
339 }
340
341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
348 public function retun_key_value($text)
349 {
350 // phpcs:enable
351 /*
352 preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
353
354 if (empty($matches))
355 {
356 return array(false,$text);
357 }
358 else
359 {
360 $matches = array_splice($matches, 1, 2);
361 return $matches;
362 }*/
363 return explode(':', $text, 2);
364 }
365
366 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
373 public function ical_rrule($value)
374 {
375 // phpcs:enable
376 $result = array();
377 $rrule = explode(';', $value);
378 foreach ($rrule as $line) {
379 $rcontent = explode('=', $line);
380 $result[$rcontent[0]] = $rcontent[1];
381 }
382 return $result;
383 }
384
385 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
392 public function ical_date_to_unix($ical_date)
393 {
394 // phpcs:enable
395 $ical_date = str_replace('T', '', $ical_date);
396 $ical_date = str_replace('Z', '', $ical_date);
397
398 $ntime = 0;
399 // TIME LIMITED EVENT
400 $date = array();
401 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)) {
402 $ntime = dol_mktime((int) $date[4], (int) $date[5], (int) $date[6], (int) $date[2], (int) $date[3], (int) $date[1], true);
403 }
404
405 //if (empty($date[4])) print 'Error bad date: '.$ical_date.' - date1='.$date[1];
406 //print dol_print_date($ntime,'dayhour');exit;
407 return $ntime; // ntime is a GTM time
408 }
409
410 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
418 public function ical_dt_date($key, $value)
419 {
420 // phpcs:enable
421 $return_value = array();
422
423 // Analyse TZID
424 $temp = explode(";", $key);
425
426 if (empty($temp[1])) { // not TZID in key
427 $value = $this->ical_date_to_unix($value);
428 return array($key, $value);
429 }
430
431 $value = str_replace('T', '', $value);
432
433 $key = $temp[0];
434 $temp = explode("=", $temp[1]);
435 $return_value[$temp[0]] = $temp[1];
436 $return_value['unixtime'] = $value;
437
438 return array($key, $return_value);
439 }
440
441 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
447 public function get_sort_event_list()
448 {
449 // phpcs:enable
450 $temp = $this->get_event_list();
451 if (!empty($temp)) {
452 usort($temp, array($this, "ical_dtstart_compare")); // false-positive @phpstan-ignore-line
453 return $temp;
454 } else {
455 return false;
456 }
457 }
458
459 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
467 public function ical_dtstart_compare($a, $b)
468 {
469 // phpcs:enable
470 return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
471 }
472
473 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
479 public function get_event_list()
480 {
481 // phpcs:enable
482 return (empty($this->cal['VEVENT']) ? array() : $this->cal['VEVENT']);
483 }
484
485 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
491 public function get_freebusy_list()
492 {
493 // phpcs:enable
494 return (empty($this->cal['VFREEBUSY']) ? array() : $this->cal['VFREEBUSY']);
495 }
496
497 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
503 public function get_todo_list()
504 {
505 // phpcs:enable
506 return $this->cal['VTODO'];
507 }
508
509 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
515 public function get_calender_data()
516 {
517 // phpcs:enable
518 return $this->cal['VCALENDAR'];
519 }
520
521 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
527 public function get_all_data()
528 {
529 // phpcs:enable
530 return $this->cal;
531 }
532}
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:636