dolibarr  16.0.5
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  // Note: A cal file is an UTF8 encoded file
47  $calfileh = fopen($outputfile, "w");
48 
49  if ($calfileh) {
50  include_once DOL_DOCUMENT_ROOT."/core/lib/date.lib.php";
51 
52  $now = dol_now();
53  $encoding = "";
54 
55  if ($format === "vcal") {
56  $encoding = "ENCODING=QUOTED-PRINTABLE:";
57  }
58 
59  // Print header
60  fwrite($calfileh, "BEGIN:VCALENDAR\n");
61 
62  // version is always "2.0"
63  fwrite($calfileh, "VERSION:2.0\n");
64 
65  fwrite($calfileh, "METHOD:PUBLISH\n");
66  fwrite($calfileh, "PRODID:-//DOLIBARR ".DOL_VERSION."\n");
67  fwrite($calfileh, "CALSCALE:GREGORIAN\n");
68  fwrite($calfileh, "X-WR-CALNAME:".$encoding.format_cal($format, $title)."\n");
69  fwrite($calfileh, "X-WR-CALDESC:".$encoding.format_cal($format, $desc)."\n");
70  //fwrite($calfileh,"X-WR-TIMEZONE:Europe/Paris\n");
71 
72  if (!empty($conf->global->MAIN_AGENDA_EXPORT_CACHE) && $conf->global->MAIN_AGENDA_EXPORT_CACHE > 60) {
73  $hh = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "hour");
74  $mm = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "min");
75  $ss = convertSecondToTime($conf->global->MAIN_AGENDA_EXPORT_CACHE, "sec");
76 
77  fwrite($calfileh, "X-PUBLISHED-TTL: P".$hh."H".$mm."M".$ss."S\n");
78  }
79 
80  foreach ($events_array as $key => $event) {
81  // See http://fr.wikipedia.org/wiki/ICalendar for format
82  // See http://www.ietf.org/rfc/rfc2445.txt for RFC
83 
84  // TODO: avoid use extra event array, use objects direct thahtwas created before
85 
86  $uid = $event["uid"];
87  $type = $event["type"];
88  $startdate = $event["startdate"];
89  $duration = $event["duration"];
90  $enddate = $event["enddate"];
91  $summary = $event["summary"];
92  $category = $event["category"];
93  $priority = $event["priority"];
94  $fulldayevent = $event["fulldayevent"];
95  $location = $event["location"];
96  $email = $event["email"];
97  $url = $event["url"];
98  $transparency = $event["transparency"];
99  $description = dol_string_nohtmltag(preg_replace("/<br[\s\/]?>/i", "\n", $event["desc"]), 0);
100  $created = $event["created"];
101  $modified = $event["modified"];
102  $assignedUsers = $event["assignedUsers"];
103  //print $fulldayevent.' '.dol_print_date($startdate, 'dayhour', 'gmt');
104 
105  // Format
106  $summary = format_cal($format, $summary);
107  $description = format_cal($format, $description);
108  $category = format_cal($format, $category);
109  $location = format_cal($format, $location);
110 
111  // Output the vCard/iCal VEVENT object
112  /*
113  Example from Google ical export for a 1 hour event:
114  BEGIN:VEVENT
115  DTSTART:20101103T120000Z
116  DTEND:20101103T130000Z
117  DTSTAMP:20101121T144902Z
118  UID:4eilllcsq8r1p87ncg7vc8dbpk@google.com
119  CREATED:20101121T144657Z
120  DESCRIPTION:
121  LAST-MODIFIED:20101121T144707Z
122  LOCATION:
123  SEQUENCE:0
124  STATUS:CONFIRMED
125  SUMMARY:Tâche 1 heure
126  TRANSP:OPAQUE
127  END:VEVENT
128 
129  Example from Google ical export for a 1 day event:
130  BEGIN:VEVENT
131  DTSTART;VALUE=DATE:20101102
132  DTEND;VALUE=DATE:20101103
133  DTSTAMP:20101121T144902Z
134  UID:d09t43kcf1qgapu9efsmmo1m6k@google.com
135  CREATED:20101121T144607Z
136  DESCRIPTION:
137  LAST-MODIFIED:20101121T144607Z
138  LOCATION:
139  SEQUENCE:0
140  STATUS:CONFIRMED
141  SUMMARY:Tâche 1 jour
142  TRANSP:TRANSPARENT
143  END:VEVENT
144  */
145 
146  if ($type === "event") {
147  fwrite($calfileh, "BEGIN:VEVENT\n");
148  fwrite($calfileh, "UID:".$uid."\n");
149 
150  if (!empty($email)) {
151  fwrite($calfileh, "ORGANIZER:MAILTO:".$email."\n");
152  fwrite($calfileh, "CONTACT:MAILTO:".$email."\n");
153  }
154 
155  if (!empty($url)) {
156  fwrite($calfileh, "URL:".$url."\n");
157  }
158 
159  if (is_array($assignedUsers)) {
160  foreach ($assignedUsers as $assignedUser) {
161  if ($assignedUser->email === $email) {
162  continue;
163  }
164 
165  fwrite($calfileh, "ATTENDEE;RSVP=TRUE:mailto:".$assignedUser->email."\n");
166  }
167  }
168 
169  if ($created) {
170  fwrite($calfileh, "CREATED:".dol_print_date($created, "dayhourxcard", true)."\n");
171  }
172 
173  if ($modified) {
174  fwrite($calfileh, "LAST-MODIFIED:".dol_print_date($modified, "dayhourxcard", true)."\n");
175  }
176 
177  fwrite($calfileh, "SUMMARY:".$encoding.$summary."\n");
178  fwrite($calfileh, "DESCRIPTION:".$encoding.$description."\n");
179 
180  if (!empty($location)) {
181  fwrite($calfileh, "LOCATION:".$encoding.$location."\n");
182  }
183 
184  if ($fulldayevent) {
185  fwrite($calfileh, "X-FUNAMBOL-ALLDAY:1\n");
186  }
187 
188  // see https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcical/0f262da6-c5fd-459e-9f18-145eba86b5d2
189  if ($fulldayevent) {
190  fwrite($calfileh, "X-MICROSOFT-CDO-ALLDAYEVENT:TRUE\n");
191  }
192 
193  // Date must be GMT dates
194  // Current date
195  fwrite($calfileh, "DTSTAMP:".dol_print_date($now, "dayhourxcard", 'gmt')."\n");
196 
197  // Start date
198  $prefix = "";
199  $startdatef = dol_print_date($startdate, "dayhourxcard", 'gmt');
200 
201  if ($fulldayevent) {
202  // Local time
203  $prefix = ";VALUE=DATE";
204  $startdatef = dol_print_date($startdate, "dayxcard", 'gmt');
205  }
206 
207  fwrite($calfileh, "DTSTART".$prefix.":".$startdatef."\n");
208 
209  // End date
210  if ($fulldayevent) {
211  if (empty($enddate)) {
212  // We add 1 day needed for full day event (DTEND must be next day after event).
213  // This is mention in https://datatracker.ietf.org/doc/html/rfc5545:
214  // "The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event."
215  $enddate = dol_time_plus_duree($startdate, 1, "d");
216  }
217  } else {
218  if (empty($enddate)) {
219  $enddate = $startdate + $duration;
220  }
221  }
222 
223  $prefix = "";
224  $enddatef = dol_print_date($enddate, "dayhourxcard", 'gmt');
225 
226  if ($fulldayevent) {
227  $prefix = ";VALUE=DATE";
228  // We add 1 second so we reach the +1 day needed for full day event (DTEND must be next day after event)
229  // This is mention in https://datatracker.ietf.org/doc/html/rfc5545:
230  // "The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event."
231  $enddatef = dol_print_date($enddate + 1, "dayxcard", 'gmt');
232  }
233 
234  fwrite($calfileh, "DTEND".$prefix.":".$enddatef."\n");
235  fwrite($calfileh, "STATUS:CONFIRMED\n");
236 
237  if (!empty($transparency)) {
238  fwrite($calfileh, "TRANSP:".$transparency."\n");
239  }
240 
241  if (!empty($category)) {
242  fwrite($calfileh, "CATEGORIES:".$encoding.$category."\n");
243  }
244 
245  fwrite($calfileh, "END:VEVENT\n");
246  }
247 
248  // Output the vCard/iCal VJOURNAL object
249  if ($type === "journal") {
250  fwrite($calfileh, "BEGIN:VJOURNAL\n");
251  fwrite($calfileh, "UID:".$uid."\n");
252 
253  if (!empty($email)) {
254  fwrite($calfileh, "ORGANIZER:MAILTO:".$email."\n");
255  fwrite($calfileh, "CONTACT:MAILTO:".$email."\n");
256  }
257 
258  if (!empty($url)) {
259  fwrite($calfileh, "URL:".$url."\n");
260  }
261 
262  if ($created) {
263  fwrite($calfileh, "CREATED:".dol_print_date($created, "dayhourxcard", 'gmt')."\n");
264  }
265 
266  if ($modified) {
267  fwrite($calfileh, "LAST-MODIFIED:".dol_print_date($modified, "dayhourxcard", 'gmt')."\n");
268  }
269 
270  fwrite($calfileh, "SUMMARY:".$encoding.$summary."\n");
271  fwrite($calfileh, "DESCRIPTION:".$encoding.$description."\n");
272  fwrite($calfileh, "STATUS:CONFIRMED\n");
273  fwrite($calfileh, "CATEGORIES:".$category."\n");
274  fwrite($calfileh, "LOCATION:".$location."\n");
275  fwrite($calfileh, "TRANSP:OPAQUE\n");
276  fwrite($calfileh, "CLASS:CONFIDENTIAL\n");
277  fwrite($calfileh, "DTSTAMP:".dol_print_date($startdatef, "dayhourxcard", 'gmt')."\n");
278 
279  fwrite($calfileh, "END:VJOURNAL\n");
280  }
281  }
282 
283  // Footer
284  fwrite($calfileh, "END:VCALENDAR");
285 
286  fclose($calfileh);
287 
288  if (!empty($conf->global->MAIN_UMASK)) {
289  @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
290  }
291  } else {
292  dol_syslog("xcal.lib.php::build_calfile Failed to open file ".$outputfile." for writing");
293  return -2;
294  }
295 }
296 
311 function build_rssfile($format, $title, $desc, $events_array, $outputfile, $filter = '', $url = '', $langcode = '')
312 {
313  global $user, $conf, $langs;
314  global $dolibarr_main_url_root;
315 
316  dol_syslog("xcal.lib.php::build_rssfile Build rss file ".$outputfile." to format ".$format);
317 
318  if (empty($outputfile)) {
319  // -1 = error
320  return -1;
321  }
322 
323  $fichier = fopen($outputfile, "w");
324 
325  if ($fichier) {
326  $date = date("r");
327 
328  // Print header
329  fwrite($fichier, '<?xml version="1.0" encoding="'.$langs->charset_output.'"?>');
330  fwrite($fichier, "\n");
331 
332  fwrite($fichier, '<rss version="2.0">');
333  fwrite($fichier, "\n");
334 
335  fwrite($fichier, "<channel>\n");
336  fwrite($fichier, "<title>".$title."</title>\n");
337  if ($langcode) {
338  fwrite($fichier, "<language>".$langcode."</language>\n");
339  }
340 
341  /*
342  fwrite($fichier, "<description><![CDATA[".$desc.".]]></description>"."\n".
343  // "<language>fr</language>"."\n".
344  "<copyright>Dolibarr</copyright>"."\n".
345  "<lastBuildDate>".$date."</lastBuildDate>"."\n".
346  "<generator>Dolibarr</generator>"."\n");
347  */
348 
349  if (empty($url)) {
350  // Define $urlwithroot
351  $urlwithouturlroot = preg_replace("/".preg_quote(DOL_URL_ROOT, "/")."$/i", "", trim($dolibarr_main_url_root));
352  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
353  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
354 
355  $url = $urlwithroot."/public/agenda/agendaexport.php?format=rss&exportkey=".urlencode($conf->global->MAIN_AGENDA_XCAL_EXPORTKEY);
356  }
357 
358  fwrite($fichier, "<link><![CDATA[".$url."]]></link>\n");
359 
360  foreach ($events_array as $key => $event) {
361  $eventqualified = true;
362 
363  if ($filter) {
364  // TODO Add a filter
365 
366  $eventqualified = false;
367  }
368 
369  if ($eventqualified) {
370  if (is_object($event) && get_class($event) == 'WebsitePage') {
371  // Convert object into an array
372  $tmpevent = array();
373  $tmpevent['uid'] = $event->id;
374  $tmpevent['startdate'] = $event->date_creation;
375  $tmpevent['summary'] = $event->title;
376  $tmpevent['url'] = $event->fullpageurl ? $event->fullpageurl : $event->pageurl.'.php';
377  $tmpevent['author'] = $event->author_alias ? $event->author_alias : 'unknown';
378  //$tmpevent['category'] = '';
379  $tmpevent['desc'] = $event->description;
380 
381  $event = $tmpevent;
382  }
383 
384  $uid = $event["uid"];
385  $startdate = $event["startdate"];
386  $summary = $event["summary"];
387  $url = $event["url"];
388  $author = $event["author"];
389  $category = $event["category"];
390 
391  /* No place inside a RSS
392  $priority = $event["priority"];
393  $fulldayevent = $event["fulldayevent"];
394  $location = $event["location"];
395  $email = $event["email"];
396  */
397 
398  $description = dol_string_nohtmltag(preg_replace("/<br[\s\/]?>/i", "\n", $event["desc"]), 0);
399 
400  fwrite($fichier, "<item>\n");
401  fwrite($fichier, "<title><![CDATA[".$summary."]]></title>\n");
402  fwrite($fichier, "<link><![CDATA[".$url."]]></link>\n");
403  fwrite($fichier, "<author><![CDATA[".$author."]]></author>\n");
404  fwrite($fichier, "<category><![CDATA[".$category."]]></category>\n");
405  fwrite($fichier, "<description><![CDATA[");
406 
407  if ($description) {
408  fwrite($fichier, $description);
409  }
410  // else
411  // fwrite($fichier, "NoDesc");
412 
413  fwrite($fichier, "]]></description>\n");
414  fwrite($fichier, "<pubDate>".date("r", $startdate)."</pubDate>\n");
415  fwrite($fichier, "<guid isPermaLink=\"true\"><![CDATA[".$uid."]]></guid>\n");
416  fwrite($fichier, "<source><![CDATA[Dolibarr]]></source>\n");
417  fwrite($fichier, "</item>\n");
418  }
419  }
420 
421  fwrite($fichier, "</channel>");
422  fwrite($fichier, "\n");
423  fwrite($fichier, "</rss>");
424 
425  fclose($fichier);
426 
427  if (!empty($conf->global->MAIN_UMASK)) {
428  @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
429  }
430  }
431 }
432 
440 function format_cal($format, $string)
441 {
442  global $conf;
443 
444  $newstring = $string;
445 
446  if ($format === "vcal") {
447  $newstring = quotedPrintEncode($newstring);
448  }
449 
450  if ($format === "ical") {
451  // Replace new lines chars by "\n"
452  $newstring = preg_replace("/\r\n/i", "\\n", $newstring);
453  $newstring = preg_replace("/\n\r/i", "\\n", $newstring);
454  $newstring = preg_replace("/\n/i", "\\n", $newstring);
455 
456  // Must not exceed 75 char. Cut with "\r\n"+Space
457  $newstring = calEncode($newstring);
458  }
459 
460  return $newstring;
461 }
462 
470 function calEncode($line)
471 {
472  $out = "";
473  $newpara = "";
474 
475  // If mb_ functions exists, it"s better to use them
476  if (function_exists("mb_strlen")) {
477  $strlength = mb_strlen($line, "UTF-8");
478 
479  for ($j = 0; $j < $strlength; $j++) {
480  // Take char at position $j
481  $char = mb_substr($line, $j, 1, "UTF-8");
482 
483  if ((mb_strlen($newpara, "UTF-8") + mb_strlen($char, "UTF-8")) >= 75) {
484  // CRLF + Space for cal
485  $out .= $newpara."\r\n ";
486 
487  $newpara = "";
488  }
489 
490  $newpara .= $char;
491  }
492 
493  $out .= $newpara;
494  } else {
495  $strlength = dol_strlen($line);
496 
497  for ($j = 0; $j < $strlength; $j++) {
498  // Take char at position $j
499  $char = substr($line, $j, 1);
500 
501  if ((dol_strlen($newpara) + dol_strlen($char)) >= 75) {
502  // CRLF + Space for cal
503  $out .= $newpara."\r\n ";
504 
505  $newpara = "";
506  }
507 
508  $newpara .= $char;
509  }
510 
511  $out .= $newpara;
512  }
513 
514  return trim($out);
515 }
516 
517 
525 function quotedPrintEncode($str, $forcal = 0)
526 {
527  $lines = preg_split("/\r\n/", $str);
528  $out = "";
529 
530  foreach ($lines as $line) {
531  $newpara = "";
532 
533  // Do not use dol_strlen here, we need number of bytes
534  $strlength = strlen($line);
535 
536  for ($j = 0; $j < $strlength; $j++) {
537  $char = substr($line, $j, 1);
538  $ascii = ord($char);
539 
540  if ($ascii < 32 || $ascii === 61 || $ascii > 126) {
541  $char = "=".strtoupper(sprintf("%02X", $ascii));
542  }
543 
544  // Do not use dol_strlen here, we need number of bytes
545  if ((strlen($newpara) + strlen($char)) >= 76) {
546  // New line with carray-return (CR) and line-feed (LF)
547  $out .= $newpara."=\r\n";
548 
549  // extra space for cal
550  if ($forcal) {
551  $out .= " ";
552  }
553 
554  $newpara = "";
555  }
556 
557  $newpara .= $char;
558  }
559 
560  $out .= $newpara;
561  }
562  return trim($out);
563 }
564 
571 function quotedPrintDecode($str)
572 {
573  return trim(quoted_printable_decode(preg_replace("/=\r?\n/", "", $str)));
574 }
build_rssfile
build_rssfile($format, $title, $desc, $events_array, $outputfile, $filter='', $url='', $langcode='')
Build a file from an array of events.
Definition: xcal.lib.php:311
calEncode
calEncode($line)
Cut string after 75 chars.
Definition: xcal.lib.php:470
convertSecondToTime
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:236
dol_print_date
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
Definition: functions.lib.php:2514
dol_string_nohtmltag
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
Definition: functions.lib.php:6694
dol_syslog
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
Definition: functions.lib.php:1603
dol_strlen
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
Definition: functions.lib.php:3747
dol_time_plus_duree
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:121
quotedPrintDecode
quotedPrintDecode($str)
Decode vcal format.
Definition: xcal.lib.php:571
build_calfile
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
dol_now
dol_now($mode='auto')
Return date for now.
Definition: functions.lib.php:2845
format_cal
format_cal($format, $string)
Encode for cal export.
Definition: xcal.lib.php:440
quotedPrintEncode
quotedPrintEncode($str, $forcal=0)
Encode into vcal format.
Definition: xcal.lib.php:525