ical.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /*
  3. Copyright (c) 2010, Roman Ožana
  4. Permission is hereby granted, free of charge, to any person
  5. obtaining a copy of this software and associated documentation
  6. files (the "Software"), to deal in the Software without
  7. restriction, including without limitation the rights to use,
  8. copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the
  10. Software is furnished to do so, subject to the following
  11. conditions:
  12. The above copyright notice and this permission notice shall be
  13. included in all copies or substantial portions of the Software.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  16. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  21. OTHER DEALINGS IN THE SOFTWARE.
  22. */
  23. /**
  24. * This class Parse iCal standard. Is prepare to iCal feature version. Now is testing with apple iCal standard 2.0.
  25. *
  26. * @name iCal parser
  27. * @copyright Roman Ožana, 2011
  28. * @author Roman Ožana 2006-2011
  29. * @link www.nabito.net
  30. * @link www.omdesign.cz
  31. * @link https://github.com/OzzyCzech/icalparser/blob/master/ical.php
  32. * @version 2.0
  33. * @license MIT
  34. *
  35. * <code>
  36. * $ical = new ical('./calendar.ics');
  37. * $ical->parse();
  38. * echo '<pre>'.print_r($ical->get_all_data(), true).'</pre>';
  39. * </code>
  40. *
  41. */
  42. //namespace Helpers\Calendar;
  43. class ical
  44. {
  45. /** @var string content of file */
  46. private $plain_content = null;
  47. /** @var array save iCalendar parse data */
  48. private $cal = [];
  49. /** @var string Help variable save last key (multiline string) */
  50. private $last_key = '';
  51. /** @var array buffer */
  52. private $buffer = [];
  53. /** @var string nesting or open tag */
  54. private $nesting = 'VCALENDAR';
  55. /**
  56. * Constructr iCal parser object
  57. * @param string $filename
  58. */
  59. public function __construct($filename = null)
  60. {
  61. if (!is_null($filename)) $this->read_file($filename);
  62. }
  63. /**
  64. * Read iCal file
  65. * @param string $filename
  66. */
  67. public function read_file($filename)
  68. {
  69. // FIXME load file content and replace wrong way formated lines
  70. $this->plain_content = preg_replace("/[\r\n]{1,} ([:;])/", "\\1", file_get_contents($filename));
  71. // because Mozilla Calendar save values wrong, like this -->
  72. #SUMMARY
  73. // :Text of sumary
  74. // good way is, for example in SunnyBird. SunnyBird save iCal like this example -->
  75. #SUMMARY:Text of sumary
  76. }
  77. /**
  78. * Prekladac kalendare
  79. *
  80. * @param string|url $filename
  81. * @return unknown
  82. */
  83. public function parse()
  84. {
  85. $this->plain_content = preg_split("/[\n]/", $this->plain_content); // split by lines
  86. // is this text vcalendar standart text ? on line 1 is BEGIN:VCALENDAR
  87. if (strpos($this->plain_content[0], 'BEGIN:VCALENDAR') === false)
  88. {
  89. throw new Exception('Not a VCALENDAR file');
  90. }
  91. foreach ($this->plain_content as $text)
  92. {
  93. $text = trim($text); // trim one line
  94. if (!empty($text))
  95. {
  96. // get Key and Value VCALENDAR:Begin --> Key = VCALENDAR, Value = begin
  97. list($key, $value) = $this->retun_key_value($text);
  98. if ($key === false)
  99. {
  100. $key = $this->last_key; // in case key is empty
  101. }
  102. // process simple dates
  103. if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED"))
  104. {
  105. $value = $this->ical_date_to_unix($value);
  106. }
  107. // process RRULE
  108. if ($key == "RRULE")
  109. {
  110. $value = $this->ical_rrule($value);
  111. }
  112. //
  113. // process ical date values like
  114. //
  115. // [DTSTART;VALUE=DATE] => 20121224
  116. // [DTEND;VALUE=DATE] => 20121225
  117. if (strpos($key, 'DTSTART') !== false || strpos($key, 'DTEND') !== false)
  118. {
  119. list($key, $value) = $this->ical_dt_date($key, $value);
  120. }
  121. switch ($text) // search special string
  122. {
  123. case "BEGIN:VCALENDAR":
  124. case "BEGIN:DAYLIGHT":
  125. case "BEGIN:VTIMEZONE":
  126. case "BEGIN:STANDARD":
  127. case "BEGIN:VTODO":
  128. case "BEGIN:VEVENT":
  129. $this->nesting = substr($text, 6);
  130. $this->buffer[$this->nesting] = []; // null buffer
  131. break;
  132. case "END:VCALENDAR":
  133. $this->cal['VCALENDAR'] = $this->buffer['VCALENDAR']; // save buffer
  134. break;
  135. case "END:DAYLIGHT":
  136. case "END:VTIMEZONE":
  137. case "END:STANDARD":
  138. case "END:VEVENT":
  139. case "END:VTODO":
  140. $this->cal[substr($text, 4)][] = $this->buffer[$this->nesting]; // save buffer
  141. break;
  142. default: // no special string
  143. $this->buffer[$this->nesting][$key] = $value;
  144. $this->last_key = $key; // save last key
  145. break;
  146. }
  147. }
  148. }
  149. return $this->cal;
  150. }
  151. /* --------------------------------------------------------------------------
  152. * Private parser functions
  153. * -------------------------------------------------------------------------- */
  154. /**
  155. * Parse text "XXXX:value text some with : " and return array($key = "XXXX", $value="value");
  156. *
  157. * @param unknown_type $text
  158. * @return unknown
  159. */
  160. private function retun_key_value($text)
  161. {
  162. preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
  163. if (empty($matches))
  164. {
  165. return [false, $text];
  166. }
  167. else
  168. {
  169. $matches = array_splice($matches, 1, 2);
  170. return $matches;
  171. }
  172. }
  173. /**
  174. * Parse RRULE return array
  175. *
  176. * @param unknown_type $value
  177. * @return unknown
  178. */
  179. private function ical_rrule($value)
  180. {
  181. $rrule = explode(';', $value);
  182. foreach ($rrule as $line)
  183. {
  184. $rcontent = explode('=', $line);
  185. $result[$rcontent[0]] = $rcontent[1];
  186. }
  187. return $result;
  188. }
  189. /**
  190. * Return Unix time from ical date time fomrat (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
  191. *
  192. * @param unknown_type $ical_date
  193. * @return unknown
  194. */
  195. private function ical_date_to_unix($ical_date)
  196. {
  197. $ical_date = preg_replace(['/T/', '/Z/'], '', $ical_date); // remove T and Z from strig
  198. 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))
  199. {
  200. if ($date[1] <= 1970)
  201. {
  202. $date[1] = 1971; // FIXME UNIX timestamps can't deal with pre 1970 dates
  203. }
  204. return mktime((int) $date[4], (int) $date[5], (int) $date[6], (int) $date[2], (int) $date[3], (int) $date[1]);
  205. }
  206. else
  207. {
  208. return null;
  209. }
  210. }
  211. /**
  212. * Return unix date from iCal date format
  213. *
  214. * @param string $key
  215. * @param string $value
  216. * @return array
  217. */
  218. private function ical_dt_date($key, $value)
  219. {
  220. $value = $this->ical_date_to_unix($value);
  221. // zjisteni TZID
  222. $temp = explode(";", $key);
  223. if (empty($temp[1])) // neni TZID
  224. {
  225. // 2011-06-02 ms fix
  226. $value = str_replace('T', '', $value);
  227. return [$key, $value];
  228. }
  229. // pridani $value a $tzid do pole
  230. $key = $temp[0];
  231. $temp = explode("=", $temp[1]);
  232. $return_value[$temp[0]] = $temp[1];
  233. $return_value['unixtime'] = $value;
  234. return [$key, $return_value];
  235. }
  236. /* --------------------------------------------------------------------------
  237. * List of public getters
  238. * -------------------------------------------------------------------------- */
  239. /**
  240. * Return sorted eventlist as array or false if calenar is empty
  241. *
  242. * @return array|boolean
  243. */
  244. public function get_sort_event_list()
  245. {
  246. $temp = $this->get_event_list();
  247. if (!empty($temp))
  248. {
  249. usort($temp, [&$this, "ical_dtstart_compare"]);
  250. return $temp;
  251. }
  252. else
  253. {
  254. return false;
  255. }
  256. }
  257. /**
  258. * Compare two unix timestamp
  259. *
  260. * @param array $a
  261. * @param array $b
  262. * @return integer
  263. */
  264. private function ical_dtstart_compare($a, $b)
  265. {
  266. return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
  267. }
  268. /**
  269. * Return eventlist array (not sort eventlist array)
  270. *
  271. * @return array
  272. */
  273. public function get_event_list()
  274. {
  275. return $this->cal['VEVENT'];
  276. }
  277. /**
  278. * Return todo arry (not sort todo array)
  279. *
  280. * @return array
  281. */
  282. public function get_todo_list()
  283. {
  284. if (empty($this->cal['VTODO'])) {
  285. return [];
  286. }
  287. return $this->cal['VTODO'];
  288. }
  289. /**
  290. * Return base calendar data
  291. *
  292. * @return array
  293. */
  294. public function get_calender_data()
  295. {
  296. return $this->cal['VCALENDAR'];
  297. }
  298. /**
  299. * Return array with all data
  300. *
  301. * @return array
  302. */
  303. public function get_all_data()
  304. {
  305. return $this->cal;
  306. }
  307. }