ical.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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. // Text in file
  34. public $file_text;
  35. public $cal; // Array to save iCalendar parse data
  36. public $event_count; // Number of Events
  37. public $todo_count; // Number of Todos
  38. public $freebusy_count; // Number of Freebusy
  39. public $last_key; //Help variable save last key (multiline string)
  40. public $error;
  41. /**
  42. * Constructor
  43. */
  44. public function __construct()
  45. {
  46. }
  47. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  48. /**
  49. * Read text file, icalender text file
  50. *
  51. * @param string $file File
  52. * @return string
  53. */
  54. public function read_file($file)
  55. {
  56. // phpcs:enable
  57. $this->file = $file;
  58. $file_text = '';
  59. $tmpresult = getURLContent($file, 'GET');
  60. if ($tmpresult['http_code'] != 200) {
  61. $file_text = '';
  62. $this->error = 'Error: '.$tmpresult['http_code'].' '.$tmpresult['content'];
  63. } else {
  64. $file_text = preg_replace("/[\r\n]{1,} /", "", $tmpresult['content']);
  65. }
  66. //var_dump($tmpresult);
  67. return $file_text; // return all text
  68. }
  69. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  70. /**
  71. * Returns the number of calendar events
  72. *
  73. * @return int
  74. */
  75. public function get_event_count()
  76. {
  77. // phpcs:enable
  78. return $this->event_count;
  79. }
  80. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  81. /**
  82. * Returns the number of to do
  83. *
  84. * @return int
  85. */
  86. public function get_todo_count()
  87. {
  88. // phpcs:enable
  89. return $this->todo_count;
  90. }
  91. /**
  92. * Translate Calendar
  93. *
  94. * @param string $uri Url
  95. * @return array|string
  96. */
  97. public function parse($uri)
  98. {
  99. $this->cal = array(); // new empty array
  100. $this->event_count = -1;
  101. // read FILE text
  102. $this->file_text = $this->read_file($uri);
  103. $this->file_text = preg_split("[\n]", $this->file_text);
  104. // is this text vcalendar standard text ? on line 1 is BEGIN:VCALENDAR
  105. if (!stristr($this->file_text[0], 'BEGIN:VCALENDAR')) {
  106. return 'error not VCALENDAR';
  107. }
  108. $insidealarm = 0;
  109. $tmpkey = ''; $tmpvalue = ''; $type = '';
  110. foreach ($this->file_text as $text) {
  111. $text = trim($text); // trim one line
  112. if (!empty($text)) {
  113. // get Key and Value VCALENDAR:Begin -> Key = VCALENDAR, Value = begin
  114. list($key, $value) = $this->retun_key_value($text);
  115. //var_dump($text.' -> '.$key.' - '.$value);
  116. switch ($text) { // search special string
  117. case "BEGIN:VTODO":
  118. $this->todo_count = $this->todo_count + 1; // new to do begin
  119. $type = "VTODO";
  120. break;
  121. case "BEGIN:VEVENT":
  122. $this->event_count = $this->event_count + 1; // new event begin
  123. $type = "VEVENT";
  124. break;
  125. case "BEGIN:VFREEBUSY":
  126. $this->freebusy_count = $this->freebusy_count + 1; // new event begin
  127. $type = "VFREEBUSY";
  128. break;
  129. case "BEGIN:VCALENDAR": // all other special string
  130. case "BEGIN:DAYLIGHT":
  131. case "BEGIN:VTIMEZONE":
  132. case "BEGIN:STANDARD":
  133. $type = $value; // save array under value key
  134. break;
  135. case "END:VTODO": // end special text - goto VCALENDAR key
  136. case "END:VEVENT":
  137. case "END:VFREEBUSY":
  138. case "END:VCALENDAR":
  139. case "END:DAYLIGHT":
  140. case "END:VTIMEZONE":
  141. case "END:STANDARD":
  142. $type = "VCALENDAR";
  143. break;
  144. // Manage VALARM that are inside a VEVENT to avoid fields of VALARM to overwrites fields of VEVENT
  145. case "BEGIN:VALARM":
  146. $insidealarm = 1;
  147. break;
  148. case "END:VALARM":
  149. $insidealarm = 0;
  150. break;
  151. default: // no special string (SUMMARY, DESCRIPTION, ...)
  152. if ($tmpvalue) {
  153. $tmpvalue .= $text;
  154. if (!preg_match('/=$/', $text)) { // No more lines
  155. $key = $tmpkey;
  156. $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue));
  157. $tmpkey = '';
  158. $tmpvalue = '';
  159. }
  160. } elseif (preg_match('/^ENCODING=QUOTED-PRINTABLE:/i', $value)) {
  161. if (preg_match('/=$/', $value)) {
  162. $tmpkey = $key;
  163. $tmpvalue = $tmpvalue.preg_replace('/=$/', "", $value); // We must wait to have next line to have complete message
  164. } else {
  165. $value = quotedPrintDecode(preg_replace('/^ENCODING=QUOTED-PRINTABLE:/i', '', $tmpvalue.$value));
  166. }
  167. } //$value=quotedPrintDecode($tmpvalue.$value);
  168. if (!$insidealarm && !$tmpkey) {
  169. $this->add_to_array($type, $key, $value); // add to array
  170. }
  171. break;
  172. }
  173. }
  174. }
  175. //var_dump($this->cal);
  176. return $this->cal;
  177. }
  178. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  179. /**
  180. * Add to $this->ical array one value and key.
  181. *
  182. * @param string $type Type ('VTODO', 'VEVENT', 'VFREEBUSY', 'VCALENDAR'...)
  183. * @param string $key Key ('DTSTART', ...). Note: Field is never 'DTSTART;TZID=...' because ';...' was before removed and added as another property
  184. * @param string $value Value
  185. * @return void
  186. */
  187. public function add_to_array($type, $key, $value)
  188. {
  189. // phpcs:enable
  190. //print 'type='.$type.' key='.$key.' value='.$value.'<br>'."\n";
  191. if (empty($key)) {
  192. $key = $this->last_key;
  193. switch ($type) {
  194. case 'VEVENT':
  195. $value = $this->cal[$type][$this->event_count][$key].$value;
  196. break;
  197. case 'VFREEBUSY':
  198. $value = $this->cal[$type][$this->freebusy_count][$key].$value;
  199. break;
  200. case 'VTODO':
  201. $value = $this->cal[$type][$this->todo_count][$key].$value;
  202. break;
  203. }
  204. }
  205. if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED")) {
  206. $value = $this->ical_date_to_unix($value);
  207. }
  208. //if ($key == "RRULE" ) $value = $this->ical_rrule($value);
  209. if (stristr($key, "DTSTART") || stristr($key, "DTEND") || stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
  210. if (stristr($key, "DTSTART;VALUE=DATE") || stristr($key, "DTEND;VALUE=DATE")) {
  211. list($key, $value) = array($key, $value);
  212. } else {
  213. list($key, $value) = $this->ical_dt_date($key, $value);
  214. }
  215. }
  216. switch ($type) {
  217. case "VTODO":
  218. $this->cal[$type][$this->todo_count][$key] = $value;
  219. break;
  220. case "VEVENT":
  221. $this->cal[$type][$this->event_count][$key] = $value;
  222. break;
  223. case "VFREEBUSY":
  224. $this->cal[$type][$this->freebusy_count][$key] = $value;
  225. break;
  226. default:
  227. $this->cal[$type][$key] = $value;
  228. break;
  229. }
  230. $this->last_key = $key;
  231. }
  232. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  233. /**
  234. * Parse text "XXXX:value text some with : " and return array($key = "XXXX", $value="value");
  235. *
  236. * @param string $text Text
  237. * @return array
  238. */
  239. public function retun_key_value($text)
  240. {
  241. // phpcs:enable
  242. /*
  243. preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
  244. if (empty($matches))
  245. {
  246. return array(false,$text);
  247. }
  248. else
  249. {
  250. $matches = array_splice($matches, 1, 2);
  251. return $matches;
  252. }*/
  253. return explode(':', $text, 2);
  254. }
  255. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  256. /**
  257. * Parse RRULE return array
  258. *
  259. * @param string $value string
  260. * @return array
  261. */
  262. public function ical_rrule($value)
  263. {
  264. // phpcs:enable
  265. $result = array();
  266. $rrule = explode(';', $value);
  267. foreach ($rrule as $line) {
  268. $rcontent = explode('=', $line);
  269. $result[$rcontent[0]] = $rcontent[1];
  270. }
  271. return $result;
  272. }
  273. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  274. /**
  275. * Return Unix time from ical date time fomrat (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
  276. *
  277. * @param string $ical_date String date
  278. * @return int
  279. */
  280. public function ical_date_to_unix($ical_date)
  281. {
  282. // phpcs:enable
  283. $ical_date = str_replace('T', '', $ical_date);
  284. $ical_date = str_replace('Z', '', $ical_date);
  285. $ntime = 0;
  286. // TIME LIMITED EVENT
  287. 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)) {
  288. $ntime = dol_mktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1], true);
  289. }
  290. //if (empty($date[4])) print 'Error bad date: '.$ical_date.' - date1='.$date[1];
  291. //print dol_print_date($ntime,'dayhour');exit;
  292. return $ntime; // ntime is a GTM time
  293. }
  294. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  295. /**
  296. * Return unix date from iCal date format
  297. *
  298. * @param string $key Key
  299. * @param string $value Value
  300. * @return array
  301. */
  302. public function ical_dt_date($key, $value)
  303. {
  304. // phpcs:enable
  305. $return_value = array();
  306. $value = $this->ical_date_to_unix($value);
  307. // Analyse TZID
  308. $temp = explode(";", $key);
  309. if (empty($temp[1])) { // not TZID
  310. $value = str_replace('T', '', $value);
  311. return array($key, $value);
  312. }
  313. $key = $temp[0];
  314. $temp = explode("=", $temp[1]);
  315. $return_value[$temp[0]] = $temp[1];
  316. $return_value['unixtime'] = $value;
  317. return array($key, $return_value);
  318. }
  319. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  320. /**
  321. * Return sorted eventlist as array or false if calendar is empty
  322. *
  323. * @return array|false
  324. */
  325. public function get_sort_event_list()
  326. {
  327. // phpcs:enable
  328. $temp = $this->get_event_list();
  329. if (!empty($temp)) {
  330. usort($temp, array(&$this, "ical_dtstart_compare"));
  331. return $temp;
  332. } else {
  333. return false;
  334. }
  335. }
  336. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  337. /**
  338. * Compare two unix timestamp
  339. *
  340. * @param array $a Operand a
  341. * @param array $b Operand b
  342. * @return integer
  343. */
  344. public function ical_dtstart_compare($a, $b)
  345. {
  346. // phpcs:enable
  347. return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
  348. }
  349. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  350. /**
  351. * Return eventlist array (not sorted eventlist array)
  352. *
  353. * @return array
  354. */
  355. public function get_event_list()
  356. {
  357. // phpcs:enable
  358. return (empty($this->cal['VEVENT']) ? '' : $this->cal['VEVENT']);
  359. }
  360. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  361. /**
  362. * Return freebusy array (not sort eventlist array)
  363. *
  364. * @return array
  365. */
  366. public function get_freebusy_list()
  367. {
  368. // phpcs:enable
  369. return (empty($this->cal['VFREEBUSY']) ? '' : $this->cal['VFREEBUSY']);
  370. }
  371. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  372. /**
  373. * Return to do array (not sorted todo array)
  374. *
  375. * @return array
  376. */
  377. public function get_todo_list()
  378. {
  379. // phpcs:enable
  380. return $this->cal['VTODO'];
  381. }
  382. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  383. /**
  384. * Return base calendar data
  385. *
  386. * @return array
  387. */
  388. public function get_calender_data()
  389. {
  390. // phpcs:enable
  391. return $this->cal['VCALENDAR'];
  392. }
  393. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  394. /**
  395. * Return array with all data
  396. *
  397. * @return array
  398. */
  399. public function get_all_data()
  400. {
  401. // phpcs:enable
  402. return $this->cal;
  403. }
  404. }