ical.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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. * @version 2.0
  32. * @license MIT
  33. *
  34. * <code>
  35. * $ical = new ical('./calendar.ics');
  36. * $ical->parse();
  37. * echo '<pre>'.print_r($ical->get_all_data(), true).'</pre>';
  38. * </code>
  39. *
  40. */
  41. //namespace Helpers\Calendar;
  42. class ical
  43. {
  44. /** @var string content of file */
  45. private $plain_content = null;
  46. /** @var array save iCalendar parse data */
  47. private $cal = array ();
  48. /** @var string Help variable save last key (multiline string) */
  49. private $last_key = '';
  50. /** @var array buffer */
  51. private $buffer = array ();
  52. /** @var string nesting or open tag */
  53. private $nesting = 'VCALENDAR';
  54. /**
  55. * Constructr iCal parser object
  56. * @param string $filename
  57. */
  58. public function __construct($filename = null)
  59. {
  60. if (!is_null($filename)) $this->read_file($filename);
  61. }
  62. /**
  63. * Read iCal file
  64. * @param string $filename
  65. */
  66. public function read_file($filename)
  67. {
  68. // FIXME load file content and replace wrong way formated lines
  69. $this->plain_content = preg_replace("/[\r\n]{1,} ([:;])/", "\\1", file_get_contents($filename));
  70. // because Mozilla Calendar save values wrong, like this -->
  71. #SUMMARY
  72. # :Text of sumary
  73. // good way is, for example in SunnyBird. SunnyBird save iCal like this example -->
  74. #SUMMARY:Text of sumary
  75. }
  76. /**
  77. * Prekladac kalendare
  78. *
  79. * @param string|url $filename
  80. * @return unknown
  81. */
  82. public function parse()
  83. {
  84. $this->plain_content = preg_split("/[\n]/", $this->plain_content); // split by lines
  85. // is this text vcalendar standart text ? on line 1 is BEGIN:VCALENDAR
  86. if (strpos($this->plain_content[0], 'BEGIN:VCALENDAR') === false)
  87. {
  88. throw new Exception('Not a VCALENDAR file');
  89. }
  90. foreach ($this->plain_content as $text)
  91. {
  92. $text = trim($text); // trim one line
  93. if (!empty($text))
  94. {
  95. // get Key and Value VCALENDAR:Begin --> Key = VCALENDAR, Value = begin
  96. list($key, $value) = $this->retun_key_value($text);
  97. if ($key === false)
  98. {
  99. $key = $this->last_key; // in case key is empty
  100. }
  101. // process simple dates
  102. if (($key == "DTSTAMP") || ($key == "LAST-MODIFIED") || ($key == "CREATED"))
  103. {
  104. $value = $this->ical_date_to_unix($value);
  105. }
  106. // process RRULE
  107. if ($key == "RRULE")
  108. {
  109. $value = $this->ical_rrule($value);
  110. }
  111. //
  112. // process ical date values like
  113. //
  114. // [DTSTART;VALUE=DATE] => 20121224
  115. // [DTEND;VALUE=DATE] => 20121225
  116. if (strpos($key, 'DTSTART') !== false || strpos($key, 'DTEND') !== false)
  117. {
  118. list($key, $value) = $this->ical_dt_date($key, $value);
  119. }
  120. switch ($text) // search special string
  121. {
  122. case "BEGIN:VCALENDAR":
  123. case "BEGIN:DAYLIGHT":
  124. case "BEGIN:VTIMEZONE":
  125. case "BEGIN:STANDARD":
  126. case "BEGIN:VTODO":
  127. case "BEGIN:VEVENT":
  128. $this->nesting = substr($text, 6);
  129. $this->buffer[$this->nesting] = array (); // null buffer
  130. break;
  131. case "END:VCALENDAR":
  132. $this->cal['VCALENDAR'] = $this->buffer['VCALENDAR']; // save buffer
  133. break;
  134. case "END:DAYLIGHT":
  135. case "END:VTIMEZONE":
  136. case "END:STANDARD":
  137. case "END:VEVENT":
  138. case "END:VTODO":
  139. $this->cal[substr($text, 4)][] = $this->buffer[$this->nesting]; // save buffer
  140. break;
  141. default: // no special string
  142. $this->buffer[$this->nesting][$key] = $value;
  143. $this->last_key = $key; // save last key
  144. break;
  145. }
  146. }
  147. }
  148. return $this->cal;
  149. }
  150. /* --------------------------------------------------------------------------
  151. * Private parser functions
  152. * -------------------------------------------------------------------------- */
  153. /**
  154. * Parse text "XXXX:value text some with : " and return array($key = "XXXX", $value="value");
  155. *
  156. * @param unknown_type $text
  157. * @return unknown
  158. */
  159. private function retun_key_value($text)
  160. {
  161. preg_match("/([^:]+)[:]([\w\W]+)/", $text, $matches);
  162. if (empty($matches))
  163. {
  164. return array (false, $text);
  165. }
  166. else
  167. {
  168. $matches = array_splice($matches, 1, 2);
  169. return $matches;
  170. }
  171. }
  172. /**
  173. * Parse RRULE return array
  174. *
  175. * @param unknown_type $value
  176. * @return unknown
  177. */
  178. private function ical_rrule($value)
  179. {
  180. $rrule = explode(';', $value);
  181. foreach ($rrule as $line)
  182. {
  183. $rcontent = explode('=', $line);
  184. $result[$rcontent[0]] = $rcontent[1];
  185. }
  186. return $result;
  187. }
  188. /**
  189. * Return Unix time from ical date time fomrat (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
  190. *
  191. * @param unknown_type $ical_date
  192. * @return unknown
  193. */
  194. private function ical_date_to_unix($ical_date)
  195. {
  196. $ical_date = preg_replace(array ('/T/', '/Z/'), '', $ical_date); // remove T and Z from strig
  197. 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))
  198. {
  199. if ($date[1] <= 1970)
  200. {
  201. $date[1] = 1971; // FIXME UNIX timestamps can't deal with pre 1970 dates
  202. }
  203. return mktime((int) $date[4], (int) $date[5], (int) $date[6], (int) $date[2], (int) $date[3], (int) $date[1]);
  204. }
  205. else
  206. {
  207. return null;
  208. }
  209. }
  210. /**
  211. * Return unix date from iCal date format
  212. *
  213. * @param string $key
  214. * @param string $value
  215. * @return array
  216. */
  217. private function ical_dt_date($key, $value)
  218. {
  219. $value = $this->ical_date_to_unix($value);
  220. // zjisteni TZID
  221. $temp = explode(";", $key);
  222. if (empty($temp[1])) // neni TZID
  223. {
  224. # 2011-06-02 ms fix
  225. $value = str_replace('T', '', $value);
  226. return array ($key, $value);
  227. }
  228. // pridani $value a $tzid do pole
  229. $key = $temp[0];
  230. $temp = explode("=", $temp[1]);
  231. $return_value[$temp[0]] = $temp[1];
  232. $return_value['unixtime'] = $value;
  233. return array ($key, $return_value);
  234. }
  235. /* --------------------------------------------------------------------------
  236. * List of public getters
  237. * -------------------------------------------------------------------------- */
  238. /**
  239. * Return sorted eventlist as array or false if calenar is empty
  240. *
  241. * @return array|boolean
  242. */
  243. public function get_sort_event_list()
  244. {
  245. $temp = $this->get_event_list();
  246. if (!empty($temp))
  247. {
  248. usort($temp, array (&$this, "ical_dtstart_compare"));
  249. return $temp;
  250. }
  251. else
  252. {
  253. return false;
  254. }
  255. }
  256. /**
  257. * Compare two unix timestamp
  258. *
  259. * @param array $a
  260. * @param array $b
  261. * @return integer
  262. */
  263. private function ical_dtstart_compare($a, $b)
  264. {
  265. return strnatcasecmp($a['DTSTART']['unixtime'], $b['DTSTART']['unixtime']);
  266. }
  267. /**
  268. * Return eventlist array (not sort eventlist array)
  269. *
  270. * @return array
  271. */
  272. public function get_event_list()
  273. {
  274. return $this->cal['VEVENT'];
  275. }
  276. /**
  277. * Return todo arry (not sort todo array)
  278. *
  279. * @return array
  280. */
  281. public function get_todo_list()
  282. {
  283. if (empty($this->cal['VTODO'])) {
  284. return array();
  285. }
  286. return $this->cal['VTODO'];
  287. }
  288. /**
  289. * Return base calendar data
  290. *
  291. * @return array
  292. */
  293. public function get_calender_data()
  294. {
  295. return $this->cal['VCALENDAR'];
  296. }
  297. /**
  298. * Return array with all data
  299. *
  300. * @return array
  301. */
  302. public function get_all_data()
  303. {
  304. return $this->cal;
  305. }
  306. }