ical.class.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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 Frédéric France <frederic.france@netlogic.fr>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. /**
  22. * \file htdocs/comm/action/class/ical.class.php
  23. * \ingroup agenda
  24. * \brief File of class to parse ical calendars
  25. */
  26. require_once DOL_DOCUMENT_ROOT.'/core/lib/xcal.lib.php';
  27. require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  28. /**
  29. * Class to read/parse ICal calendars
  30. */
  31. class ICal
  32. {
  33. /**
  34. * @var string Name of remote HTTP file to read
  35. */
  36. public $file;
  37. // Text in file
  38. public $file_text;
  39. public $cal; // Array to save iCalendar parse data
  40. public $event_count; // Number of Events
  41. public $todo_count; // Number of Todos
  42. public $freebusy_count; // Number of Freebusy
  43. public $last_key; //Help variable save last key (multiline string)
  44. public $error;
  45. /**
  46. * Constructor
  47. */
  48. public function __construct()
  49. {
  50. }
  51. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  52. /**
  53. * Read text file, icalender text file
  54. *
  55. * @param string $file File
  56. * @return string|null Content of remote file read or null if error
  57. */
  58. public function read_file($file)
  59. {
  60. // phpcs:enable
  61. $this->file = $file;
  62. $file_text = '';
  63. $tmpresult = getURLContent($file, 'GET');
  64. if ($tmpresult['http_code'] != 200) {
  65. $file_text = null;
  66. $this->error = 'Error: '.$tmpresult['http_code'].' '.$tmpresult['content'];
  67. } else {
  68. $file_text = preg_replace("/[\r\n]{1,} /", "", $tmpresult['content']);
  69. }
  70. //var_dump($tmpresult);
  71. return $file_text; // return all text
  72. }
  73. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  74. /**
  75. * Returns the number of calendar events
  76. *
  77. * @return int
  78. */
  79. public function get_event_count()
  80. {
  81. // phpcs:enable
  82. return $this->event_count;
  83. }
  84. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  85. /**
  86. * Returns the number of to do
  87. *
  88. * @return int
  89. */
  90. public function get_todo_count()
  91. {
  92. // phpcs:enable
  93. return $this->todo_count;
  94. }
  95. /**
  96. * Translate Calendar
  97. *
  98. * @param string $uri Url
  99. * @param string $usecachefile Full path of a cache file to use a cache file
  100. * @param string $delaycache Delay in seconds for cache (by default 3600 secondes)
  101. * @return array|string
  102. */
  103. public function parse($uri, $usecachefile = '', $delaycache = 3600)
  104. {
  105. $this->cal = array(); // new empty array
  106. $this->event_count = -1;
  107. $this->file_text = null;
  108. // Save file into a cache
  109. if ($usecachefile) {
  110. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  111. $datefile = dol_filemtime($usecachefile);
  112. $now = dol_now('gmt');
  113. //print $datefile.' '.$now.' ...';
  114. if ($datefile && $datefile > ($now - $delaycache)) {
  115. // We reuse the cache file
  116. $this->file_text = file_get_contents($usecachefile);
  117. }
  118. }
  119. // read FILE text
  120. if (is_null($this->file_text)) {
  121. $this->file_text = $this->read_file($uri);
  122. if ($usecachefile && !is_null($this->file_text)) {
  123. // Save the file content into cache file
  124. file_put_contents($usecachefile, $this->file_text, LOCK_EX);
  125. dolChmod($usecachefile);
  126. }
  127. }
  128. $this->file_text = preg_split("[\n]", $this->file_text);
  129. // is this text vcalendar standard text ? on line 1 is BEGIN:VCALENDAR
  130. if (!stristr($this->file_text[0], 'BEGIN:VCALENDAR')) {
  131. return 'error not VCALENDAR';
  132. }
  133. $insidealarm = 0;
  134. $tmpkey = '';
  135. $tmpvalue = '';
  136. $type = '';
  137. foreach ($this->file_text as $text) {
  138. $text = trim($text); // trim one line
  139. if (!empty($text)) {
  140. // get Key and Value VCALENDAR:Begin -> Key = VCALENDAR, Value = begin
  141. list($key, $value) = $this->retun_key_value($text);
  142. //var_dump($text.' -> '.$key.' - '.$value);
  143. switch ($text) { // search special string
  144. case "BEGIN:VTODO":
  145. $this->todo_count = $this->todo_count + 1; // new to do begin
  146. $type = "VTODO";
  147. break;
  148. case "BEGIN:VEVENT":
  149. $this->event_count = $this->event_count + 1; // new event begin
  150. $type = "VEVENT";
  151. break;
  152. case "BEGIN:VFREEBUSY":
  153. $this->freebusy_count = $this->freebusy_count + 1; // new event begin
  154. $type = "VFREEBUSY";
  155. break;
  156. case "BEGIN:VCALENDAR": // all other special string
  157. case "BEGIN:DAYLIGHT":
  158. case "BEGIN:VTIMEZONE":
  159. case "BEGIN:STANDARD":
  160. $type = $value; // save array under value key
  161. break;
  162. case "END:VTODO": // end special text - goto VCALENDAR key
  163. case "END:VEVENT":
  164. case "END:VFREEBUSY":
  165. case "END:VCALENDAR":
  166. case "END:DAYLIGHT":
  167. case "END:VTIMEZONE":
  168. case "END:STANDARD":
  169. $type = "VCALENDAR";
  170. break;
  171. // Manage VALARM that are inside a VEVENT to avoid fields of VALARM to overwrites fields of VEVENT
  172. case "BEGIN:VALARM":
  173. $insidealarm = 1;
  174. break;
  175. case "END:VALARM":
  176. $insidealarm = 0;
  177. break;
  178. default: // no special string (SUMMARY, DESCRIPTION, ...)
  179. if ($tmpvalue) {
  180. $tmpvalue .= $text;
  181. if (!preg_match('/=$/', $text)) { // No more lines
  182. $key = $tmpkey;
  183. $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue));
  184. $tmpkey = '';
  185. $tmpvalue = '';
  186. }
  187. } elseif (preg_match('/^ENCODING=QUOTED-PRINTABLE:/i', $value)) {
  188. if (preg_match('/=$/', $value)) {
  189. $tmpkey = $key;
  190. $tmpvalue = $tmpvalue.preg_replace('/=$/', "", $value); // We must wait to have next line to have complete message
  191. } else {
  192. $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue.$value));
  193. }
  194. } //$value=quotedPrintDecode($tmpvalue.$value);
  195. if (!$insidealarm && !$tmpkey) {
  196. $this->add_to_array($type, $key, $value); // add to array
  197. }
  198. break;
  199. }
  200. }
  201. }
  202. //var_dump($this->cal);
  203. return $this->cal;
  204. }
  205. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  206. /**
  207. * Add to $this->ical array one value and key.
  208. *
  209. * @param string $type Type ('VTODO', 'VEVENT', 'VFREEBUSY', 'VCALENDAR'...)
  210. * @param string $key Key ('DTSTART', ...). Note: Field is never 'DTSTART;TZID=...' because ';...' was before removed and added as another property
  211. * @param string $value Value
  212. * @return void
  213. */
  214. public function add_to_array($type, $key, $value)
  215. {
  216. // phpcs:enable
  217. //print 'type='.$type.' key='.$key.' value='.$value.'<br>'."\n";
  218. if (empty($key)) {
  219. $key = $this->last_key;
  220. switch ($type) {
  221. case 'VEVENT':
  222. $value = $this->cal[$type][$this->event_count][$key].$value;
  223. break;
  224. case 'VFREEBUSY':
  225. $value = $this->cal[$type][$this->freebusy_count][$key].$value;
  226. break;
  227. case 'VTODO':
  228. $value = $this->cal[$type][$this->todo_count][$key].$value;
  229. break;
  230. }
  231. }
  232. if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED")) {
  233. $value = $this->ical_date_to_unix($value);
  234. }
  235. //if ($key == "RRULE" ) $value = $this->ical_rrule($value);
  236. if (stristr($key, "DTSTART") || stristr($key, "DTEND") || stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
  237. if (stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
  238. list($key, $value) = array($key, $value);
  239. } else {
  240. list($key, $value) = $this->ical_dt_date($key, $value);
  241. }
  242. }
  243. switch ($type) {
  244. case "VTODO":
  245. $this->cal[$type][$this->todo_count][$key] = $value;
  246. break;
  247. case "VEVENT":
  248. $this->cal[$type][$this->event_count][$key] = $value;
  249. break;
  250. case "VFREEBUSY":
  251. $this->cal[$type][$this->freebusy_count][$key] = $value;
  252. break;
  253. default:
  254. $this->cal[$type][$key] = $value;
  255. break;
  256. }
  257. $this->last_key = $key;
  258. }
  259. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  260. /**
  261. * Parse text "XXXX:value text some with : " and return array($key = "XXXX", $value="value");
  262. *
  263. * @param string $text Text
  264. * @return array
  265. */
  266. public function retun_key_value($text)
  267. {
  268. // phpcs:enable
  269. /*
  270. preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
  271. if (empty($matches))
  272. {
  273. return array(false,$text);
  274. }
  275. else
  276. {
  277. $matches = array_splice($matches, 1, 2);
  278. return $matches;
  279. }*/
  280. return explode(':', $text, 2);
  281. }
  282. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  283. /**
  284. * Parse RRULE return array
  285. *
  286. * @param string $value string
  287. * @return array
  288. */
  289. public function ical_rrule($value)
  290. {
  291. // phpcs:enable
  292. $result = array();
  293. $rrule = explode(';', $value);
  294. foreach ($rrule as $line) {
  295. $rcontent = explode('=', $line);
  296. $result[$rcontent[0]] = $rcontent[1];
  297. }
  298. return $result;
  299. }
  300. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  301. /**
  302. * Return Unix time from ical date time fomrat (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
  303. *
  304. * @param string $ical_date String date
  305. * @return int
  306. */
  307. public function ical_date_to_unix($ical_date)
  308. {
  309. // phpcs:enable
  310. $ical_date = str_replace('T', '', $ical_date);
  311. $ical_date = str_replace('Z', '', $ical_date);
  312. $ntime = 0;
  313. // TIME LIMITED EVENT
  314. 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)) {
  315. $ntime = dol_mktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1], true);
  316. }
  317. //if (empty($date[4])) print 'Error bad date: '.$ical_date.' - date1='.$date[1];
  318. //print dol_print_date($ntime,'dayhour');exit;
  319. return $ntime; // ntime is a GTM time
  320. }
  321. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  322. /**
  323. * Return unix date from iCal date format
  324. *
  325. * @param string $key Key
  326. * @param string $value Value
  327. * @return array
  328. */
  329. public function ical_dt_date($key, $value)
  330. {
  331. // phpcs:enable
  332. $return_value = array();
  333. $value = $this->ical_date_to_unix($value);
  334. // Analyse TZID
  335. $temp = explode(";", $key);
  336. if (empty($temp[1])) { // not TZID
  337. $value = str_replace('T', '', $value);
  338. return array($key, $value);
  339. }
  340. $key = $temp[0];
  341. $temp = explode("=", $temp[1]);
  342. $return_value[$temp[0]] = $temp[1];
  343. $return_value['unixtime'] = $value;
  344. return array($key, $return_value);
  345. }
  346. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  347. /**
  348. * Return sorted eventlist as array or false if calendar is empty
  349. *
  350. * @return array|false
  351. */
  352. public function get_sort_event_list()
  353. {
  354. // phpcs:enable
  355. $temp = $this->get_event_list();
  356. if (!empty($temp)) {
  357. usort($temp, array(&$this, "ical_dtstart_compare"));
  358. return $temp;
  359. } else {
  360. return false;
  361. }
  362. }
  363. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  364. /**
  365. * Compare two unix timestamp
  366. *
  367. * @param array $a Operand a
  368. * @param array $b Operand b
  369. * @return integer
  370. */
  371. public function ical_dtstart_compare($a, $b)
  372. {
  373. // phpcs:enable
  374. return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
  375. }
  376. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  377. /**
  378. * Return eventlist array (not sorted eventlist array)
  379. *
  380. * @return array
  381. */
  382. public function get_event_list()
  383. {
  384. // phpcs:enable
  385. return (empty($this->cal['VEVENT']) ? array() : $this->cal['VEVENT']);
  386. }
  387. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  388. /**
  389. * Return freebusy array (not sort eventlist array)
  390. *
  391. * @return array
  392. */
  393. public function get_freebusy_list()
  394. {
  395. // phpcs:enable
  396. return (empty($this->cal['VFREEBUSY']) ? array() : $this->cal['VFREEBUSY']);
  397. }
  398. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  399. /**
  400. * Return to do array (not sorted todo array)
  401. *
  402. * @return array
  403. */
  404. public function get_todo_list()
  405. {
  406. // phpcs:enable
  407. return $this->cal['VTODO'];
  408. }
  409. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  410. /**
  411. * Return base calendar data
  412. *
  413. * @return array
  414. */
  415. public function get_calender_data()
  416. {
  417. // phpcs:enable
  418. return $this->cal['VCALENDAR'];
  419. }
  420. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  421. /**
  422. * Return array with all data
  423. *
  424. * @return array
  425. */
  426. public function get_all_data()
  427. {
  428. // phpcs:enable
  429. return $this->cal;
  430. }
  431. }