xcal.lib.php 16 KB

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