dolibarr  18.0.6
xcal.lib.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2008-2011 Laurent Destailleur <eldy@users.sourceforge.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 
35 function build_calfile($format, $title, $desc, $events_array, $outputfile)
36 {
37  global $conf, $langs;
38 
39  dol_syslog("xcal.lib.php::build_calfile Build cal file ".$outputfile." to format ".$format);
40 
41  if (empty($outputfile)) {
42  // -1 = error
43  return -1;
44  }
45 
46  $nbevents = 0;
47 
48  // Note: A cal file is an UTF8 encoded file
49  $calfileh = fopen($outputfile, "w");
50 
51  if ($calfileh) {
52  include_once DOL_DOCUMENT_ROOT."/core/lib/date.lib.php";
53 
54  $now = dol_now();
55  $encoding = "";
56 
57  if ($format === "vcal") {
58  $encoding = "ENCODING=QUOTED-PRINTABLE:";
59  }
60 
61  // Print header
62  fwrite($calfileh, "BEGIN:VCALENDAR\n");
63 
64  // version is always "2.0"
65  fwrite($calfileh, "VERSION:2.0\n");
66 
67  fwrite($calfileh, "METHOD:PUBLISH\n");
68  fwrite($calfileh, "PRODID:-//DOLIBARR ".DOL_VERSION."\n");
69  fwrite($calfileh, "CALSCALE:GREGORIAN\n");
70  fwrite($calfileh, "X-WR-CALNAME:".$encoding.format_cal($format, $title)."\n");
71  fwrite($calfileh, "X-WR-CALDESC:".$encoding.format_cal($format, $desc)."\n");
72  //fwrite($calfileh,"X-WR-TIMEZONE:Europe/Paris\n");
73 
74  if (!empty($conf->global->MAIN_AGENDA_EXPORT_CACHE) && $conf->global->MAIN_AGENDA_EXPORT_CACHE > 60) {
75  $hh = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "hour");
76  $mm = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "min");
77  $ss = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "sec");
78 
79  fwrite($calfileh, "X-PUBLISHED-TTL: P".$hh."H".$mm."M".$ss."S\n");
80  }
81 
82  foreach ($events_array as $key => $event) {
83  // See http://fr.wikipedia.org/wiki/ICalendar for format
84  // See http://www.ietf.org/rfc/rfc2445.txt for RFC
85 
86  // TODO: avoid use extra event array, use objects direct thahtwas created before
87 
88  $uid = $event["uid"];
89  $type = $event["type"];
90  $startdate = $event["startdate"];
91  $duration = $event["duration"];
92  $enddate = $event["enddate"];
93  $summary = $event["summary"];
94  $category = $event["category"];
95  $priority = $event["priority"];
96  $fulldayevent = $event["fulldayevent"];
97  $location = $event["location"];
98  $email = $event["email"];
99  $url = $event["url"];
100  $transparency = $event["transparency"];
101  $description = dol_string_nohtmltag(preg_replace("/<br[\s\/]?>/i", "\n", $event["desc"]), 0);
102  $created = $event["created"];
103  $modified = $event["modified"];
104  $assignedUsers = $event["assignedUsers"];
105  //print $fulldayevent.' '.dol_print_date($startdate, 'dayhour', 'gmt');
106 
107  // Format
108  $summary = format_cal($format, $summary);
109  $description = format_cal($format, $description);
110  $category = format_cal($format, $category);
111  $location = format_cal($format, $location);
112 
113  // Output the vCard/iCal VEVENT object
114  /*
115  Example from Google ical export for a 1 hour event:
116  BEGIN:VEVENT
117  DTSTART:20101103T120000Z
118  DTEND:20101103T130000Z
119  DTSTAMP:20101121T144902Z
120  UID:4eilllcsq8r1p87ncg7vc8dbpk@google.com
121  CREATED:20101121T144657Z
122  DESCRIPTION:
123  LAST-MODIFIED:20101121T144707Z
124  LOCATION:
125  SEQUENCE:0
126  STATUS:CONFIRMED
127  SUMMARY:Tâche 1 heure
128  TRANSP:OPAQUE
129  END:VEVENT
130 
131  Example from Google ical export for a 1 day event:
132  BEGIN:VEVENT
133  DTSTART;VALUE=DATE:20101102
134  DTEND;VALUE=DATE:20101103
135  DTSTAMP:20101121T144902Z
136  UID:d09t43kcf1qgapu9efsmmo1m6k@google.com
137  CREATED:20101121T144607Z
138  DESCRIPTION:
139  LAST-MODIFIED:20101121T144607Z
140  LOCATION:
141  SEQUENCE:0
142  STATUS:CONFIRMED
143  SUMMARY:Tâche 1 jour
144  TRANSP:TRANSPARENT
145  END:VEVENT
146  */
147 
148  if ($type === "event") {
149  $nbevents++;
150 
151  fwrite($calfileh, "BEGIN:VEVENT\n");
152  fwrite($calfileh, "UID:".$uid."\n");
153 
154  if (!empty($email)) {
155  fwrite($calfileh, "ORGANIZER:MAILTO:".$email."\n");
156  fwrite($calfileh, "CONTACT:MAILTO:".$email."\n");
157  }
158 
159  if (!empty($url)) {
160  fwrite($calfileh, "URL:".$url."\n");
161  }
162 
163  if (is_array($assignedUsers)) {
164  foreach ($assignedUsers as $assignedUser) {
165  if ($assignedUser->email === $email) {
166  continue;
167  }
168 
169  fwrite($calfileh, "ATTENDEE;RSVP=TRUE:mailto:".$assignedUser->email."\n");
170  }
171  }
172 
173  if ($created) {
174  fwrite($calfileh, "CREATED:".dol_print_date($created, "dayhourxcard", true)."\n");
175  }
176 
177  if ($modified) {
178  fwrite($calfileh, "LAST-MODIFIED:".dol_print_date($modified, "dayhourxcard", true)."\n");
179  }
180 
181  fwrite($calfileh, "SUMMARY:".$encoding.$summary."\n");
182  fwrite($calfileh, "DESCRIPTION:".$encoding.$description."\n");
183 
184  if (!empty($location)) {
185  fwrite($calfileh, "LOCATION:".$encoding.$location."\n");
186  }
187 
188  if ($fulldayevent) {
189  fwrite($calfileh, "X-FUNAMBOL-ALLDAY:1\n");
190  }
191 
192  // see https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcical/0f262da6-c5fd-459e-9f18-145eba86b5d2
193  if ($fulldayevent) {
194  fwrite($calfileh, "X-MICROSOFT-CDO-ALLDAYEVENT:TRUE\n");
195  }
196 
197  // Date must be GMT dates
198  // Current date
199  fwrite($calfileh, "DTSTAMP:".dol_print_date($now, "dayhourxcard", 'gmt')."\n");
200 
201  // Start date
202  $prefix = "";
203  $startdatef = dol_print_date($startdate, "dayhourxcard", 'gmt');
204 
205  if ($fulldayevent) {
206  // For fullday event, date was stored with old version by using the user timezone instead of storing the date at UTC+0
207  // in the timezone of server (so for a PHP timezone of -3, we should store '2023-05-31 21:00:00.000'
208  // Using option MAIN_STORE_FULL_EVENT_IN_GMT=1 change the behaviour to store in GMT for full day event. This must become
209  // the default behaviour but there is no way to change keeping old saved date compatible.
210  $tzforfullday = getDolGlobalString('MAIN_STORE_FULL_EVENT_IN_GMT');
211  // Local time should be used to prevent users in time zones earlier than GMT from being one day earlier
212  $prefix = ";VALUE=DATE";
213  if ($tzforfullday) {
214  $startdatef = dol_print_date($startdate, "dayxcard", 'gmt');
215  } else {
216  $startdatef = dol_print_date($startdate, "dayxcard", 'tzserver');
217  }
218  }
219 
220  fwrite($calfileh, "DTSTART".$prefix.":".$startdatef."\n");
221 
222  // End date
223  if ($fulldayevent) {
224  if (empty($enddate)) {
225  // We add 1 day needed for full day event (DTEND must be next day after event).
226  // This is mention in https://datatracker.ietf.org/doc/html/rfc5545:
227  // "The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event."
228  $enddate = dol_time_plus_duree($startdate, 1, "d");
229  }
230  } else {
231  if (empty($enddate)) {
232  $enddate = $startdate + $duration;
233  }
234  }
235 
236  $prefix = "";
237  $enddatef = dol_print_date($enddate, "dayhourxcard", 'gmt');
238 
239  if ($fulldayevent) {
240  $prefix = ";VALUE=DATE";
241  // We add 1 second so we reach the +1 day needed for full day event (DTEND must be next day after event)
242  // This is mention in https://datatracker.ietf.org/doc/html/rfc5545:
243  // "The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event."
244  $enddatef = dol_print_date($enddate + 1, "dayxcard", 'tzserver');
245  }
246 
247  fwrite($calfileh, "DTEND".$prefix.":".$enddatef."\n");
248  fwrite($calfileh, "STATUS:CONFIRMED\n");
249 
250  if (!empty($transparency)) {
251  fwrite($calfileh, "TRANSP:".$transparency."\n");
252  }
253 
254  if (!empty($category)) {
255  fwrite($calfileh, "CATEGORIES:".$encoding.$category."\n");
256  }
257 
258  fwrite($calfileh, "END:VEVENT\n");
259  }
260 
261  // Output the vCard/iCal VJOURNAL object
262  if ($type === "journal") {
263  $nbevents++;
264 
265  fwrite($calfileh, "BEGIN:VJOURNAL\n");
266  fwrite($calfileh, "UID:".$uid."\n");
267 
268  if (!empty($email)) {
269  fwrite($calfileh, "ORGANIZER:MAILTO:".$email."\n");
270  fwrite($calfileh, "CONTACT:MAILTO:".$email."\n");
271  }
272 
273  if (!empty($url)) {
274  fwrite($calfileh, "URL:".$url."\n");
275  }
276 
277  if ($created) {
278  fwrite($calfileh, "CREATED:".dol_print_date($created, "dayhourxcard", 'gmt')."\n");
279  }
280 
281  if ($modified) {
282  fwrite($calfileh, "LAST-MODIFIED:".dol_print_date($modified, "dayhourxcard", 'gmt')."\n");
283  }
284 
285  fwrite($calfileh, "SUMMARY:".$encoding.$summary."\n");
286  fwrite($calfileh, "DESCRIPTION:".$encoding.$description."\n");
287  fwrite($calfileh, "STATUS:CONFIRMED\n");
288  fwrite($calfileh, "CATEGORIES:".$category."\n");
289  fwrite($calfileh, "LOCATION:".$location."\n");
290  fwrite($calfileh, "TRANSP:OPAQUE\n");
291  fwrite($calfileh, "CLASS:CONFIDENTIAL\n");
292  fwrite($calfileh, "DTSTAMP:".dol_print_date($startdatef, "dayhourxcard", 'gmt')."\n");
293 
294  fwrite($calfileh, "END:VJOURNAL\n");
295  }
296  }
297 
298  // Footer
299  fwrite($calfileh, "END:VCALENDAR");
300 
301  fclose($calfileh);
302  dolChmod($outputfile);
303  } else {
304  dol_syslog("xcal.lib.php::build_calfile Failed to open file ".$outputfile." for writing");
305  return -2;
306  }
307 
308  return $nbevents;
309 }
310 
325 function build_rssfile($format, $title, $desc, $events_array, $outputfile, $filter = '', $url = '', $langcode = '')
326 {
327  global $user, $conf, $langs, $mysoc;
328  global $dolibarr_main_url_root;
329 
330  dol_syslog("xcal.lib.php::build_rssfile Build rss file ".$outputfile." to format ".$format);
331 
332  if (empty($outputfile)) {
333  // -1 = error
334  return -1;
335  }
336 
337  $nbevents = 0;
338 
339  $fichier = fopen($outputfile, "w");
340 
341  if ($fichier) {
342  // Print header
343  fwrite($fichier, '<?xml version="1.0" encoding="'.$langs->charset_output.'"?>');
344  fwrite($fichier, "\n");
345 
346  fwrite($fichier, '<rss version="2.0">');
347  fwrite($fichier, "\n");
348 
349  fwrite($fichier, "<channel>\n");
350  fwrite($fichier, "<title>".$title."</title>\n");
351  if ($langcode) {
352  fwrite($fichier, "<language>".$langcode."</language>\n");
353  }
354 
355  // Define $urlwithroot
356  $urlwithouturlroot = preg_replace("/".preg_quote(DOL_URL_ROOT, "/")."$/i", "", trim($dolibarr_main_url_root));
357  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
358  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
359 
360  // Url
361  if (empty($url)) {
362  $url = $urlwithroot."/public/agenda/agendaexport.php?format=rss&exportkey=".urlencode($conf->global->MAIN_AGENDA_XCAL_EXPORTKEY);
363  }
364  fwrite($fichier, "<link><![CDATA[".$url."]]></link>\n");
365 
366  // Image
367  if (!empty($mysoc->logo_squarred_small)) {
368  $urlimage = $urlwithroot.'/viewimage.php?cache=1&amp;modulepart=mycompany&amp;file='.urlencode($mysoc->logo_squarred_small);
369  if ($urlimage) {
370  fwrite($fichier, "<image><url><![CDATA[".$urlimage."]]></url><title>'.$title.</title></image>\n");
371  }
372  }
373 
374  foreach ($events_array as $key => $event) {
375  $eventqualified = true;
376 
377  if ($filter) {
378  // TODO Add a filter
379 
380  $eventqualified = false;
381  }
382 
383  if ($eventqualified) {
384  $nbevents++;
385 
386  if (is_object($event) && get_class($event) == 'WebsitePage') {
387  // Convert object into an array
388  $tmpevent = array();
389  $tmpevent['uid'] = $event->id;
390  $tmpevent['startdate'] = $event->date_creation;
391  $tmpevent['summary'] = $event->title;
392  $tmpevent['url'] = $event->fullpageurl ? $event->fullpageurl : $event->pageurl.'.php';
393  $tmpevent['author'] = $event->author_alias ? $event->author_alias : 'unknown';
394  //$tmpevent['category'] = '';
395  $tmpevent['desc'] = $event->description;
396  $tmpevent['image'] = $GLOBALS['website']->virtualhost.'/medias/'.$event->image;
397  $event = $tmpevent;
398  }
399 
400  $uid = $event["uid"];
401  $startdate = $event["startdate"];
402  $summary = $event["summary"];
403  $url = $event["url"];
404  $author = $event["author"];
405  $category = $event["category"];
406  if (!empty($event["image"])) {
407  $image = $event["image"];
408  }
409  /* No place inside a RSS
410  $priority = $event["priority"];
411  $fulldayevent = $event["fulldayevent"];
412  $location = $event["location"];
413  $email = $event["email"];
414  */
415 
416  $description = dol_string_nohtmltag(preg_replace("/<br[\s\/]?>/i", "\n", $event["desc"]), 0);
417 
418  fwrite($fichier, "<item>\n");
419  fwrite($fichier, "<title><![CDATA[".$summary."]]></title>\n");
420  fwrite($fichier, "<link><![CDATA[".$url."]]></link>\n");
421  fwrite($fichier, "<author><![CDATA[".$author."]]></author>\n");
422  fwrite($fichier, "<category><![CDATA[".$category."]]></category>\n");
423  fwrite($fichier, "<description><![CDATA[");
424 
425  if (!empty($image)) {
426  fwrite($fichier, '<p><img class="center" src="'.$image.'"/></p>');
427  }
428 
429  if ($description) {
430  fwrite($fichier, $description);
431  }
432  // else
433  // fwrite($fichier, "NoDesc");
434 
435  fwrite($fichier, "]]></description>\n");
436  fwrite($fichier, "<pubDate>".date("r", $startdate)."</pubDate>\n");
437  fwrite($fichier, "<guid isPermaLink=\"true\"><![CDATA[".$uid."]]></guid>\n");
438  fwrite($fichier, "<source><![CDATA[Dolibarr]]></source>\n");
439  fwrite($fichier, "</item>\n");
440  }
441  }
442 
443  fwrite($fichier, "</channel>");
444  fwrite($fichier, "\n");
445  fwrite($fichier, "</rss>");
446 
447  fclose($fichier);
448  dolChmod($outputfile);
449  }
450 
451  return $nbevents;
452 }
453 
461 function format_cal($format, $string)
462 {
463  $newstring = $string;
464 
465  if ($format === "vcal") {
466  $newstring = quotedPrintEncode($newstring);
467  }
468 
469  if ($format === "ical") {
470  // Replace new lines chars by "\n"
471  $newstring = preg_replace("/\r\n/i", "\\n", $newstring);
472  $newstring = preg_replace("/\n\r/i", "\\n", $newstring);
473  $newstring = preg_replace("/\n/i", "\\n", $newstring);
474 
475  // Must not exceed 75 char. Cut with "\r\n"+Space
476  $newstring = calEncode($newstring);
477  }
478 
479  return $newstring;
480 }
481 
489 function calEncode($line)
490 {
491  $out = "";
492  $newpara = "";
493 
494  // If mb_ functions exists, it"s better to use them
495  if (function_exists("mb_strlen")) {
496  $strlength = mb_strlen($line, "UTF-8");
497 
498  for ($j = 0; $j < $strlength; $j++) {
499  // Take char at position $j
500  $char = mb_substr($line, $j, 1, "UTF-8");
501 
502  if ((mb_strlen($newpara, "UTF-8") + mb_strlen($char, "UTF-8")) >= 75) {
503  // CRLF + Space for cal
504  $out .= $newpara."\r\n ";
505 
506  $newpara = "";
507  }
508 
509  $newpara .= $char;
510  }
511 
512  $out .= $newpara;
513  } else {
514  $strlength = dol_strlen($line);
515 
516  for ($j = 0; $j < $strlength; $j++) {
517  // Take char at position $j
518  $char = substr($line, $j, 1);
519 
520  if ((dol_strlen($newpara) + dol_strlen($char)) >= 75) {
521  // CRLF + Space for cal
522  $out .= $newpara."\r\n ";
523 
524  $newpara = "";
525  }
526 
527  $newpara .= $char;
528  }
529 
530  $out .= $newpara;
531  }
532 
533  return trim($out);
534 }
535 
536 
544 function quotedPrintEncode($str, $forcal = 0)
545 {
546  $lines = preg_split("/\r\n/", $str);
547  $out = "";
548 
549  foreach ($lines as $line) {
550  $newpara = "";
551 
552  // Do not use dol_strlen here, we need number of bytes
553  $strlength = strlen($line);
554 
555  for ($j = 0; $j < $strlength; $j++) {
556  $char = substr($line, $j, 1);
557  $ascii = ord($char);
558 
559  if ($ascii < 32 || $ascii === 61 || $ascii > 126) {
560  $char = "=".strtoupper(sprintf("%02X", $ascii));
561  }
562 
563  // Do not use dol_strlen here, we need number of bytes
564  if ((strlen($newpara) + strlen($char)) >= 76) {
565  // New line with carray-return (CR) and line-feed (LF)
566  $out .= $newpara."=\r\n";
567 
568  // extra space for cal
569  if ($forcal) {
570  $out .= " ";
571  }
572 
573  $newpara = "";
574  }
575 
576  $newpara .= $char;
577  }
578 
579  $out .= $newpara;
580  }
581  return trim($out);
582 }
583 
590 function quotedPrintDecode($str)
591 {
592  return trim(quoted_printable_decode(preg_replace("/=\r?\n/", "", $str)));
593 }
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:122
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition: date.lib.php:240
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolChmod($filepath, $newmask='')
Change mod of a file.
dol_now($mode='auto')
Return date for now.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
quotedPrintEncode($str, $forcal=0)
Encode into vcal format.
Definition: xcal.lib.php:544
build_calfile($format, $title, $desc, $events_array, $outputfile)
Build a file from an array of events All input params and data must be encoded in $conf->charset_outp...
Definition: xcal.lib.php:35
format_cal($format, $string)
Encode for cal export.
Definition: xcal.lib.php:461
build_rssfile($format, $title, $desc, $events_array, $outputfile, $filter='', $url='', $langcode='')
Build a file from an array of events.
Definition: xcal.lib.php:325
calEncode($line)
Cut string after 75 chars.
Definition: xcal.lib.php:489
quotedPrintDecode($str)
Decode vcal format.
Definition: xcal.lib.php:590