PageRenderTime 26ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/jlextdfbnetplayerimport/admin/helpers/iCal2csv.php

https://gitlab.com/julienv/joomleague
PHP | 598 lines | 529 code | 0 blank | 69 comment | 181 complexity | 6890e5d5c3aa2094ae428ebfb8743a01 MD5 | raw file
  1. <?php
  2. /**
  3. * iCal2csv
  4. * ver 2.0
  5. *
  6. * copyright (c) 2009 Kjell-Inge Gustafsson kigkonsult
  7. * www.kigkonsult.se/index.php
  8. * ical@kigkonsult.se
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation; either
  13. * version 2.1 of the License, or (at your option) any later version.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public
  21. * License along with this library; if not, write to the Free Software
  22. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  23. *
  24. **/
  25. /**
  26. * function iCal2csv
  27. *
  28. * Convert iCal file to csv format and send file to browser (default) or save csv file to disk
  29. * Definition iCal : rcf2445, http://localhost/work/kigkonsult.se/downloads/index.php#rfc2445
  30. * Definition csv : http://en.wikipedia.org/wiki/Comma-separated_values
  31. * Using iCalcreator: http://localhost/work/kigkonsult.se/downloads/index.php#iCalcreator
  32. * ical directory/file read/write error OR iCalcreator parse error will be directed to error_log/log
  33. *
  34. * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
  35. * @since 2.0 - 2009-03-27
  36. * @param string $filename file to convert (incl. opt. directory)
  37. * @param array $conf opt, default FALSE(=array('del'=>'"','sep'=>',', 'nl'=>'\n'), delimiter, separator and newline characters
  38. * escape sequences will be expanded, '\n' will be used as "\n" etc.
  39. * also map iCal property names to user friendly names, ex. 'DTSTART' => 'startdate'
  40. * also order output columns, ex. 2 => 'DTSTART' (2=first order column, 3 next etc)
  41. * also properties to skip, ex. 'skip' => array( 'CREATED', 'PRIORITY' );
  42. * @param bool $save opt, default FALSE, TRUE=save to disk
  43. * @param string $diskfilename opt, filename for file to save or else taken from $filename + 'csv' extension
  44. * @param object $log opt, default FALSE (error_log), writes log to file using PEAR LOG or eClog class
  45. * @return bool returns FALSE when error
  46. */
  47. function iCal2csv( $filename, $conf=FALSE, $save=FALSE, $diskfilename=FALSE, $log=FALSE ) {
  48. if( $log ) $timeexec = array( 'start' => microtime( TRUE ));
  49. $iCal2csv_VERSION = 'iCal2csv 2.0';
  50. if( !function_exists( 'fileCheckRead' ))
  51. require_once 'fileCheck.php';
  52. if( !class_exists( 'vcalendar', FALSE ))
  53. require_once 'iCalcreator.class.php';
  54. if( $log ) $log->log( "$iCal2csv_VERSION input=$filename, conf=".var_export($conf,TRUE).", save=$save, diskfilename=$diskfilename", 7 );
  55. $remoteInput = (( 'http://' == strtolower( substr( $filename, 0, 7 ))) || ( 'webcal://' == strtolower( substr( $filename, 0, 9 )))) ? TRUE : FALSE;
  56. // field DELimiter && field SEParator && NewLine character(-s) etc.
  57. if( !$conf ) $conf = array();
  58. if( !isset( $conf['del'] ))
  59. $conf['del'] = '"';
  60. if( !isset( $conf['sep'] ))
  61. $conf['sep'] = ',';
  62. if( !isset( $conf['nl'] ))
  63. $conf['nl'] = "\n";
  64. foreach( $conf as $key => $value ) {
  65. if( 'skip' == $key ) {
  66. foreach( $value as $six => $skipp )
  67. $conf['skip'][$six] = strtoupper( $skipp );
  68. }
  69. elseif(( '2' <= $key) && ( '99' > $key )) {
  70. $conf[$key] = strtoupper( $value );
  71. if( $log ) $log->log( "$iCal2csv_VERSION column $key contains ".strtoupper( $value ), 7 );
  72. }
  73. elseif( in_array( $key, array( 'del', 'sep', 'nl' )))
  74. $conf[$key] = "$value";
  75. else {
  76. $conf[strtoupper( $key )] = $value;
  77. if( $log ) $log->log( "$iCal2csv_VERSION ".strtoupper( $key )." mapped to $value", 7 );
  78. }
  79. }
  80. /* create path and filename */
  81. if( $remoteInput ) {
  82. $inputFileParts = parse_url( $filename );
  83. $inputFileParts = array_merge( $inputFileParts, pathinfo( $inputFileParts['path'] ));
  84. if( !$diskfilename )
  85. $diskfilename = $inputFileParts['filename'].'.csv';
  86. }
  87. else {
  88. if( FALSE === ( $filename = fileCheckRead( $filename, $log ))) {
  89. if( $log ) {
  90. $log->log( "$iCal2csv_VERSION (".number_format(( microtime( TRUE ) - $timeexec['start'] ), 5 ).')' );
  91. $log->flush();
  92. }
  93. return FALSE;
  94. }
  95. $inputFileParts = pathinfo( $filename );
  96. if( !$diskfilename )
  97. $diskfilename = $inputFileParts['dirname'].DIRECTORY_SEPARATOR.$inputFileParts['filename'].'.csv';
  98. }
  99. $outputFileParts = pathinfo( $diskfilename );
  100. if( $save ) {
  101. if( FALSE === ( $diskfilename = fileCheckWrite( $outputFileParts['dirname'].DIRECTORY_SEPARATOR.$outputFileParts['basename'], $log ))) {
  102. if( $log ) {
  103. $log->log( "$iCal2csv_VERSION (".number_format(( microtime( TRUE ) - $timeexec['start'] ), 5 ).')' );
  104. $log->flush();
  105. }
  106. return FALSE;
  107. }
  108. }
  109. if( $log ) {
  110. $msg = $iCal2csv_VERSION.' INPUT FILE:"'.$inputFileParts['dirname'].DIRECTORY_SEPARATOR.$inputFileParts['basename'].'"';
  111. if( $save )
  112. $msg .= ' OUTPUT FILE: "'.$outputFileParts['dirname'].DIRECTORY_SEPARATOR.$outputFileParts['basename'].'"';
  113. $log->log( $msg, 7 );
  114. }
  115. /* iCalcreator check, read and parse input iCal file */
  116. $calendar = new vcalendar();
  117. $calnl = $calendar->getConfig( 'nl' );
  118. if( $remoteInput ) {
  119. if( FALSE === $calendar->setConfig( 'url', $filename )) {
  120. $msg = $iCal2csv_VERSION.' ERROR 3 INPUT FILE:"'.$filename.'" iCalcreator: invalid url';
  121. if( $log ) { $log->log( $msg, 3 ); $log->flush(); } else error_log( $msg );
  122. return FALSE;
  123. }
  124. }
  125. else {
  126. if( FALSE === $calendar->setConfig( 'directory', $inputFileParts['dirname'] )) {
  127. $msg = $iCal2csv_VERSION.' ERROR 4 INPUT FILE:"'.$filename.'" iCalcreator: invalid directory: "'.$inputFileParts['dirname'].'"';
  128. if( $log ) { $log->log( $msg, 3 ); $log->flush(); } else error_log( $msg );
  129. return FALSE;
  130. }
  131. if( FALSE === $calendar->setConfig( 'filename', $inputFileParts['basename'] )) {
  132. $msg = $iCal2csv_VERSION.' ERROR 5 INPUT FILE:"'.$filename.'" iCalcreator: invalid filename: "'.$inputFileParts['basename'].'"';
  133. if( $log ) { $log->log( $msg, 3 ); $log->flush(); } else error_log( $msg );
  134. return FALSE;
  135. }
  136. }
  137. if( FALSE === $calendar->parse()) {
  138. $msg = $iCal2csv_VERSION.' ERROR 6 INPUT FILE:"'.$filename.'" iCalcreator parse error';
  139. if( $log ) { $log->log( $msg, 3 ); $log->flush(); } else error_log( $msg );
  140. return FALSE;
  141. }
  142. if( $log ) $timeexec['fileOk'] = microtime( TRUE );
  143. if( !function_exists( 'iCaldate2timestamp' )) {
  144. function iCaldate2timestamp( $d ) {
  145. if( 6 > count( $d ))
  146. return mktime( 0, 0, 0, $d['month'], $d['day'], $d['year'] );
  147. else
  148. return mktime( $d['hour'], $d['min'], $d['sec'], $d['month'], $d['day'], $d['year'] );
  149. }
  150. }
  151. if( !function_exists( 'fixiCalString' )) {
  152. function fixiCalString( $s ) {
  153. $s = str_replace( '\,', ',', $s );
  154. $s = str_replace( '\;', ';', $s );
  155. $s = str_replace( '\n ', chr(10), $s );
  156. $s = str_replace( '\\\\', '\\', $s );
  157. return $s;
  158. }
  159. }
  160. /* create output array */
  161. $rows = array();
  162. /* info rows */
  163. $rows[] = array( 'kigkonsult.se', ICALCREATOR_VERSION, $iCal2csv_VERSION, date( 'Y-m-d H:i:s' ));
  164. $filename = ( $remoteInput ) ? $filename : $inputFileParts['basename'];
  165. $rows[] = array( 'iCal input', $filename, 'csv output', $outputFileParts['basename'] );
  166. if( $prop = $calendar->getProperty( 'CALSCALE' ))
  167. $rows[] = array( 'CALSCALE', $prop );
  168. if( $prop = $calendar->getProperty( 'METHOD' ))
  169. $rows[] = array( 'METHOD', $prop );
  170. while( $xprop = $calendar->getProperty())
  171. $rows[] = array( $xprop[0], $xprop[1] );
  172. if( $log ) $timeexec['infoOk'] = microtime( TRUE );
  173. /* fix vtimezone property order list */
  174. $proporder = array();
  175. foreach( $conf as $key => $value ) {
  176. if(( '2' <= $key) && ( '99' > $key )) {
  177. $proporder[$value] = $key;
  178. if( $log ) $log->log( "$iCal2csv_VERSION $value in column $key", 7 );
  179. }
  180. }
  181. $proporder['TYPE'] = 0;
  182. $proporder['ORDER'] = 1;
  183. $props = array( 'TZID', 'LAST-MODIFIED', 'TZURL', 'DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM'
  184. , 'TZOFFSETTFROM' // iCalcreator 2.6 bug
  185. , 'COMMENT', 'RRULE', 'RDATE', 'TZNAME' );
  186. $pix = 2;
  187. foreach( $props as $prop ) {
  188. if( isset( $proporder[$prop] )) continue;
  189. if( isset( $conf['skip'] ) && in_array( $prop, $conf['skip'] )) {
  190. if( $log ) $log->log( "$iCal2csv_VERSION $prop removed from output", 7 );
  191. continue;
  192. }
  193. while( in_array( $pix, $proporder )) $pix++;
  194. $proporder[$prop] = $pix++;
  195. }
  196. /* remove unused properties from and add x-props to property order list */
  197. $maxpropix = 11;
  198. if( $maxpropix != ( count( $proporder ) - 1 ))
  199. $maxpropix = count( $proporder ) - 1;
  200. $compsinfo = $calendar->getConfig( 'compsinfo');
  201. $potmp = array();
  202. $potmp[0] = 'TYPE';
  203. $potmp[1] = 'ORDER';
  204. foreach( $compsinfo as $cix => $compinfo) {
  205. if( 'vtimezone' != $compinfo['type'] )
  206. continue;
  207. $comp = $calendar->getComponent( $compinfo['ordno'] );
  208. foreach( $compinfo['props'] as $propName => $propcnt ) {
  209. if( !in_array( $propName, $potmp ) && isset( $proporder[$propName] ))
  210. $potmp[$proporder[$propName]] = $propName;
  211. elseif( 'X-PROP' == $propName ) {
  212. while( $xprop = $comp->getProperty()) {
  213. if( !in_array( $xprop[0], $potmp )) {
  214. $maxpropix += 1;
  215. $potmp[$maxpropix] = $xprop[0];
  216. } // end if
  217. } // end while xprop
  218. } // end X-PROP
  219. } // end $compinfo['props']
  220. if( isset( $compinfo['sub'] )) {
  221. foreach( $compinfo['sub'] as $compinfo2 ) {
  222. foreach( $compinfo2['props'] as $propName => $propcnt ) {
  223. if( !in_array( $propName, $potmp ) && isset( $proporder[$propName] ))
  224. $potmp[$proporder[$propName]] = $propName;
  225. elseif( 'X-PROP' == $propName ) {
  226. $scomp = $comp->getComponent( $compinfo2['ordno'] );
  227. while( $xprop = $scomp->getProperty()) {
  228. if( !in_array( $xprop[0], $potmp )) {
  229. $maxpropix += 1;
  230. $potmp[$maxpropix] = $xprop[0];
  231. } // end if
  232. } // end while xprop
  233. } // end X-PROP
  234. } // end $compinfo['sub']['props']
  235. } // end foreach( $compinfo['sub']
  236. } // end if( isset( $compinfo['sub']
  237. } // end foreach compinfo - vtimezone
  238. ksort( $potmp, SORT_NUMERIC );
  239. if( '2.6' == substr ( ICALCREATOR_VERSION, -3 )) {
  240. foreach( $potmp as $k => $v ) { // iCalcreator 2.6 bug fix
  241. if( 'TZOFFSETTFROM' == $v )
  242. $potmp[$k] = 'TZOFFSETFROM';
  243. }
  244. }
  245. $proporder = array_flip( array_values( $potmp ));
  246. if( $log ) $log->log( "$iCal2csv_VERSION zone proporder=".implode(',',array_flip($proporder)), 7 );
  247. /* create vtimezone info */
  248. $row = count( $rows ) - 1;
  249. if( 2 < count( $proporder )) {
  250. $row += 1;
  251. /* create vtimezone header row */
  252. foreach( $proporder as $propName => $col ) {
  253. if( isset( $conf[$propName] )) {
  254. $rows[$row][$col] = $conf[$propName]; // check map of userfriendly name to iCal property name
  255. if( $log ) $log->log( "$iCal2csv_VERSION header row, col=$col: $propName, replaced by ".$conf[$propName], 7 );
  256. }
  257. else
  258. $rows[$row][$col] = $propName;
  259. }
  260. $allowedProps = array( 'VTIMEZONE' => array( 'TZID', 'LAST-MODIFIED', 'TZURL' )
  261. , 'STANDARD' => array( 'DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'COMMENT', 'RDATE', 'RRULE', 'TZNAME' )
  262. , 'DAYLIGHT' => array( 'DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'COMMENT', 'RDATE', 'RRULE', 'TZNAME' ));
  263. /* create vtimezone data rows */
  264. foreach( $compsinfo as $cix => $compinfo) {
  265. if( 'vtimezone' != $compinfo['type'] )
  266. continue;
  267. $row += 1;
  268. foreach( $proporder as $propName => $col )
  269. $rows[$row][] = ''; // set all cells empty
  270. $rows[$row][$proporder['TYPE']] = $compinfo['type'];
  271. $rows[$row][$proporder['ORDER']] = $compinfo['ordno'];
  272. $comp = $calendar->getComponent( $compinfo['ordno'] );
  273. foreach( $proporder as $propName => $col ) {
  274. if(( 'TYPE' == $propName ) || ( 'ORDER' == $propName ))
  275. continue;
  276. if( 'X-' == substr( $propName, 0, 2 ))
  277. continue;
  278. if( !in_array( $propName, $allowedProps['VTIMEZONE'] )) // check if component allows property
  279. continue;
  280. if( isset( $compinfo['props'][$propName] )) {
  281. if( 'LAST-MODIFIED' == $propName )
  282. $fcn = 'createLastModified';
  283. else
  284. $fcn = 'create'.strtoupper( substr( $propName, 0, 1 )).strtolower( substr( $propName, 1 ));
  285. if( !method_exists ( $comp, $fcn )) {
  286. $msg = $iCal2csv_VERSION.' ERROR 7 INPUT FILE:"'.$filename.'" iCalcreator: unknown property: "'.$propName.'" ('.$fcn.')';
  287. if( $log ) $log->log( $msg, 3 ); else error_log( $msg );
  288. continue;
  289. }
  290. $output = str_replace( "$calnl ", '', rtrim( $comp->$fcn()));
  291. $output = str_replace( $propName.';', '', $output );
  292. $output = str_replace( $propName.':', '', $output );
  293. $rows[$row][$proporder[$propName]] = fixiCalString( $output );
  294. }
  295. } // end foreach( $proporder
  296. if( isset( $compinfo['props']['X-PROP'] )) {
  297. while( $xprop = $comp->getProperty()) {
  298. $output = str_replace( "$calnl ", '', rtrim( $xprop[1] ));
  299. $rows[$row][$proporder[$xprop[0]]] = fixiCalString( $output );
  300. }
  301. }
  302. if( isset( $compinfo['sub'] )) {
  303. foreach( $compinfo['sub'] as $compinfo2 ) {
  304. $row += 1;
  305. foreach( $proporder as $propName => $col )
  306. $rows[$row][] = ''; // set all cells empty
  307. $rows[$row][$proporder['TYPE']] = $compinfo2['type'];
  308. $rows[$row][$proporder['ORDER']] = $compinfo['ordno'].':'.$compinfo2['ordno'];
  309. $scomp = $comp->getComponent( $compinfo2['ordno'] );
  310. foreach( $proporder as $propName => $col ) {
  311. if(( 'TYPE' == $propName ) || ( 'ORDER' == $propName ))
  312. continue;
  313. if( 'X-' == substr( $propName, 0, 2 ))
  314. continue;
  315. if( !in_array( $propName, $allowedProps[strtoupper( $compinfo2['type'] )] )) // check if component allows property
  316. continue;
  317. if(( '2.6' == substr ( ICALCREATOR_VERSION, -3 )) && ( 'TZOFFSETFROM' == $propName )) $propName = 'TZOFFSETTFROM'; // iCalcreator 2.6 bug fix
  318. if( isset( $compinfo2['props'][$propName] )) {
  319. if(( '2.6' == substr ( ICALCREATOR_VERSION, -3 )) && ( 'TZOFFSETTFROM' == $propName )) $propName = 'TZOFFSETFROM'; // iCalcreator 2.6 bug fix
  320. $fcn = 'create'.strtoupper( substr( $propName, 0, 1 )).strtolower( substr( $propName, 1 ));
  321. if( !method_exists ( $scomp, $fcn )) {
  322. $msg = $iCal2csv_VERSION.' ERROR 8 INPUT FILE:"'.$filename.'" iCalcreator: unknown property: "'.$propName.'" ('.$fcn.')';
  323. if( $log ) $log->log( $msg, 3 ); else error_log( $msg );
  324. continue;
  325. }
  326. $output = str_replace( "$calnl ", '', rtrim( $scomp->$fcn()));
  327. $output = str_replace( $propName.';', '', $output );
  328. $output = str_replace( $propName.':', '', $output );
  329. $rows[$row][$proporder[$propName]] = fixiCalString( $output );
  330. }
  331. } // end foreach( $proporder
  332. if( isset( $compinfo2['props']['X-PROP'] )) {
  333. while( $xprop = $scomp->getProperty()) {
  334. $output = str_replace( "$calnl ", '', rtrim( $xprop[1] ));
  335. $rows[$row][$proporder[$xprop[0]]] = fixiCalString( $output );
  336. }
  337. }
  338. } // end foreach( $compinfo['sub']
  339. } // end if( isset( $compinfo['sub']['props'] ))
  340. } // end foreach
  341. } // end vtimezone
  342. if( $log ) $timeexec['zoneOk'] = microtime( TRUE );
  343. $maxColCount = count( $proporder );
  344. /* fix property order list */
  345. $proporder = array();
  346. foreach( $conf as $key => $value ) {
  347. if(( '2' <= $key) && ( '99' > $key )) {
  348. $proporder[$value] = $key;
  349. if( $log ) $log->log( "$iCal2csv_VERSION $value in column $key", 7 );
  350. }
  351. }
  352. $proporder['TYPE'] = 0;
  353. $proporder['ORDER'] = 1;
  354. $props = array( 'UID', 'DTSTAMP', 'SUMMARY', 'DTSTART', 'DURATION', 'DTEND', 'DUE', 'RRULE', 'RDATE', 'EXRULE', 'EXDATE'
  355. , 'DESCRIPTION', 'CATEGORIES', 'ORGANIZER', 'LOCATION', 'RESOURCES', 'CONTACT', 'URL', 'COMMENT', 'PRIORITY'
  356. , 'ATTENDEE', 'CLASS', 'TRANSP', 'SEQUENCE', 'STATUS', 'COMPLETED', 'CREATED', 'LAST-MODIFIED', 'ACTION'
  357. , 'TRIGGER', 'REPEAT', 'ATTACH', 'FREEBUSY', 'RELATED-TO', 'REQUEST-STATUS', 'GEO', 'PERCENT-COMPLETE', 'RECURRENCE-ID' );
  358. $pix = 2;
  359. foreach( $props as $prop ) {
  360. if( isset( $proporder[$prop] )) continue;
  361. if( isset( $conf['skip'] ) && in_array( $prop, $conf['skip'] )) {
  362. if( $log ) $log->log( "$iCal2csv_VERSION $prop removed from output", 7 );
  363. continue;
  364. }
  365. while( in_array( $pix, $proporder )) $pix++;
  366. $proporder[$prop] = $pix++;
  367. }
  368. /* remove unused properties from and add x-props to property order list */
  369. if( $maxpropix < (count( $proporder ) - 1))
  370. $maxpropix = count( $proporder ) - 1;
  371. $potmp = array();
  372. $potmp[0] = 'TYPE';
  373. $potmp[1] = 'ORDER';
  374. // $potmp[2] = 'UID';
  375. foreach( $compsinfo as $cix => $compinfo) {
  376. if( 'vtimezone' == $compinfo['type'] )
  377. continue;
  378. foreach( $compinfo['props'] as $propName => $propcnt ) {
  379. if( !in_array( $propName, $potmp ) && isset( $proporder[$propName] ))
  380. $potmp[$proporder[$propName]] = $propName;
  381. elseif( 'X-PROP' == $propName ) {
  382. $comp = $calendar->getComponent( $compinfo['ordno'] );
  383. while( $xprop = $comp->getProperty()) {
  384. if( !in_array( $xprop[0], $potmp )) {
  385. $maxpropix += 1;
  386. $potmp[$maxpropix] = $xprop[0];
  387. } // end if
  388. } // while( $xprop
  389. } // end elseif( 'X-PROP'
  390. } // end foreach( $compinfo['props']
  391. if( isset( $compinfo['sub'] )) {
  392. foreach( $compinfo['sub'] as $compinfo2 ) {
  393. foreach( $compinfo2['props'] as $propName => $propcnt ) {
  394. if( !in_array( $propName, $potmp ) && isset( $proporder[$propName] ))
  395. $potmp[$proporder[$propName]] = $propName;
  396. elseif( 'X-PROP' == $propName ) {
  397. $scomp = $comp->getComponent( $compinfo2['ordno'] );
  398. while( $xprop = $scomp->getProperty()) {
  399. if( !in_array( $xprop[0], $potmp )) {
  400. $maxpropix += 1;
  401. $potmp[$maxpropix] = $xprop[0];
  402. } // end if
  403. } // end while xprop
  404. } // end X-PROP
  405. } // end $compinfo['sub']['props']
  406. } // end foreach( $compinfo['sub']
  407. } // end if( isset( $compinfo['sub']
  408. }
  409. ksort( $potmp, SORT_NUMERIC );
  410. $proporder = array_flip( array_values( $potmp ));
  411. if( $log ) $log->log( "$iCal2csv_VERSION comp proporder=".implode(',',array_flip($proporder)), 7 );
  412. if( $maxColCount < count( $proporder ))
  413. $maxColCount = count( $proporder );
  414. /* create header row */
  415. $row += 1;
  416. foreach( $proporder as $propName => $col ) {
  417. if( isset( $conf[$propName] )) {
  418. $rows[$row][$col] = $conf[$propName]; // check map of userfriendly name to iCal property name
  419. if( $log ) $log->log( "$iCal2csv_VERSION header row, col=$col: $propName, replaced by ".$conf[$propName], 7 );
  420. }
  421. else
  422. $rows[$row][$col] = $propName;
  423. }
  424. $allowedProps = array( 'VEVENT' => array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'CONTACT', 'CREATED', 'DESCRIPTION', 'DTEND'
  425. , 'DTSTAMP', 'DTSTART', 'DURATION', 'EXDATE', 'RXRULE', 'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER'
  426. , 'PRIORITY', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'SEQUENCE'
  427. , 'STATUS', 'SUMMARY', 'TRANSP', 'UID', 'URL', )
  428. , 'VTODO' => array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'COMPLETED', 'CONTACT', 'CREATED', 'DESCRIPTION'
  429. , 'DTSTAMP', 'DTSTART', 'DUE', 'DURATION', 'EXATE', 'EXRULE', 'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER'
  430. , 'PERCENT', 'PRIORITY', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS'
  431. , 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL' )
  432. , 'VJOURNAL' => array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'CONTACT', 'CREATED', 'DESCRIPTION', 'DTSTAMP'
  433. , 'DTSTART', 'EXDATE', 'EXRULE', 'LAST-MODIFIED', 'ORGANIZER', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO'
  434. , 'RRULE', 'REQUEST-STATUS', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL' )
  435. , 'VFREEBUSY' => array( 'ATTENDEE', 'COMMENT', 'CONTACT', 'DTEND', 'DTSTAMP', 'DTSTART', 'DURATION', 'FREEBUSY', 'ORGANIZER', 'UID', 'URL' )
  436. , 'VALARM' => array( 'ACTION', 'ATTACH', 'ATTENDEE', 'DESCRIPTION', 'DURATION', 'REPEAT', 'TRANSP', 'TRIGGER' ));
  437. /* create data rows */
  438. foreach( $compsinfo as $cix => $compinfo) {
  439. if( 'vtimezone' == $compinfo['type'] )
  440. continue;
  441. $row += 1;
  442. foreach( $proporder as $propName => $col )
  443. $rows[$row][] = ''; // set all cells empty
  444. $rows[$row][$proporder['TYPE']] = $compinfo['type'];
  445. $rows[$row][$proporder['ORDER']] = $compinfo['ordno'];
  446. // $rows[$row][$proporder['UID']] = $compinfo['uid'];
  447. $comp = $calendar->getComponent( $compinfo['ordno'] );
  448. foreach( $proporder as $propName => $col ) {
  449. if(( 'TYPE' == $propName ) || ( 'ORDER' == $propName ))
  450. continue;
  451. if( 'X-' == substr( $propName, 0, 2 ))
  452. continue;
  453. if( !in_array( $propName, $allowedProps[strtoupper( $compinfo['type'] )] )) // check if component allows property
  454. continue;
  455. if( isset( $compinfo['props'][$propName] )) {
  456. switch( $propName ) {
  457. case 'LAST-MODIFIED' ;
  458. $fcn = 'createLastModified';
  459. break;
  460. case 'RECURRENCE-ID':
  461. $fcn = 'createRecurrenceid';
  462. break;
  463. case 'RELATED-TO':
  464. $fcn = 'createRelatedTo';
  465. break;
  466. case 'REQUEST-STATUS':
  467. $fcn = 'createRequestStatus';
  468. break;
  469. case 'PERCENT-COMPLETE':
  470. $fcn = 'createPercentComplete';
  471. break;
  472. default:
  473. $fcn = 'create'.strtoupper( substr( $propName, 0, 1 )).strtolower( substr( $propName, 1 ));
  474. }
  475. if( !method_exists ( $comp, $fcn )) {
  476. $msg = $iCal2csv_VERSION.' ERROR 9 INPUT FILE:"'.$filename.'" iCalcreator: unknown property: "'.$propName.'" ('.$fcn.')';
  477. if( $log ) $log->log( $msg, 3 ); else error_log( $msg );
  478. continue;
  479. }
  480. $output = str_replace( "$calnl ", '', rtrim( $comp->$fcn()));
  481. $output = str_replace( $propName.';', '', $output );
  482. $output = str_replace( $propName.':', '', $output );
  483. $rows[$row][$proporder[$propName]] = fixiCalString( $output );
  484. }
  485. } // end foreach( $proporder
  486. if( isset( $compinfo['props']['X-PROP'] )) {
  487. while( $xprop = $comp->getProperty()) {
  488. $output = str_replace( "$calnl ", '', rtrim( $xprop[1] ));
  489. $rows[$row][$proporder[$xprop[0]]] = fixiCalString( $output );
  490. }
  491. }
  492. if( isset( $compinfo['sub'] )) {
  493. foreach( $compinfo['sub'] as $compinfo2 ) {
  494. $row += 1;
  495. foreach( $proporder as $propName => $col )
  496. $rows[$row][] = ''; // set all cells empty
  497. $rows[$row][$proporder['TYPE']] = $compinfo2['type'];
  498. $rows[$row][$proporder['ORDER']] = $compinfo['ordno'].':'.$compinfo2['ordno'];
  499. $scomp = $comp->getComponent( $compinfo2['ordno'] );
  500. foreach( $proporder as $propName => $col ) {
  501. if(( 'TYPE' == $propName ) || ( 'ORDER' == $propName ))
  502. continue;
  503. if( 'X-' == substr( $propName, 0, 2 ))
  504. continue;
  505. if( !in_array( $propName, $allowedProps[strtoupper( $compinfo2['type'] )] )) // check if component allows property
  506. continue;
  507. if( isset( $compinfo2['props'][$propName] )) {
  508. $fcn = 'create'.strtoupper( substr( $propName, 0, 1 )).strtolower( substr( $propName, 1 ));
  509. if( !method_exists ( $scomp, $fcn )) {
  510. $msg = $iCal2csv_VERSION.' ERROR 10 INPUT FILE:"'.$filename.'" iCalcreator: unknown property: "'.$propName.'" ('.$fcn.')';
  511. if( $log ) $log->log( $msg, 3 ); else error_log( $msg );
  512. continue;
  513. }
  514. $output = str_replace( "$calnl ", '', rtrim( $scomp->$fcn()));
  515. $output = str_replace( $propName.';', '', $output );
  516. $output = str_replace( $propName.':', '', $output );
  517. $rows[$row][$proporder[$propName]] = fixiCalString( $output );
  518. }
  519. } // end foreach( $proporder
  520. if( isset( $compinfo2['props']['X-PROP'] )) {
  521. while( $xprop = $scomp->getProperty()) {
  522. $output = str_replace( "$calnl ", '', rtrim( $xprop[1] ));
  523. $rows[$row][$proporder[$xprop[0]]] = fixiCalString( $output );
  524. }
  525. }
  526. } // if( isset( $compinfo2['props']['X-PROP']
  527. } // end if( isset( $compinfo['sub']
  528. } // foreach( $compsinfo as
  529. if( $log ) $timeexec['compOk'] = microtime( TRUE );
  530. /* fix csv format */
  531. // fields that contain commas, double-quotes, or line-breaks must be quoted,
  532. // a quote within a field must be escaped with an additional quote immediately preceding the literal quote,
  533. // space before and after delimiter commas may be trimmed (which is prohibited by RFC 4180)
  534. // a line break within an element must be preserved.
  535. // Fields may ALWAYS be enclosed within double-quote characters, whether necessary or not.
  536. foreach( $rows as $row => $line ) {
  537. for( $col = 0; $col < $maxColCount; $col++ ) {
  538. if( !isset( $line[$col] ) || empty( $line[$col] )) {
  539. $rows[$row][$col] = $conf['del'].$conf['del'];
  540. continue;
  541. }
  542. if( ctype_digit( $line[$col] ))
  543. continue;
  544. $cell = str_replace( $conf['del'], $conf['del'].$conf['del'], $line[$col] );
  545. $rows[$row][$col] = $conf['del'].$cell.$conf['del'];
  546. }
  547. $rows[$row] = implode( $conf['sep'], $rows[$row] );
  548. }
  549. $output = implode( $conf['nl'], $rows ).$conf['nl'];
  550. if( $log ) {
  551. $timeexec['exit'] = microtime( TRUE );
  552. $msg = "$iCal2csv_VERSION '$filename'";
  553. $msg .= ' fileOk:' .number_format(( $timeexec['fileOk'] - $timeexec['start'] ), 5 );
  554. $msg .= ' infoOk:' .number_format(( $timeexec['infoOk'] - $timeexec['fileOk'] ), 5 );
  555. $msg .= ' zoneOk:' .number_format(( $timeexec['zoneOk'] - $timeexec['infoOk'] ), 5 );
  556. $msg .= ' compOk:' .number_format(( $timeexec['compOk'] - $timeexec['zoneOk'] ), 5 );
  557. $msg .= ' csvOk:' .number_format(( $timeexec['exit'] - $timeexec['compOk'] ), 5 );
  558. $msg .= ' total:' .number_format(( $timeexec['exit'] - $timeexec['start'] ), 5 ).'sec';
  559. $log->log( $msg, 7 );
  560. $msg = "$iCal2csv_VERSION '$filename' (".count($compsinfo).' components) start:'.date( 'H:i:s', $timeexec['start'] );
  561. $msg .= ' total:' .number_format(( $timeexec['exit'] - $timeexec['start'] ), 5 ).'sec';
  562. if( $save )
  563. $msg .= " -> '$diskfilename'";
  564. $msg .= ', size='.strlen( $output );
  565. $msg .= ', '.count( $rows )." rows, $maxColCount cols";
  566. $log->log( $msg, 6 );
  567. }
  568. /* save or send the file */
  569. if( $save ) {
  570. if( FALSE !== file_put_contents( $diskfilename, $output )) {
  571. if( $log ) $log->flush();
  572. return TRUE;
  573. }
  574. else {
  575. $msg = $iCal2csv_VERSION.' ERROR 11 INPUT FILE:"'.$filename.'" Invalid write to output file : "'.$diskfilename.'"';
  576. if( $log ) { $log->log( $msg, 3 ); $log->flush(); } else error_log( $msg );
  577. return FALSE;
  578. }
  579. }
  580. else {
  581. if( $log ) $log->flush();
  582. /** return data, auto gzip */
  583. $filezise = strlen( $output );
  584. if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] )) {
  585. $output = gzencode( $output, 9 );
  586. $filezise = strlen( $output );
  587. header( 'Content-Encoding: gzip');
  588. header( 'Vary: *');
  589. }
  590. header( 'Content-Type: text/csv; charset=utf-8' );
  591. header( 'Content-Disposition: attachment; filename="'.$outputFileParts['basename'].'"' );
  592. header( 'Cache-Control: max-age=10' );
  593. header( 'Content-Length: '.$filezise );
  594. echo $output;
  595. }
  596. exit();
  597. }
  598. ?>