PageRenderTime 129ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 3ms

/wp-content/plugins/all-in-one-event-calendar/lib/iCal/iCalcreator-2.20/iCalcreator.class.php

https://github.com/dedavidd/piratenpartij.nl
PHP | 10543 lines | 8068 code | 31 blank | 2444 comment | 2377 complexity | ca019c5514c8c616bf64eacb28151ef2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /*********************************************************************************/
  3. /**
  4. *
  5. * This file is a PHP implementation of rfc2445/rfc5545.
  6. *
  7. * @copyright Copyright (c) 2007-2014 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
  8. * @link http://kigkonsult.se/iCalcreator/index.php
  9. * @license http://kigkonsult.se/downloads/dl.php?f=LGPL
  10. * @package iCalcreator
  11. * @version v2.20
  12. */
  13. /**
  14. * This library is free software; you can redistribute it and/or
  15. * modify it under the terms of the GNU Lesser General Public
  16. * License as published by the Free Software Foundation; either
  17. * version 2.1 of the License, or (at your option) any later version.
  18. *
  19. * This library is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  22. * Lesser General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Lesser General Public
  25. * License along with this library; if not, write to the Free Software
  26. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  27. */
  28. /*********************************************************************************/
  29. /**
  30. * Do NOT remove or change version!!
  31. *
  32. * @copyright Copyright (c) 2007-2014 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
  33. * @license http://kigkonsult.se/downloads/dl.php?f=LGPL
  34. */
  35. define( 'ICALCREATOR_VERSION', 'iCalcreator 2.20' );
  36. /*********************************************************************************/
  37. /**
  38. * vcalendar class
  39. *
  40. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  41. * @since 2.9.6 - 2011-05-14
  42. */
  43. class vcalendar {
  44. // calendar property variables
  45. var $calscale;
  46. var $method;
  47. var $prodid;
  48. var $version;
  49. var $xprop;
  50. // container for calendar components
  51. var $components;
  52. // component config variables
  53. var $allowEmpty;
  54. var $unique_id;
  55. var $language;
  56. var $directory;
  57. var $filename;
  58. var $url;
  59. var $delimiter;
  60. var $nl;
  61. var $format;
  62. var $dtzid;
  63. // component internal variables
  64. var $attributeDelimiter;
  65. var $valueInit;
  66. // component xCal declaration container
  67. var $xcaldecl;
  68. /**
  69. * constructor for calendar object
  70. *
  71. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  72. * @since 2.9.6 - 2011-05-14
  73. * @param array $config
  74. * @return void
  75. */
  76. function vcalendar ( $config = array()) {
  77. $this->_makeVersion();
  78. $this->calscale = null;
  79. $this->method = null;
  80. $this->_makeUnique_id();
  81. $this->prodid = null;
  82. $this->xprop = array();
  83. $this->language = null;
  84. $this->directory = null;
  85. $this->filename = null;
  86. $this->url = null;
  87. $this->dtzid = null;
  88. /**
  89. * language = <Text identifying a language, as defined in [RFC 1766]>
  90. */
  91. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  92. $config['language'] = ICAL_LANG;
  93. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  94. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  95. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  96. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  97. $this->setConfig( $config );
  98. $this->xcaldecl = array();
  99. $this->components = array();
  100. }
  101. /**
  102. * return iCalcreator version number
  103. *
  104. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  105. * @since 2.18.5 - 2013-08-29
  106. * @uses ICALCREATOR_VERSION
  107. * @return string
  108. */
  109. public static function iCalcreatorVersion() {
  110. return trim( substr( ICALCREATOR_VERSION, strpos( ICALCREATOR_VERSION, ' ' )));
  111. }
  112. /*********************************************************************************/
  113. /**
  114. * Property Name: CALSCALE
  115. */
  116. /**
  117. * creates formatted output for calendar property calscale
  118. *
  119. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  120. * @since 2.10.16 - 2011-10-28
  121. * @return string
  122. */
  123. function createCalscale() {
  124. if( empty( $this->calscale )) return FALSE;
  125. switch( $this->format ) {
  126. case 'xcal':
  127. return $this->nl.' calscale="'.$this->calscale.'"';
  128. break;
  129. default:
  130. return 'CALSCALE:'.$this->calscale.$this->nl;
  131. break;
  132. }
  133. }
  134. /**
  135. * set calendar property calscale
  136. *
  137. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  138. * @since 2.4.8 - 2008-10-21
  139. * @param string $value
  140. * @return void
  141. */
  142. function setCalscale( $value ) {
  143. if( empty( $value )) return FALSE;
  144. $this->calscale = $value;
  145. }
  146. /*********************************************************************************/
  147. /**
  148. * Property Name: METHOD
  149. */
  150. /**
  151. * creates formatted output for calendar property method
  152. *
  153. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  154. * @since 2.10.16 - 2011-10-28
  155. * @return string
  156. */
  157. function createMethod() {
  158. if( empty( $this->method )) return FALSE;
  159. switch( $this->format ) {
  160. case 'xcal':
  161. return $this->nl.' method="'.$this->method.'"';
  162. break;
  163. default:
  164. return 'METHOD:'.$this->method.$this->nl;
  165. break;
  166. }
  167. }
  168. /**
  169. * set calendar property method
  170. *
  171. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  172. * @since 2.4.8 - 2008-20-23
  173. * @param string $value
  174. * @return bool
  175. */
  176. function setMethod( $value ) {
  177. if( empty( $value )) return FALSE;
  178. $this->method = $value;
  179. return TRUE;
  180. }
  181. /*********************************************************************************/
  182. /**
  183. * Property Name: PRODID
  184. *
  185. */
  186. /**
  187. * creates formatted output for calendar property prodid
  188. *
  189. * @copyright copyright (c) 2007-2013 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
  190. * @license http://kigkonsult.se/downloads/dl.php?f=LGPL
  191. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  192. * @since 2.12.11 - 2012-05-13
  193. * @return string
  194. */
  195. function createProdid() {
  196. if( !isset( $this->prodid ))
  197. $this->_makeProdid();
  198. switch( $this->format ) {
  199. case 'xcal':
  200. return $this->nl.' prodid="'.$this->prodid.'"';
  201. break;
  202. default:
  203. $toolbox = new calendarComponent();
  204. $toolbox->setConfig( $this->getConfig());
  205. return $toolbox->_createElement( 'PRODID', '', $this->prodid );
  206. break;
  207. }
  208. }
  209. /**
  210. * make default value for calendar prodid, do NOT alter or remove this method or invoke of this method
  211. *
  212. * @copyright copyright (c) 2007-2013 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
  213. * @license http://kigkonsult.se/downloads/dl.php?f=LGPL
  214. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  215. * @since 2.6.8 - 2009-12-30
  216. * @return void
  217. */
  218. function _makeProdid() {
  219. $this->prodid = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
  220. }
  221. /**
  222. * Conformance: The property MUST be specified once in an iCalendar object.
  223. * Description: The vendor of the implementation SHOULD assure that this
  224. * is a globally unique identifier; using some technique such as an FPI
  225. * value, as defined in [ISO 9070].
  226. */
  227. /**
  228. * make default unique_id for calendar prodid
  229. *
  230. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  231. * @since 0.3.0 - 2006-08-10
  232. * @return void
  233. */
  234. function _makeUnique_id() {
  235. $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
  236. }
  237. /*********************************************************************************/
  238. /**
  239. * Property Name: VERSION
  240. *
  241. * Description: A value of "2.0" corresponds to this memo.
  242. */
  243. /**
  244. * creates formatted output for calendar property version
  245. *
  246. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  247. * @since 2.10.16 - 2011-10-28
  248. * @return string
  249. */
  250. function createVersion() {
  251. if( empty( $this->version ))
  252. $this->_makeVersion();
  253. switch( $this->format ) {
  254. case 'xcal':
  255. return $this->nl.' version="'.$this->version.'"';
  256. break;
  257. default:
  258. return 'VERSION:'.$this->version.$this->nl;
  259. break;
  260. }
  261. }
  262. /**
  263. * set default calendar version
  264. *
  265. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  266. * @since 0.3.0 - 2006-08-10
  267. * @return void
  268. */
  269. function _makeVersion() {
  270. $this->version = '2.0';
  271. }
  272. /**
  273. * set calendar version
  274. *
  275. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  276. * @since 2.4.8 - 2008-10-23
  277. * @param string $value
  278. * @return void
  279. */
  280. function setVersion( $value ) {
  281. if( empty( $value )) return FALSE;
  282. $this->version = $value;
  283. return TRUE;
  284. }
  285. /*********************************************************************************/
  286. /**
  287. * Property Name: x-prop
  288. */
  289. /**
  290. * creates formatted output for calendar property x-prop, iCal format only
  291. *
  292. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  293. * @since 2.16.21 - 2013-05-25
  294. * @return string
  295. */
  296. function createXprop() {
  297. if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
  298. $output = null;
  299. $toolbox = new calendarComponent();
  300. $toolbox->setConfig( $this->getConfig());
  301. foreach( $this->xprop as $label => $xpropPart ) {
  302. if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
  303. if( $this->getConfig( 'allowEmpty' ))
  304. $output .= $toolbox->_createElement( $label );
  305. continue;
  306. }
  307. $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
  308. if( is_array( $xpropPart['value'] )) {
  309. foreach( $xpropPart['value'] as $pix => $theXpart )
  310. $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
  311. $xpropPart['value'] = implode( ',', $xpropPart['value'] );
  312. }
  313. else
  314. $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
  315. $output .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
  316. if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
  317. foreach( $toolbox->xcaldecl as $localxcaldecl )
  318. $this->xcaldecl[] = $localxcaldecl;
  319. }
  320. }
  321. return $output;
  322. }
  323. /**
  324. * set calendar property x-prop
  325. *
  326. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  327. * @since 2.18.10 - 2013-09-04
  328. * @param string $label
  329. * @param string $value
  330. * @param array $params optional
  331. * @return bool
  332. */
  333. function setXprop( $label, $value, $params=FALSE ) {
  334. if( empty( $label ))
  335. return FALSE;
  336. $label = strtoupper( $label );
  337. if( 'X-' != substr( $label, 0, 2 ))
  338. return FALSE;
  339. if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  340. $xprop = array( 'value' => $value );
  341. $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
  342. if( !is_array( $this->xprop ))
  343. $this->xprop = array();
  344. $this->xprop[$label] = $xprop;
  345. return TRUE;
  346. }
  347. /*********************************************************************************/
  348. /**
  349. * delete calendar property value
  350. *
  351. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  352. * @since 2.8.8 - 2011-03-15
  353. * @param mixed $propName, bool FALSE => X-property
  354. * @param int $propix, optional, if specific property is wanted in case of multiply occurences
  355. * @return bool, if successfull delete
  356. */
  357. function deleteProperty( $propName=FALSE, $propix=FALSE ) {
  358. $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
  359. if( !$propix )
  360. $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
  361. $this->propdelix[$propName] = --$propix;
  362. $return = FALSE;
  363. switch( $propName ) {
  364. case 'CALSCALE':
  365. if( isset( $this->calscale )) {
  366. $this->calscale = null;
  367. $return = TRUE;
  368. }
  369. break;
  370. case 'METHOD':
  371. if( isset( $this->method )) {
  372. $this->method = null;
  373. $return = TRUE;
  374. }
  375. break;
  376. default:
  377. $reduced = array();
  378. if( $propName != 'X-PROP' ) {
  379. if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
  380. foreach( $this->xprop as $k => $a ) {
  381. if(( $k != $propName ) && !empty( $a ))
  382. $reduced[$k] = $a;
  383. }
  384. }
  385. else {
  386. if( count( $this->xprop ) <= $propix ) return FALSE;
  387. $xpropno = 0;
  388. foreach( $this->xprop as $xpropkey => $xpropvalue ) {
  389. if( $propix != $xpropno )
  390. $reduced[$xpropkey] = $xpropvalue;
  391. $xpropno++;
  392. }
  393. }
  394. $this->xprop = $reduced;
  395. if( empty( $this->xprop )) {
  396. unset( $this->propdelix[$propName] );
  397. return FALSE;
  398. }
  399. return TRUE;
  400. }
  401. return $return;
  402. }
  403. /**
  404. * get calendar property value/params
  405. *
  406. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  407. * @since 2.18.10 - 2013-09-04
  408. * @param string $propName, optional
  409. * @param int $propix, optional, if specific property is wanted in case of multiply occurences
  410. * @param bool $inclParam=FALSE
  411. * @return mixed
  412. */
  413. function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
  414. $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
  415. if( 'X-PROP' == $propName ) {
  416. if( !$propix )
  417. $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
  418. $this->propix[$propName] = --$propix;
  419. }
  420. else {
  421. $mProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
  422. $vComps = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
  423. $dateFmt = '%04d%02d%02d';
  424. }
  425. switch( $propName ) {
  426. case 'ATTENDEE':
  427. case 'CATEGORIES':
  428. case 'CONTACT':
  429. case 'DTSTART':
  430. case 'GEOLOCATION':
  431. case 'LOCATION':
  432. case 'ORGANIZER':
  433. case 'PRIORITY':
  434. case 'RESOURCES':
  435. case 'STATUS':
  436. case 'SUMMARY':
  437. case 'RECURRENCE-ID-UID':
  438. case 'RELATED-TO':
  439. case 'R-UID':
  440. case 'UID':
  441. case 'URL':
  442. $output = array();
  443. foreach ( $this->components as $cix => $component) {
  444. if( !in_array( $component->objName, $vComps))
  445. continue;
  446. if( in_array( $propName, $mProps )) {
  447. $component->_getProperties( $propName, $output );
  448. continue;
  449. }
  450. elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
  451. if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
  452. $content = $component->getProperty( 'UID' );
  453. }
  454. elseif( 'GEOLOCATION' == $propName ) {
  455. $content = ( FALSE === ( $loc = $component->getProperty( 'LOCATION' ))) ? '' : $loc.' ';
  456. if( FALSE === ( $geo = $component->getProperty( 'GEO' )))
  457. continue;
  458. $content .= iCalUtilityFunctions::_geo2str2( $geo['latitude'], iCalUtilityFunctions::$geoLatFmt ).
  459. iCalUtilityFunctions::_geo2str2( $geo['longitude'], iCalUtilityFunctions::$geoLongFmt ).'/';
  460. }
  461. elseif( FALSE === ( $content = $component->getProperty( $propName )))
  462. continue;
  463. if(( FALSE === $content ) || empty( $content ))
  464. continue;
  465. elseif( is_array( $content )) {
  466. if( isset( $content['year'] )) {
  467. $key = sprintf( $dateFmt, $content['year'], $content['month'], $content['day'] );
  468. if( !isset( $output[$key] ))
  469. $output[$key] = 1;
  470. else
  471. $output[$key] += 1;
  472. }
  473. else {
  474. foreach( $content as $partValue => $partCount ) {
  475. if( !isset( $output[$partValue] ))
  476. $output[$partValue] = $partCount;
  477. else
  478. $output[$partValue] += $partCount;
  479. }
  480. }
  481. } // end elseif( is_array( $content )) {
  482. elseif( !isset( $output[$content] ))
  483. $output[$content] = 1;
  484. else
  485. $output[$content] += 1;
  486. } // end foreach ( $this->components as $cix => $component)
  487. if( !empty( $output ))
  488. ksort( $output );
  489. return $output;
  490. break;
  491. case 'CALSCALE':
  492. return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
  493. break;
  494. case 'METHOD':
  495. return ( !empty( $this->method )) ? $this->method : FALSE;
  496. break;
  497. case 'PRODID':
  498. if( empty( $this->prodid ))
  499. $this->_makeProdid();
  500. return $this->prodid;
  501. break;
  502. case 'VERSION':
  503. return ( !empty( $this->version )) ? $this->version : FALSE;
  504. break;
  505. default:
  506. if( $propName != 'X-PROP' ) {
  507. if( !isset( $this->xprop[$propName] )) return FALSE;
  508. return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
  509. : array( $propName, $this->xprop[$propName]['value'] );
  510. }
  511. else {
  512. if( empty( $this->xprop )) return FALSE;
  513. $xpropno = 0;
  514. foreach( $this->xprop as $xpropkey => $xpropvalue ) {
  515. if( $propix == $xpropno )
  516. return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
  517. : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
  518. else
  519. $xpropno++;
  520. }
  521. unset( $this->propix[$propName] );
  522. return FALSE; // not found ??
  523. }
  524. }
  525. return FALSE;
  526. }
  527. /**
  528. * general vcalendar property setting
  529. *
  530. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  531. * @since 2.2.13 - 2007-11-04
  532. * @param mixed $args variable number of function arguments,
  533. * first argument is ALWAYS component name,
  534. * second ALWAYS component value!
  535. * @return bool
  536. */
  537. function setProperty () {
  538. $numargs = func_num_args();
  539. if( 1 > $numargs )
  540. return FALSE;
  541. $arglist = func_get_args();
  542. $arglist[0] = strtoupper( $arglist[0] );
  543. switch( $arglist[0] ) {
  544. case 'CALSCALE':
  545. return $this->setCalscale( $arglist[1] );
  546. case 'METHOD':
  547. return $this->setMethod( $arglist[1] );
  548. case 'VERSION':
  549. return $this->setVersion( $arglist[1] );
  550. default:
  551. if( !isset( $arglist[1] )) $arglist[1] = null;
  552. if( !isset( $arglist[2] )) $arglist[2] = null;
  553. return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
  554. }
  555. return FALSE;
  556. }
  557. /*********************************************************************************/
  558. /**
  559. * get vcalendar config values or * calendar components
  560. *
  561. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  562. * @since 2.11.7 - 2012-01-12
  563. * @param mixed $config
  564. * @return value
  565. */
  566. function getConfig( $config = FALSE ) {
  567. if( !$config ) {
  568. $return = array();
  569. $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' );
  570. $return['DELIMITER'] = $this->getConfig( 'DELIMITER' );
  571. $return['DIRECTORY'] = $this->getConfig( 'DIRECTORY' );
  572. $return['FILENAME'] = $this->getConfig( 'FILENAME' );
  573. $return['DIRFILE'] = $this->getConfig( 'DIRFILE' );
  574. $return['FILESIZE'] = $this->getConfig( 'FILESIZE' );
  575. $return['FORMAT'] = $this->getConfig( 'FORMAT' );
  576. if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' )))
  577. $return['LANGUAGE'] = $lang;
  578. $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
  579. $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' );
  580. if( FALSE !== ( $url = $this->getConfig( 'URL' )))
  581. $return['URL'] = $url;
  582. $return['TZID'] = $this->getConfig( 'TZID' );
  583. return $return;
  584. }
  585. switch( strtoupper( $config )) {
  586. case 'ALLOWEMPTY':
  587. return $this->allowEmpty;
  588. break;
  589. case 'COMPSINFO':
  590. unset( $this->compix );
  591. $info = array();
  592. foreach( $this->components as $cix => $component ) {
  593. if( empty( $component )) continue;
  594. $info[$cix]['ordno'] = $cix + 1;
  595. $info[$cix]['type'] = $component->objName;
  596. $info[$cix]['uid'] = $component->getProperty( 'uid' );
  597. $info[$cix]['props'] = $component->getConfig( 'propinfo' );
  598. $info[$cix]['sub'] = $component->getConfig( 'compsinfo' );
  599. }
  600. return $info;
  601. break;
  602. case 'DELIMITER':
  603. return $this->delimiter;
  604. break;
  605. case 'DIRECTORY':
  606. if( empty( $this->directory ) && ( '0' != $this->directory ))
  607. $this->directory = '.';
  608. return $this->directory;
  609. break;
  610. case 'DIRFILE':
  611. return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
  612. break;
  613. case 'FILEINFO':
  614. return array( $this->getConfig( 'directory' )
  615. , $this->getConfig( 'filename' )
  616. , $this->getConfig( 'filesize' ));
  617. break;
  618. case 'FILENAME':
  619. if( empty( $this->filename ) && ( '0' != $this->filename )) {
  620. if( 'xcal' == $this->format )
  621. $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
  622. else
  623. $this->filename = date( 'YmdHis' ).'.ics';
  624. }
  625. return $this->filename;
  626. break;
  627. case 'FILESIZE':
  628. $size = 0;
  629. if( empty( $this->url )) {
  630. $dirfile = $this->getConfig( 'dirfile' );
  631. if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
  632. $size = 0;
  633. clearstatcache();
  634. }
  635. return $size;
  636. break;
  637. case 'FORMAT':
  638. return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
  639. break;
  640. case 'LANGUAGE':
  641. /* get language for calendar component as defined in [RFC 1766] */
  642. return $this->language;
  643. break;
  644. case 'NL':
  645. case 'NEWLINECHAR':
  646. return $this->nl;
  647. break;
  648. case 'TZID':
  649. return $this->dtzid;
  650. break;
  651. case 'UNIQUE_ID':
  652. return $this->unique_id;
  653. break;
  654. case 'URL':
  655. if( !empty( $this->url ))
  656. return $this->url;
  657. else
  658. return FALSE;
  659. break;
  660. }
  661. }
  662. /**
  663. * general vcalendar config setting
  664. *
  665. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  666. * @since 2.18.12 - 2013-09-12
  667. * @param mixed $config
  668. * @param string $value
  669. * @return void
  670. */
  671. function setConfig( $config, $value = FALSE) {
  672. if( is_array( $config )) {
  673. $config = array_change_key_case( $config, CASE_UPPER );
  674. if( isset( $config['DELIMITER'] )) {
  675. if( FALSE === $this->setConfig( 'DELIMITER', $config['DELIMITER'] ))
  676. return FALSE;
  677. unset( $config['DELIMITER'] );
  678. }
  679. if( isset( $config['DIRECTORY'] )) {
  680. if( FALSE === $this->setConfig( 'DIRECTORY', $config['DIRECTORY'] ))
  681. return FALSE;
  682. unset( $config['DIRECTORY'] );
  683. }
  684. foreach( $config as $cKey => $cValue ) {
  685. if( FALSE === $this->setConfig( $cKey, $cValue ))
  686. return FALSE;
  687. }
  688. return TRUE;
  689. }
  690. else
  691. $res = FALSE;
  692. $config = strtoupper( $config );
  693. switch( $config ) {
  694. case 'ALLOWEMPTY':
  695. $this->allowEmpty = $value;
  696. $subcfg = array( 'ALLOWEMPTY' => $value );
  697. $res = TRUE;
  698. break;
  699. case 'DELIMITER':
  700. $this->delimiter = $value;
  701. return TRUE;
  702. break;
  703. case 'DIRECTORY':
  704. if( FALSE === ( $value = realpath( rtrim( trim( $value ), $this->delimiter ))))
  705. return FALSE;
  706. else {
  707. /* local directory */
  708. $this->directory = $value;
  709. $this->url = null;
  710. return TRUE;
  711. }
  712. break;
  713. case 'FILENAME':
  714. $value = trim( $value );
  715. $dirfile = $this->directory.$this->delimiter.$value;
  716. if( file_exists( $dirfile )) {
  717. /* local file exists */
  718. if( is_readable( $dirfile ) || is_writable( $dirfile )) {
  719. clearstatcache();
  720. $this->filename = $value;
  721. return TRUE;
  722. }
  723. else
  724. return FALSE;
  725. }
  726. elseif( is_readable( $this->directory ) || is_writable( $this->directory )) {
  727. /* read- or writable directory */
  728. clearstatcache();
  729. $this->filename = $value;
  730. return TRUE;
  731. }
  732. else
  733. return FALSE;
  734. break;
  735. case 'FORMAT':
  736. $value = trim( strtolower( $value ));
  737. if( 'xcal' == $value ) {
  738. $this->format = 'xcal';
  739. $this->attributeDelimiter = $this->nl;
  740. $this->valueInit = null;
  741. }
  742. else {
  743. $this->format = null;
  744. $this->attributeDelimiter = ';';
  745. $this->valueInit = ':';
  746. }
  747. $subcfg = array( 'FORMAT' => $value );
  748. $res = TRUE;
  749. break;
  750. case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
  751. $value = trim( $value );
  752. $this->language = $value;
  753. $this->_makeProdid();
  754. $subcfg = array( 'LANGUAGE' => $value );
  755. $res = TRUE;
  756. break;
  757. case 'NL':
  758. case 'NEWLINECHAR':
  759. $this->nl = $value;
  760. if( 'xcal' == $value ) {
  761. $this->attributeDelimiter = $this->nl;
  762. $this->valueInit = null;
  763. }
  764. else {
  765. $this->attributeDelimiter = ';';
  766. $this->valueInit = ':';
  767. }
  768. $subcfg = array( 'NL' => $value );
  769. $res = TRUE;
  770. break;
  771. case 'TZID':
  772. $this->dtzid = $value;
  773. $subcfg = array( 'TZID' => $value );
  774. $res = TRUE;
  775. break;
  776. case 'UNIQUE_ID':
  777. $value = trim( $value );
  778. $this->unique_id = $value;
  779. $this->_makeProdid();
  780. $subcfg = array( 'UNIQUE_ID' => $value );
  781. $res = TRUE;
  782. break;
  783. case 'URL':
  784. /* remote file - URL */
  785. $value = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
  786. $value = str_replace( 'HTTPS://', 'https://', trim( $value ));
  787. if(( 'http://' != substr( $value, 0, 7 )) && ( 'https://' != substr( $value, 0, 8 )))
  788. return FALSE;
  789. $this->directory = '.';
  790. $this->url = $value;
  791. if( '.ics' != strtolower( substr( $value, -4 )))
  792. unset( $this->filename );
  793. else
  794. $this->filename = $basename( $value );
  795. return TRUE;
  796. break;
  797. default: // any unvalid config key.. .
  798. return TRUE;
  799. }
  800. if( !$res ) return FALSE;
  801. if( isset( $subcfg ) && !empty( $this->components )) {
  802. foreach( $subcfg as $cfgkey => $cfgvalue ) {
  803. foreach( $this->components as $cix => $component ) {
  804. $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
  805. if( !$res )
  806. break 2;
  807. $this->components[$cix] = $component->copy(); // PHP4 compliant
  808. }
  809. }
  810. }
  811. return $res;
  812. }
  813. /*********************************************************************************/
  814. /**
  815. * add calendar component to container
  816. *
  817. * alias to setComponent
  818. *
  819. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  820. * @since 1.x.x - 2007-04-24
  821. * @param object $component calendar component
  822. * @return void
  823. */
  824. function addComponent( $component ) {
  825. $this->setComponent( $component );
  826. }
  827. /**
  828. * delete calendar component from container
  829. *
  830. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  831. * @since 2.8.8 - 2011-03-15
  832. * @param mixed $arg1 ordno / component type / component uid
  833. * @param mixed $arg2 optional, ordno if arg1 = component type
  834. * @return void
  835. */
  836. function deleteComponent( $arg1, $arg2=FALSE ) {
  837. $argType = $index = null;
  838. if ( ctype_digit( (string) $arg1 )) {
  839. $argType = 'INDEX';
  840. $index = (int) $arg1 - 1;
  841. }
  842. elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
  843. $argType = strtolower( $arg1 );
  844. $index = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
  845. }
  846. $cix1dC = 0;
  847. foreach ( $this->components as $cix => $component) {
  848. if( empty( $component )) continue;
  849. if(( 'INDEX' == $argType ) && ( $index == $cix )) {
  850. unset( $this->components[$cix] );
  851. return TRUE;
  852. }
  853. elseif( $argType == $component->objName ) {
  854. if( $index == $cix1dC ) {
  855. unset( $this->components[$cix] );
  856. return TRUE;
  857. }
  858. $cix1dC++;
  859. }
  860. elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
  861. unset( $this->components[$cix] );
  862. return TRUE;
  863. }
  864. }
  865. return FALSE;
  866. }
  867. /**
  868. * get calendar component from container
  869. *
  870. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  871. * @since 2.18.10 - 2013-09-04
  872. * @param mixed $arg1 optional, ordno/component type/ component uid
  873. * @param mixed $arg2 optional, ordno if arg1 = component type
  874. * @return object
  875. */
  876. function getComponent( $arg1=FALSE, $arg2=FALSE ) {
  877. $index = $argType = null;
  878. if ( !$arg1 ) { // first or next in component chain
  879. $argType = 'INDEX';
  880. $index = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
  881. }
  882. elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
  883. $arg2 = implode( '-', array_keys( $arg1 ));
  884. $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
  885. $dateProps = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
  886. $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
  887. $mProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
  888. }
  889. elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
  890. $argType = 'INDEX';
  891. $index = (int) $arg1;
  892. unset( $this->compix );
  893. }
  894. elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
  895. unset( $this->compix['INDEX'] );
  896. $argType = strtolower( $arg1 );
  897. if( !$arg2 )
  898. $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
  899. elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
  900. $index = (int) $arg2;
  901. }
  902. elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
  903. if( !$arg2 )
  904. $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
  905. elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
  906. $index = (int) $arg2;
  907. }
  908. if( isset( $index ))
  909. $index -= 1;
  910. $ckeys = array_keys( $this->components );
  911. if( !empty( $index) && ( $index > end( $ckeys )))
  912. return FALSE;
  913. $cix1gC = 0;
  914. foreach ( $this->components as $cix => $component) {
  915. if( empty( $component )) continue;
  916. if(( 'INDEX' == $argType ) && ( $index == $cix ))
  917. return $component->copy();
  918. elseif( $argType == $component->objName ) {
  919. if( $index == $cix1gC )
  920. return $component->copy();
  921. $cix1gC++;
  922. }
  923. elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
  924. $hit = array();
  925. $arg1 = array_change_key_case( $arg1, CASE_UPPER );
  926. foreach( $arg1 as $pName => $pValue ) {
  927. if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
  928. continue;
  929. if( in_array( $pName, $mProps )) { // multiple occurrence
  930. $propValues = array();
  931. $component->_getProperties( $pName, $propValues );
  932. $propValues = array_keys( $propValues );
  933. $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
  934. continue;
  935. } // end if(.. .// multiple occurrence
  936. if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
  937. $hit[] = FALSE; // missing property
  938. continue;
  939. }
  940. if( 'SUMMARY' == $pName ) { // exists within (any case)
  941. $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
  942. continue;
  943. }
  944. if( in_array( $pName, $dateProps )) {
  945. $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
  946. if( 8 < strlen( $pValue )) {
  947. if( isset( $value['hour'] )) {
  948. if( 'T' == substr( $pValue, 8, 1 ))
  949. $pValue = str_replace( 'T', '', $pValue );
  950. $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
  951. }
  952. else
  953. $pValue = substr( $pValue, 0, 8 );
  954. }
  955. $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
  956. continue;
  957. }
  958. elseif( !is_array( $value ))
  959. $value = array( $value );
  960. foreach( $value as $part ) {
  961. $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
  962. foreach( $part as $subPart ) {
  963. if( $pValue == $subPart ) {
  964. $hit[] = TRUE;
  965. continue 3;
  966. }
  967. }
  968. } // end foreach( $value as $part )
  969. $hit[] = FALSE; // no hit in property
  970. } // end foreach( $arg1 as $pName => $pValue )
  971. if( in_array( TRUE, $hit )) {
  972. if( $index == $cix1gC )
  973. return $component->copy();
  974. $cix1gC++;
  975. }
  976. } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
  977. elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
  978. if( $index == $cix1gC )
  979. return $component->copy();
  980. $cix1gC++;
  981. }
  982. } // end foreach ( $this->components.. .
  983. /* not found.. . */
  984. unset( $this->compix );
  985. return FALSE;
  986. }
  987. /**
  988. * create new calendar component, already included within calendar
  989. *
  990. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  991. * @since 2.6.33 - 2011-01-03
  992. * @param string $compType component type
  993. * @return object (reference)
  994. */
  995. function & newComponent( $compType ) {
  996. $config = $this->getConfig();
  997. $keys = array_keys( $this->components );
  998. $ix = end( $keys) + 1;
  999. switch( strtoupper( $compType )) {
  1000. case 'EVENT':
  1001. case 'VEVENT':
  1002. $this->components[$ix] = new vevent( $config );
  1003. break;
  1004. case 'TODO':
  1005. case 'VTODO':
  1006. $this->components[$ix] = new vtodo( $config );
  1007. break;
  1008. case 'JOURNAL':
  1009. case 'VJOURNAL':
  1010. $this->components[$ix] = new vjournal( $config );
  1011. break;
  1012. case 'FREEBUSY':
  1013. case 'VFREEBUSY':
  1014. $this->components[$ix] = new vfreebusy( $config );
  1015. break;
  1016. case 'TIMEZONE':
  1017. case 'VTIMEZONE':
  1018. array_unshift( $this->components, new vtimezone( $config ));
  1019. $ix = 0;
  1020. break;
  1021. default:
  1022. return FALSE;
  1023. }
  1024. return $this->components[$ix];
  1025. }
  1026. /**
  1027. * select components from calendar on date or selectOption basis
  1028. *
  1029. * Ensure DTSTART is set for every component.
  1030. * No date controls occurs.
  1031. *
  1032. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1033. * @since 2.18.19 - 2014-02-01
  1034. * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
  1035. * @param int $startM optional, start Month, default current Month
  1036. * @param int $startD optional, start Day, default current Day
  1037. * @param int $endY optional, end Year, default $startY
  1038. * @param int $endY optional, end Month, default $startM
  1039. * @param int $endY optional, end Day, default $startD
  1040. * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s)
  1041. * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][]
  1042. * TRUE => output : array[] (ignores split)
  1043. * @param bool $any optional, TRUE (default) - select component(-s) that occurs within period
  1044. * FALSE - only component(-s) that starts within period
  1045. * @param bool $split optional, TRUE (default) - one component copy every DAY it occurs during the
  1046. * period (implies flat=FALSE)
  1047. * FALSE - one occurance of component only in output array
  1048. * @return array or FALSE
  1049. */
  1050. function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
  1051. /* check if empty calendar */
  1052. if( 0 >= count( $this->components )) return FALSE;
  1053. if( is_array( $startY ))
  1054. return $this->selectComponents2( $startY );
  1055. /* check default dates */
  1056. if( ! $startY ) $startY = date( 'Y' );
  1057. if( ! $startM ) $startM = date( 'm' );
  1058. if( ! $startD ) $startD = date( 'd' );
  1059. $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
  1060. if( ! $endY ) $endY = $startY;
  1061. if( ! $endM ) $endM = $startM;
  1062. if( ! $endD ) $endD = $startD;
  1063. $endDate = mktime( 23, 59, 59, $endM, $endD, $endY );
  1064. // echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br>\n"; $tcnt = 0;// test ###
  1065. /* check component types */
  1066. $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
  1067. if( empty( $cType ))
  1068. $cType = $validTypes;
  1069. else {
  1070. if( ! is_array( $cType ))
  1071. $cType = array( $cType );
  1072. $cType = array_map( 'strtolower', $cType );
  1073. foreach( $cType as $cix => $theType ) {
  1074. $cType[$cix] = $theType;
  1075. if( !in_array( $theType, $validTypes ))
  1076. $cType[$cix] = 'vevent';
  1077. }
  1078. $cType = array_unique( $cType );
  1079. }
  1080. if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
  1081. $split = FALSE;
  1082. if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
  1083. $split = FALSE;
  1084. /* iterate components */
  1085. $result = array();
  1086. $this->sort( 'UID' );
  1087. $compUIDcmp = null;
  1088. $recurridList = array();
  1089. foreach ( $this->components as $cix => $component ) {
  1090. if( empty( $component )) continue;
  1091. unset( $start );
  1092. /* deselect unvalid type components */
  1093. if( !in_array( $component->objName, $cType ))
  1094. continue;
  1095. $start = $component->getProperty( 'dtstart', FALSE, TRUE );
  1096. /* select due when dtstart is missing */
  1097. if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due', FALSE, TRUE ))))
  1098. continue;
  1099. if( empty( $start ))
  1100. continue;
  1101. if( ! isset( $start['value']['tz'] ) && isset( $start['params']['TZID'] ))
  1102. $start['value']['tz'] = $start['params']['TZID'];
  1103. $start = $start['value'];
  1104. $compUID = $component->getProperty( 'UID' );
  1105. if( $compUIDcmp != $compUID ) {
  1106. $compUIDcmp = $compUID;
  1107. unset( $exdatelist, $recurridList );
  1108. }
  1109. $SCbools = array( 'dtendExist' => FALSE, 'dueExist' => FALSE, 'durationExist' => FALSE, 'endAllDayEvent' => FALSE );
  1110. $recurrid = FALSE;
  1111. $dateFormat = array();
  1112. unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend ); // clean up
  1113. $startWdate = iCalUtilityFunctions::_SCsetXCurrentDateZ( iCalUtilityFunctions::_date2timestamp( $start ), $start );
  1114. $dateFormat['start'] = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
  1115. /* get end date from dtend/due/duration properties */
  1116. $end = $component->getProperty( 'dtend', FALSE, TRUE );
  1117. if( !empty( $end )) {
  1118. $SCbools[ 'dtendExist'] = TRUE;
  1119. $dateFormat['end'] = ( isset( $end['value']['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
  1120. }
  1121. if( ! isset( $end['value']['tz'] ) && isset( $end['params']['TZID'] ))
  1122. $end['value']['tz'] = $end['params']['TZID'];
  1123. $end = $end['value'];
  1124. if( empty( $end ) && ( $component->objName == 'vtodo' )) {
  1125. $end = $component->getProperty( 'due' );
  1126. if( !empty( $end )) {
  1127. $SCbools[ 'dueExist'] = TRUE;
  1128. $dateFormat['end'] = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
  1129. }
  1130. }
  1131. if( !empty( $end ) && !isset( $end['hour'] )) {
  1132. /* a DTEND without time part regards an event that ends the day before,
  1133. for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
  1134. $SCbools[ 'endAllDayEvent'] = TRUE;
  1135. $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
  1136. $end['year'] = date( 'Y', $endWdate );
  1137. $end['month'] = date( 'm', $endWdate );
  1138. $end['day'] = date( 'd', $endWdate );
  1139. $end['hour'] = 23;
  1140. $end['min'] = $end['sec'] = 59;
  1141. }
  1142. if( empty( $end )) {
  1143. $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
  1144. if( !empty( $end ))
  1145. if( isset( $start['tz'] ))
  1146. $end['tz'] = $start['tz'];
  1147. $SCbools[ 'durationExist'] = TRUE;
  1148. $dateFormat['end'] = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
  1149. // if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ###
  1150. }
  1151. if( empty( $end )) { // assume one day duration if missing end date
  1152. $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
  1153. if( isset( $start['tz'] ))
  1154. $end['tz'] = $start['tz'];
  1155. }
  1156. // if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ###
  1157. $endWdate = iCalUtilityFunctions::_SCsetXCurrentDateZ( iCalUtilityFunctions::_date2timestamp( $end ), $end );
  1158. if( $endWdate < $startWdate ) { // MUST be after start date!!
  1159. $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
  1160. $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
  1161. }
  1162. $rdurWsecs = $endWdate - $startWdate; // compute event (component) duration in seconds
  1163. /* make a list of optional exclude dates for component occurence from exrule and exdate */
  1164. $exdatelist = array();
  1165. $workstart = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
  1166. $workend = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
  1167. while( FALSE !== ( $exrule = $component->getProperty( 'exrule' ))) // check exrule
  1168. iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
  1169. while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) { // check exdate
  1170. foreach( $exdate as $theExdate ) {
  1171. $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
  1172. $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
  1173. if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
  1174. $exdatelist[$exWdate] = TRUE;
  1175. } // end - foreach( $exdate as $theExdate )
  1176. } // end - check exdate
  1177. /* check recurrence-id (note, a missing sequence is the same as sequence=0 so don't test for sequence), remove hit with reccurr-id date */
  1178. if( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) {
  1179. $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
  1180. $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
  1181. $recurridList[$recurrid] = TRUE; // no recurring to start this day
  1182. // echo "adding comp no:$cix with date=".implode($start)." and recurrid=".implode($recurrid)." to recurridList id=$recurrid<br>\n"; // test ###
  1183. } // end recurrence-id/sequence test
  1184. /* select only components with.. . */
  1185. if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
  1186. ( $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) { // occurs within the period
  1187. /* add the selected component (WITHIN valid dates) to output array */
  1188. if( $flat ) { // any=true/false, ignores split
  1189. if( !$recurrid )
  1190. $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
  1191. }
  1192. elseif( $split ) { // split the original component
  1193. if( $endWdate > $endDate )
  1194. $endWdate = $endDate; // use period end date
  1195. $rstart = ( $startWdate < $startDate ) ? $startDate : $startWdate; // use period start date
  1196. $startYMD = $rstartYMD = date( 'Ymd', $rstart );
  1197. $endYMD = date( 'Ymd', $endWdate );
  1198. $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
  1199. // echo "going to test comp no:$cix with rstartYMD=$rstartYMD, endYMD=$endYMD and checkDate($checkDate) with recurridList=".implode(',',array_keys($recurridList))."<br>\n"; // test ###
  1200. if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
  1201. while( $rstartYMD <= $endYMD ) { // iterate
  1202. if( isset( $exdatelist[$checkDate] ) || // exclude any recurrence date, found in the exdatelist
  1203. ( isset( $recurridList[$checkDate] ) && !$recurrid )) { // or in the recurridList, but not itself
  1204. // echo "skipping comp no:$cix with datestart=$rstartYMD and checkdate=$checkDate<br>\n"; // test ###
  1205. $rstart += ( 24 *3600 ); // step one day
  1206. $rstartYMD = date( 'Ymd', $rstart );
  1207. continue;
  1208. }
  1209. iCalUtilityFunctions::_SCsetXCurrentStart( $component, $dateFormat, $checkDate, $rstartYMD, $rstart, $startYMD, $start );
  1210. iCalUtilityFunctions::_SCsetXCurrentEnd( $component, $dateFormat, $rstart, $rstartYMD, $endWdate, $endYMD, $end, $SCbools );
  1211. $wd = getdate( $rstart );
  1212. $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
  1213. $rstart += ( 24 *3600 ); // step one day
  1214. $rstartYMD = date( 'Ymd', $rstart );
  1215. $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
  1216. } // end while( $rstart <= $endWdate )
  1217. } // end if( !isset( $exdatelist[$checkDate] ))
  1218. } // end elseif( $split ) - else use component date
  1219. elseif( $recurrid && !$flat && !$any && !$split )
  1220. $continue = TRUE;
  1221. else { // !$flat && !$split, i.e. no flat array and DTSTART within period
  1222. $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
  1223. // echo "going to test comp no:$cix with checkDate=$checkDate with recurridList=".implode(',',array_keys($recurridList)); // test ###
  1224. if(( !$any || !isset( $exdatelist[$checkDate] )) && // exclude any recurrence date, found in exdatelist
  1225. ( !isset( $recurridList[$checkDate] ) || $recurrid )) { // or in the recurridList, but not itself
  1226. // echo " and copied to output<br>\n"; // test ###
  1227. $wd = getdate( $startWdate );
  1228. $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
  1229. }
  1230. }
  1231. } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
  1232. /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
  1233. if( TRUE === $any ) {
  1234. /* make a list of optional repeating dates for component occurence, rrule, rdate */
  1235. $recurlist = array();
  1236. while( FALSE !== ( $rrule = $component->getProperty( 'rrule' ))) // check rrule
  1237. iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
  1238. foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
  1239. $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
  1240. while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) { // check rdate
  1241. foreach( $rdate as $theRdate ) {
  1242. if( is_array( $theRdate ) && ( 2 == count( $theRdate )) && // all days within PERIOD
  1243. array_key_exists( '0', $theRdate ) && array_key_exists( '1', $theRdate )) {
  1244. $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
  1245. if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
  1246. continue;
  1247. if( isset( $theRdate[1]['year'] )) // date-date period
  1248. $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
  1249. else { // date-duration period
  1250. $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
  1251. $rend = iCalUtilityFunctions::_date2timestamp( $rend );
  1252. }
  1253. while( $rstart < $rend ) {
  1254. $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
  1255. $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
  1256. }
  1257. } // PERIOD end
  1258. else { // single date
  1259. $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
  1260. if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
  1261. $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
  1262. }
  1263. }
  1264. } // end - check rdate
  1265. foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
  1266. $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
  1267. if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
  1268. unset( $recurlist[$recurkey] );
  1269. }
  1270. if( 0 < count( $recurlist )) {
  1271. ksort( $recurlist );
  1272. $xRecurrence = 1;
  1273. $component2 = $component->copy();
  1274. $compUID = $component2->getProperty( 'UID' );
  1275. foreach( $recurlist as $recurkey => $durvalue ) {
  1276. // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br>\n"; // test ###;
  1277. if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
  1278. continue;
  1279. $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
  1280. if( isset( $recurridList[$checkDate] )) // no recurring to start this day
  1281. continue;
  1282. if( isset( $exdatelist[$checkDate] )) // check excluded dates
  1283. continue;
  1284. if( $startWdate >= $recurkey ) // exclude component start date
  1285. continue;
  1286. $rstart = $recurkey;
  1287. $rend = $recurkey + $durvalue;
  1288. /* add repeating components within valid dates to output array, only start date set */
  1289. if( $flat ) {
  1290. if( !isset( $result[$compUID] )) // only one comp
  1291. $result[$compUID] = $component2->copy(); // copy to output
  1292. }
  1293. /* add repeating components within valid dates to output array, one each day */
  1294. elseif( $split ) {
  1295. $xRecurrence += 1;
  1296. if( $rend > $endDate )
  1297. $rend = $endDate;
  1298. $startYMD = $rstartYMD = date( 'Ymd', $rstart );
  1299. $endYMD = date( 'Ymd', $rend );
  1300. // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br>\n"; // test ###;
  1301. while( $rstartYMD <= $endYMD ) { // iterate.. .
  1302. $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
  1303. if( isset( $recurridList[$checkDate] )) // no recurring to start this day
  1304. break;
  1305. if( isset( $exdatelist[$checkDate] )) // exclude any recurrence START date, found in exdatelist
  1306. break;
  1307. // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br>"; // test ###;
  1308. if( $rstart >= $startDate ) { // date after dtstart
  1309. iCalUtilityFunctions::_SCsetXCurrentStart( $component2, $dateFormat, $checkDate, $rstartYMD, $rstart, $startYMD, $start );
  1310. iCalUtilityFunctions::_SCsetXCurrentEnd( $component2, $dateFormat, $rstart, $rstartYMD, $endWdate, $endYMD, $end, $SCbools );
  1311. $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
  1312. $wd = getdate( $rstart );
  1313. $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
  1314. } // end if$rstart >= $startDate { // date after dtstart
  1315. $rstart += ( 24 *3600 ); // step one day
  1316. $rstartYMD = date( 'Ymd', $rstart );
  1317. } // end while( $rstart <= $rend )
  1318. } // end elseif( $split )
  1319. elseif( $rstart >= $startDate ) { // date within period //* flat=FALSE && split=FALSE => one comp every recur startdate *//
  1320. $xRecurrence += 1;
  1321. $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
  1322. if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
  1323. iCalUtilityFunctions::_SCsetXCurrentStart( $component2, $dateFormat, $rstart, FALSE, FALSE, FALSE, $start );
  1324. $tend = $rstart + $rdurWsecs;
  1325. iCalUtilityFunctions::_SCsetXCurrentEnd( $component2, $dateFormat, $tend, date( 'Ymd', $tend ), $endWdate, date( 'Ymd', $endWdate ), $end, $SCbools );
  1326. $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
  1327. $wd = getdate( $rstart );
  1328. $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
  1329. } // end if( !isset( $exdatelist[$checkDate] ))
  1330. } // end elseif( $rstart >= $startDate )
  1331. } // end foreach( $recurlist as $recurkey => $durvalue )
  1332. unset( $component2 );
  1333. } // end if( 0 < count( $recurlist ))
  1334. /* deselect components with startdate/enddate not within period */
  1335. if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
  1336. continue;
  1337. } // end if( TRUE === $any )
  1338. } // end foreach ( $this->components as $cix => $component )
  1339. unset( $SCbools, $recurrid, $recurridList,
  1340. $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $dateFormat ); // clean up
  1341. if( 0 >= count( $result ))
  1342. return FALSE;
  1343. elseif( !$flat ) {
  1344. foreach( $result as $y => $yeararr ) {
  1345. foreach( $yeararr as $m => $montharr ) {
  1346. foreach( $montharr as $d => $dayarr ) {
  1347. if( empty( $result[$y][$m][$d] ))
  1348. unset( $result[$y][$m][$d] );
  1349. else {
  1350. $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index
  1351. if( 1 < count( $result[$y][$m][$d] )) {
  1352. foreach( $result[$y][$m][$d] as & $c ) // sort
  1353. iCalUtilityFunctions::_setSortArgs( $c );
  1354. usort( $result[$y][$m][$d], array( 'iCalUtilityFunctions', '_cmpfcn' ));
  1355. }
  1356. }
  1357. } // end foreach( $montharr as $d => $dayarr )
  1358. if( empty( $result[$y][$m] ))
  1359. unset( $result[$y][$m] );
  1360. else
  1361. ksort( $result[$y][$m] );
  1362. } // end foreach( $yeararr as $m => $montharr )
  1363. if( empty( $result[$y] ))
  1364. unset( $result[$y] );
  1365. else
  1366. ksort( $result[$y] );
  1367. }// end foreach( $result as $y => $yeararr )
  1368. if( empty( $result ))
  1369. unset( $result );
  1370. else
  1371. ksort( $result );
  1372. } // end elseif( !$flat )
  1373. if( 0 >= count( $result ))
  1374. return FALSE;
  1375. return $result;
  1376. }
  1377. /**
  1378. * select components from calendar on based on specific property value(-s)
  1379. *
  1380. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1381. * @since 2.16.6 - 2012-12-26
  1382. * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
  1383. * @return array
  1384. */
  1385. function selectComponents2( $selectOptions ) {
  1386. $output = array();
  1387. $allowedComps = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
  1388. $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
  1389. $selectOptions = array_change_key_case( $selectOptions, CASE_UPPER );
  1390. foreach( $this->components as $cix => $component3 ) {
  1391. if( !in_array( $component3->objName, $allowedComps ))
  1392. continue;
  1393. $uid = $component3->getProperty( 'UID' );
  1394. foreach( $selectOptions as $propName => $pvalue ) {
  1395. if( !in_array( $propName, $allowedProperties ))
  1396. continue;
  1397. if( !is_array( $pvalue ))
  1398. $pvalue = array( $pvalue );
  1399. if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
  1400. $output[$uid][] = $component3->copy();
  1401. continue;
  1402. }
  1403. elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
  1404. $propValues = array();
  1405. $component3->_getProperties( $propName, $propValues );
  1406. $propValues = array_keys( $propValues );
  1407. foreach( $pvalue as $theValue ) {
  1408. if( in_array( $theValue, $propValues )) { // && !isset( $output[$uid] )) {
  1409. $output[$uid][] = $component3->copy();
  1410. break;
  1411. }
  1412. }
  1413. continue;
  1414. } // end elseif( // multiple occurrence?
  1415. elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
  1416. continue;
  1417. if( is_array( $d )) {
  1418. foreach( $d as $part ) {
  1419. if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
  1420. $output[$uid][] = $component3->copy();
  1421. }
  1422. }
  1423. elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
  1424. foreach( $pvalue as $pval ) {
  1425. if( FALSE !== stripos( $d, $pval )) {
  1426. $output[$uid][] = $component3->copy();
  1427. break;
  1428. }
  1429. }
  1430. }
  1431. elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
  1432. $output[$uid][] = $component3->copy();
  1433. } // end foreach( $selectOptions as $propName => $pvalue ) {
  1434. } // end foreach( $this->components as $cix => $component3 ) {
  1435. if( !empty( $output )) {
  1436. ksort( $output ); // uid order
  1437. $output2 = array();
  1438. foreach( $output as $uid => $components ) {
  1439. foreach( $components as $component )
  1440. $output2[] = $component;
  1441. }
  1442. $output = $output2;
  1443. }
  1444. return $output;
  1445. }
  1446. /**
  1447. * replace calendar component in vcalendar
  1448. *
  1449. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1450. * @since 2.18.14 - 2014-03-30
  1451. * @param object $component calendar component
  1452. * @return bool
  1453. */
  1454. function replaceComponent( $component ) {
  1455. if( in_array( $component->objName, array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
  1456. return $this->setComponent( $component, $component->getProperty( 'UID' ));
  1457. if(( 'vtimezone' != $component->objName ) || ( FALSE === ( $tzid = $component->getProperty( 'TZID' ))))
  1458. return FALSE;
  1459. foreach( $this->components as $cix => $comp ) {
  1460. if( 'vtimezone' != $component->objName )
  1461. continue;
  1462. if( $tzid == $comp->getComponent( 'TZID' )) {
  1463. unset( $component->propix, $component->compix );
  1464. $this->components[$cix] = $component;
  1465. return TRUE;
  1466. }
  1467. }
  1468. return FALSE;
  1469. }
  1470. /**
  1471. * add calendar component to calendar
  1472. *
  1473. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1474. * @since 2.19.3 - 2014-03-30
  1475. * @param object $component calendar component
  1476. * @param mixed $arg1 optional, ordno/component type/ component uid
  1477. * @param mixed $arg2 optional, ordno if arg1 = component type
  1478. * @return bool
  1479. */
  1480. function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) {
  1481. $component->setConfig( $this->getConfig(), FALSE, TRUE );
  1482. if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
  1483. /* make sure dtstamp and uid is set */
  1484. $dummy1 = $component->getProperty( 'dtstamp' );
  1485. $dummy2 = $component->getProperty( 'uid' );
  1486. }
  1487. unset( $component->propix, $component->compix );
  1488. if( !$arg1 ) { // plain insert, last in chain
  1489. $this->components[] = $component->copy();
  1490. return TRUE;
  1491. }
  1492. $argType = $index = null;
  1493. if ( ctype_digit( (string) $arg1 )) { // index insert/replace
  1494. $argType = 'INDEX';
  1495. $index = (int) $arg1 - 1;
  1496. }
  1497. elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
  1498. $argType = strtolower( $arg1 );
  1499. $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
  1500. }
  1501. // else if arg1 is set, arg1 must be an UID
  1502. $cix1sC = 0;
  1503. foreach ( $this->components as $cix => $component2) {
  1504. if( empty( $component2 )) continue;
  1505. if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
  1506. $this->components[$cix] = $component->copy();
  1507. return TRUE;
  1508. }
  1509. elseif( $argType == $component2->objName ) { // component Type index insert/replace
  1510. if( $index == $cix1sC ) {
  1511. $this->components[$cix] = $component->copy();
  1512. return TRUE;
  1513. }
  1514. $cix1sC++;
  1515. }
  1516. elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
  1517. $this->components[$cix] = $component->copy();
  1518. return TRUE;
  1519. }
  1520. }
  1521. /* arg1=index and not found.. . insert at index .. .*/
  1522. if( 'INDEX' == $argType ) {
  1523. $this->components[$index] = $component->copy();
  1524. ksort( $this->components, SORT_NUMERIC );
  1525. }
  1526. else /* not found.. . insert last in chain anyway .. .*/
  1527. $this->components[] = $component->copy();
  1528. return TRUE;
  1529. }
  1530. /**
  1531. * sort iCal compoments
  1532. *
  1533. * ascending sort on properties (if exist) x-current-dtstart, dtstart,
  1534. * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
  1535. * otherwise sorting on specific (argument) property values
  1536. *
  1537. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1538. * @since 2.18.4 - 2013-08-18
  1539. * @param string $sortArg, optional
  1540. * @uses iCalUtilityFunctions::_setSortArgs()
  1541. * @uses iCalUtilityFunctions::_cmpfcn()
  1542. * @return void
  1543. */
  1544. function sort( $sortArg=FALSE ) {
  1545. if( ! is_array( $this->components ) || ( 2 > count( $this->components )))
  1546. return;
  1547. if( $sortArg ) {
  1548. $sortArg = strtoupper( $sortArg );
  1549. if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
  1550. $sortArg = FALSE;
  1551. }
  1552. foreach( $this->components as & $c )
  1553. iCalUtilityFunctions::_setSortArgs( $c, $sortArg );
  1554. usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
  1555. }
  1556. /**
  1557. * parse iCal text/file into vcalendar, components, properties and parameters
  1558. *
  1559. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1560. * @since 2.18.16 - 2014-04-04
  1561. * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
  1562. * @return bool FALSE if error occurs during parsing
  1563. */
  1564. function parse( $unparsedtext=FALSE ) {
  1565. $nl = $this->getConfig( 'nl' );
  1566. if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
  1567. /* directory+filename is set previously via setConfig url or directory+filename */
  1568. if( FALSE === ( $file = $this->getConfig( 'url' ))) {
  1569. if( FALSE === ( $file = $this->getConfig( 'dirfile' )))
  1570. return FALSE; /* err 1 */
  1571. if( ! is_file( $file ))
  1572. return FALSE; /* err 2 */
  1573. if( ! is_readable( $file ))
  1574. return FALSE; /* err 3 */
  1575. }
  1576. /* READ FILE */
  1577. if( FALSE === ( $rows = file_get_contents( $file )))
  1578. return FALSE; /* err 5 */
  1579. }
  1580. elseif( is_array( $unparsedtext ))
  1581. $rows = implode( '\n'.$nl, $unparsedtext );
  1582. else
  1583. $rows = & $unparsedtext;
  1584. /* fix line folding */
  1585. $rows = iCalUtilityFunctions::convEolChar( $rows, $nl );
  1586. /* skip leading (empty/invalid) lines */
  1587. foreach( $rows as $lix => $line ) {
  1588. if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
  1589. break;
  1590. unset( $rows[$lix] );
  1591. }
  1592. $rcnt = count( $rows );
  1593. if( 3 > $rcnt ) /* err 10 */
  1594. return FALSE;
  1595. /* skip trailing empty lines and ensure an end row */
  1596. $lix = array_keys( $rows );
  1597. $lix = end( $lix );
  1598. while( 3 < $lix ) {
  1599. $tst = trim( $rows[$lix] );
  1600. if(( '\n' == $tst ) || empty( $tst )) {
  1601. unset( $rows[$lix] );
  1602. $lix--;
  1603. continue;
  1604. }
  1605. if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
  1606. $rows[] = 'END:VCALENDAR';
  1607. break;
  1608. }
  1609. $comp = & $this;
  1610. $calsync = $compsync = 0;
  1611. /* identify components and update unparsed data within component */
  1612. $config = $this->getConfig();
  1613. $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
  1614. foreach( $rows as $lix => $line ) {
  1615. if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
  1616. $calsync++;
  1617. continue;
  1618. }
  1619. elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) {
  1620. if( 0 < $compsync )
  1621. $this->components[] = $comp->copy();
  1622. $compsync--;
  1623. $calsync--;
  1624. break;
  1625. }
  1626. elseif( 1 != $calsync )
  1627. return FALSE; /* err 20 */
  1628. elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
  1629. $this->components[] = $comp->copy();
  1630. $compsync--;
  1631. continue;
  1632. }
  1633. if( 'BEGIN:VEVENT' == strtoupper( substr( $line, 0, 12 ))) {
  1634. $comp = new vevent( $config );
  1635. $compsync++;
  1636. }
  1637. elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
  1638. $comp = new vfreebusy( $config );
  1639. $compsync++;
  1640. }
  1641. elseif( 'BEGIN:VJOURNAL' == strtoupper( substr( $line, 0, 14 ))) {
  1642. $comp = new vjournal( $config );
  1643. $compsync++;
  1644. }
  1645. elseif( 'BEGIN:VTODO' == strtoupper( substr( $line, 0, 11 ))) {
  1646. $comp = new vtodo( $config );
  1647. $compsync++;
  1648. }
  1649. elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
  1650. $comp = new vtimezone( $config );
  1651. $compsync++;
  1652. }
  1653. else { /* update component with unparsed data */
  1654. $comp->unparsed[] = $line;
  1655. }
  1656. } // end foreach( $rows as $line )
  1657. unset( $config, $endtxt );
  1658. /* parse data for calendar (this) object */
  1659. if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
  1660. /* concatenate property values spread over several lines */
  1661. $propnames = array( 'calscale','method','prodid','version','x-' );
  1662. $proprows = array();
  1663. for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
  1664. $line = rtrim( $this->unparsed[$i], $nl );
  1665. while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
  1666. $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
  1667. $proprows[] = $line;
  1668. }
  1669. foreach( $proprows as $line ) {
  1670. if( '\n' == substr( $line, -2 ))
  1671. $line = substr( $line, 0, -2 );
  1672. /* get property name */
  1673. $propname = '';
  1674. $cix = 0;
  1675. while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
  1676. if( in_array( $char, array( ':', ';' )))
  1677. break;
  1678. else
  1679. $propname .= $char;
  1680. $cix++;
  1681. }
  1682. /* skip non standard property names */
  1683. if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
  1684. continue;
  1685. /* ignore version/prodid properties */
  1686. if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
  1687. continue;
  1688. /* rest of the line is opt.params and value */
  1689. $line = substr( $line, $cix);
  1690. /* separate attributes from value */
  1691. iCalUtilityFunctions::_splitContent( $line, $propAttr );
  1692. /* update Property */
  1693. if( FALSE !== strpos( $line, ',' )) {
  1694. $content = array( 0 => '' );
  1695. $cix = $lix = 0;
  1696. while( FALSE !== substr( $line, $lix, 1 )) {
  1697. if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
  1698. $cix++;
  1699. $content[$cix] = '';
  1700. }
  1701. else
  1702. $content[$cix] .= $line[$lix];
  1703. $lix++;
  1704. }
  1705. if( 1 < count( $content )) {
  1706. foreach( $content as $cix => $contentPart )
  1707. $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
  1708. $this->setProperty( $propname, $content, $propAttr );
  1709. continue;
  1710. }
  1711. else
  1712. $line = reset( $content );
  1713. $line = iCalUtilityFunctions::_strunrep( $line );
  1714. }
  1715. $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propAttr );
  1716. } // end - foreach( $this->unparsed.. .
  1717. } // end - if( is_array( $this->unparsed.. .
  1718. unset( $unparsedtext, $rows, $this->unparsed, $proprows );
  1719. /* parse Components */
  1720. if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
  1721. $ckeys = array_keys( $this->components );
  1722. foreach( $ckeys as $ckey ) {
  1723. if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
  1724. $this->components[$ckey]->parse();
  1725. }
  1726. }
  1727. }
  1728. else
  1729. return FALSE; /* err 91 or something.. . */
  1730. return TRUE;
  1731. }
  1732. /*********************************************************************************/
  1733. /**
  1734. * creates formatted output for calendar object instance
  1735. *
  1736. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1737. * @since 2.10.16 - 2011-10-28
  1738. * @return string
  1739. */
  1740. function createCalendar() {
  1741. $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
  1742. switch( $this->format ) {
  1743. case 'xcal':
  1744. $calendarInit = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
  1745. '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
  1746. '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
  1747. $calendarStart = '>'.$this->nl.'<vcalendar';
  1748. break;
  1749. default:
  1750. $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
  1751. break;
  1752. }
  1753. $calendarStart .= $this->createVersion();
  1754. $calendarStart .= $this->createProdid();
  1755. $calendarStart .= $this->createCalscale();
  1756. $calendarStart .= $this->createMethod();
  1757. if( 'xcal' == $this->format )
  1758. $calendarStart .= '>'.$this->nl;
  1759. $calendar .= $this->createXprop();
  1760. foreach( $this->components as $component ) {
  1761. if( empty( $component )) continue;
  1762. $component->setConfig( $this->getConfig(), FALSE, TRUE );
  1763. $calendar .= $component->createComponent( $this->xcaldecl );
  1764. }
  1765. if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
  1766. $calendarInit .= ' [';
  1767. $old_xcaldecl = array();
  1768. foreach( $this->xcaldecl as $declix => $declPart ) {
  1769. if(( 0 < count( $old_xcaldecl)) &&
  1770. isset( $declPart['uri'] ) && isset( $declPart['external'] ) &&
  1771. isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
  1772. ( in_array( $declPart['uri'], $old_xcaldecl['uri'] )) &&
  1773. ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
  1774. continue; // no duplicate uri and ext. references
  1775. if(( 0 < count( $old_xcaldecl)) &&
  1776. !isset( $declPart['uri'] ) && !isset( $declPart['uri'] ) &&
  1777. isset( $declPart['ref'] ) && isset( $old_xcaldecl['ref'] ) &&
  1778. ( in_array( $declPart['ref'], $old_xcaldecl['ref'] )))
  1779. continue; // no duplicate element declarations
  1780. $calendarxCaldecl .= $this->nl.'<!';
  1781. foreach( $declPart as $declKey => $declValue ) {
  1782. switch( $declKey ) { // index
  1783. case 'xmldecl': // no 1
  1784. $calendarxCaldecl .= $declValue.' ';
  1785. break;
  1786. case 'uri': // no 2
  1787. $calendarxCaldecl .= $declValue.' ';
  1788. $old_xcaldecl['uri'][] = $declValue;
  1789. break;
  1790. case 'ref': // no 3
  1791. $calendarxCaldecl .= $declValue.' ';
  1792. $old_xcaldecl['ref'][] = $declValue;
  1793. break;
  1794. case 'external': // no 4
  1795. $calendarxCaldecl .= '"'.$declValue.'" ';
  1796. $old_xcaldecl['external'][] = $declValue;
  1797. break;
  1798. case 'type': // no 5
  1799. $calendarxCaldecl .= $declValue.' ';
  1800. break;
  1801. case 'type2': // no 6
  1802. $calendarxCaldecl .= $declValue;
  1803. break;
  1804. }
  1805. }
  1806. $calendarxCaldecl .= '>';
  1807. }
  1808. $calendarxCaldecl .= $this->nl.']';
  1809. }
  1810. switch( $this->format ) {
  1811. case 'xcal':
  1812. $calendar .= '</vcalendar>'.$this->nl;
  1813. break;
  1814. default:
  1815. $calendar .= 'END:VCALENDAR'.$this->nl;
  1816. break;
  1817. }
  1818. return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
  1819. }
  1820. /**
  1821. * a HTTP redirect header is sent with created, updated and/or parsed calendar
  1822. *
  1823. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1824. * @since 2.10.24 - 2011-12-23
  1825. * @param bool $utf8Encode
  1826. * @param bool $gzip
  1827. * @return redirect
  1828. */
  1829. function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
  1830. $filename = $this->getConfig( 'filename' );
  1831. $output = $this->createCalendar();
  1832. if( $utf8Encode )
  1833. $output = utf8_encode( $output );
  1834. if( $gzip ) {
  1835. $output = gzencode( $output, 9 );
  1836. header( 'Content-Encoding: gzip' );
  1837. header( 'Vary: *' );
  1838. header( 'Content-Length: '.strlen( $output ));
  1839. }
  1840. if( 'xcal' == $this->format )
  1841. header( 'Content-Type: application/calendar+xml; charset=utf-8' );
  1842. else
  1843. header( 'Content-Type: text/calendar; charset=utf-8' );
  1844. header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
  1845. header( 'Cache-Control: max-age=10' );
  1846. die( $output );
  1847. }
  1848. /**
  1849. * save content in a file
  1850. *
  1851. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1852. * @since 2.2.12 - 2007-12-30
  1853. * @param string $directory optional
  1854. * @param string $filename optional
  1855. * @param string $delimiter optional
  1856. * @return bool
  1857. */
  1858. function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
  1859. if( $directory )
  1860. $this->setConfig( 'directory', $directory );
  1861. if( $filename )
  1862. $this->setConfig( 'filename', $filename );
  1863. if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
  1864. $this->setConfig( 'delimiter', $delimiter );
  1865. if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
  1866. $dirfile = $this->getConfig( 'dirfile' );
  1867. $iCalFile = @fopen( $dirfile, 'w' );
  1868. if( $iCalFile ) {
  1869. if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
  1870. return FALSE;
  1871. fclose( $iCalFile );
  1872. return TRUE;
  1873. }
  1874. else
  1875. return FALSE;
  1876. }
  1877. /**
  1878. * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
  1879. * else FALSE is returned
  1880. *
  1881. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1882. * @since 2.2.12 - 2007-10-28
  1883. * @param string $directory optional alt. int timeout
  1884. * @param string $filename optional
  1885. * @param string $delimiter optional
  1886. * @param int timeout optional, default 3600 sec
  1887. * @return redirect/FALSE
  1888. */
  1889. function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
  1890. if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
  1891. $timeout = (int) $directory;
  1892. $directory = FALSE;
  1893. }
  1894. if( $directory )
  1895. $this->setConfig( 'directory', $directory );
  1896. if( $filename )
  1897. $this->setConfig( 'filename', $filename );
  1898. if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
  1899. $this->setConfig( 'delimiter', $delimiter );
  1900. $filesize = $this->getConfig( 'filesize' );
  1901. if( 0 >= $filesize )
  1902. return FALSE;
  1903. $dirfile = $this->getConfig( 'dirfile' );
  1904. if( time() - filemtime( $dirfile ) < $timeout) {
  1905. clearstatcache();
  1906. $dirfile = $this->getConfig( 'dirfile' );
  1907. $filename = $this->getConfig( 'filename' );
  1908. // if( headers_sent( $filename, $linenum ))
  1909. // die( "Headers already sent in $filename on line $linenum\n" );
  1910. if( 'xcal' == $this->format )
  1911. header( 'Content-Type: application/calendar+xml; charset=utf-8' );
  1912. else
  1913. header( 'Content-Type: text/calendar; charset=utf-8' );
  1914. header( 'Content-Length: '.$filesize );
  1915. header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
  1916. header( 'Cache-Control: max-age=10' );
  1917. $fp = @fopen( $dirfile, 'r' );
  1918. if( $fp ) {
  1919. fpassthru( $fp );
  1920. fclose( $fp );
  1921. }
  1922. die();
  1923. }
  1924. else
  1925. return FALSE;
  1926. }
  1927. }
  1928. /*********************************************************************************/
  1929. /*********************************************************************************/
  1930. /**
  1931. * abstract class for calendar components
  1932. *
  1933. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1934. * @since 2.9.6 - 2011-05-14
  1935. */
  1936. class calendarComponent {
  1937. // component property variables
  1938. var $uid;
  1939. var $dtstamp;
  1940. // component config variables
  1941. var $allowEmpty;
  1942. var $language;
  1943. var $nl;
  1944. var $unique_id;
  1945. var $format;
  1946. var $objName; // created automatically at instance creation
  1947. var $dtzid; // default (local) timezone
  1948. // component internal variables
  1949. var $componentStart1;
  1950. var $componentStart2;
  1951. var $componentEnd1;
  1952. var $componentEnd2;
  1953. var $elementStart1;
  1954. var $elementStart2;
  1955. var $elementEnd1;
  1956. var $elementEnd2;
  1957. var $intAttrDelimiter;
  1958. var $attributeDelimiter;
  1959. var $valueInit;
  1960. // component xCal declaration container
  1961. var $xcaldecl;
  1962. /**
  1963. * constructor for calendar component object
  1964. *
  1965. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1966. * @since 2.9.6 - 2011-05-17
  1967. */
  1968. function calendarComponent() {
  1969. $this->objName = ( isset( $this->timezonetype )) ?
  1970. strtolower( $this->timezonetype ) : get_class ( $this );
  1971. $this->uid = array();
  1972. $this->dtstamp = array();
  1973. $this->language = null;
  1974. $this->nl = null;
  1975. $this->unique_id = null;
  1976. $this->format = null;
  1977. $this->dtzid = null;
  1978. $this->allowEmpty = TRUE;
  1979. $this->xcaldecl = array();
  1980. $this->_createFormat();
  1981. $this->_makeDtstamp();
  1982. }
  1983. /*********************************************************************************/
  1984. /**
  1985. * Property Name: ACTION
  1986. */
  1987. /**
  1988. * creates formatted output for calendar component property action
  1989. *
  1990. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  1991. * @since 2.4.8 - 2008-10-22
  1992. * @return string
  1993. */
  1994. function createAction() {
  1995. if( empty( $this->action )) return FALSE;
  1996. if( empty( $this->action['value'] ))
  1997. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
  1998. $attributes = $this->_createParams( $this->action['params'] );
  1999. return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
  2000. }
  2001. /**
  2002. * set calendar component property action
  2003. *
  2004. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2005. * @since 2.16.21 - 2013-06-23
  2006. * @param string $value "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
  2007. * @param mixed $params
  2008. * @return bool
  2009. */
  2010. function setAction( $value, $params=FALSE ) {
  2011. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2012. $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  2013. return TRUE;
  2014. }
  2015. /*********************************************************************************/
  2016. /**
  2017. * Property Name: ATTACH
  2018. */
  2019. /**
  2020. * creates formatted output for calendar component property attach
  2021. *
  2022. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2023. * @since 2.11.16 - 2012-02-04
  2024. * @return string
  2025. */
  2026. function createAttach() {
  2027. if( empty( $this->attach )) return FALSE;
  2028. $output = null;
  2029. foreach( $this->attach as $attachPart ) {
  2030. if( !empty( $attachPart['value'] )) {
  2031. $attributes = $this->_createParams( $attachPart['params'] );
  2032. if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
  2033. $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
  2034. $str = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
  2035. $output = substr( $str, 0, 75 ).$this->nl;
  2036. $str = substr( $str, 75 );
  2037. $output .= ' '.chunk_split( $str, 74, $this->nl.' ' );
  2038. if( ' ' == substr( $output, -1 ))
  2039. $output = rtrim( $output );
  2040. if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
  2041. $output .= $this->nl;
  2042. return $output;
  2043. }
  2044. $output .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
  2045. }
  2046. elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
  2047. }
  2048. return $output;
  2049. }
  2050. /**
  2051. * set calendar component property attach
  2052. *
  2053. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2054. * @since 2.16.21 - 2013-06-23
  2055. * @param string $value
  2056. * @param array $params, optional
  2057. * @param integer $index, optional
  2058. * @return bool
  2059. */
  2060. function setAttach( $value, $params=FALSE, $index=FALSE ) {
  2061. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2062. iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
  2063. return TRUE;
  2064. }
  2065. /*********************************************************************************/
  2066. /**
  2067. * Property Name: ATTENDEE
  2068. */
  2069. /**
  2070. * creates formatted output for calendar component property attendee
  2071. *
  2072. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2073. * @since 2.11.12 - 2012-01-31
  2074. * @return string
  2075. */
  2076. function createAttendee() {
  2077. if( empty( $this->attendee )) return FALSE;
  2078. $output = null;
  2079. foreach( $this->attendee as $attendeePart ) { // start foreach 1
  2080. if( empty( $attendeePart['value'] )) {
  2081. if( $this->getConfig( 'allowEmpty' ))
  2082. $output .= $this->_createElement( 'ATTENDEE' );
  2083. continue;
  2084. }
  2085. $attendee1 = $attendee2 = null;
  2086. foreach( $attendeePart as $paramlabel => $paramvalue ) { // start foreach 2
  2087. if( 'value' == $paramlabel )
  2088. $attendee2 .= $paramvalue;
  2089. elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
  2090. $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
  2091. foreach( $paramvalue as $pKey => $pValue ) { // fix (opt) quotes
  2092. if( is_array( $pValue ) || in_array( $pKey, $mParams ))
  2093. continue;
  2094. if(( FALSE !== strpos( $pValue, ':' )) ||
  2095. ( FALSE !== strpos( $pValue, ';' )) ||
  2096. ( FALSE !== strpos( $pValue, ',' )))
  2097. $paramvalue[$pKey] = '"'.$pValue.'"';
  2098. }
  2099. // set attenddee parameters in rfc2445 order
  2100. if( isset( $paramvalue['CUTYPE'] ))
  2101. $attendee1 .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
  2102. if( isset( $paramvalue['MEMBER'] )) {
  2103. $attendee1 .= $this->intAttrDelimiter.'MEMBER=';
  2104. foreach( $paramvalue['MEMBER'] as $cix => $opv )
  2105. $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
  2106. }
  2107. if( isset( $paramvalue['ROLE'] ))
  2108. $attendee1 .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
  2109. if( isset( $paramvalue['PARTSTAT'] ))
  2110. $attendee1 .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
  2111. if( isset( $paramvalue['RSVP'] ))
  2112. $attendee1 .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
  2113. if( isset( $paramvalue['DELEGATED-TO'] )) {
  2114. $attendee1 .= $this->intAttrDelimiter.'DELEGATED-TO=';
  2115. foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
  2116. $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
  2117. }
  2118. if( isset( $paramvalue['DELEGATED-FROM'] )) {
  2119. $attendee1 .= $this->intAttrDelimiter.'DELEGATED-FROM=';
  2120. foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
  2121. $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
  2122. }
  2123. if( isset( $paramvalue['SENT-BY'] ))
  2124. $attendee1 .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
  2125. if( isset( $paramvalue['CN'] ))
  2126. $attendee1 .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
  2127. if( isset( $paramvalue['DIR'] )) {
  2128. $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
  2129. $attendee1 .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
  2130. }
  2131. if( isset( $paramvalue['LANGUAGE'] ))
  2132. $attendee1 .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
  2133. $xparams = array();
  2134. foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
  2135. if( ctype_digit( (string) $optparamlabel )) {
  2136. $xparams[] = $optparamvalue;
  2137. continue;
  2138. }
  2139. if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
  2140. $xparams[$optparamlabel] = $optparamvalue;
  2141. } // end foreach 3
  2142. ksort( $xparams, SORT_STRING );
  2143. foreach( $xparams as $paramKey => $paramValue ) {
  2144. if( ctype_digit( (string) $paramKey ))
  2145. $attendee1 .= $this->intAttrDelimiter.$paramValue;
  2146. else
  2147. $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
  2148. } // end foreach 3
  2149. } // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
  2150. } // end foreach 2
  2151. $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
  2152. } // end foreach 1
  2153. return $output;
  2154. }
  2155. /**
  2156. * set calendar component property attach
  2157. *
  2158. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2159. * @since 2.18.13 - 2013-09-22
  2160. * @param string $value
  2161. * @param array $params, optional
  2162. * @param integer $index, optional
  2163. * @return bool
  2164. */
  2165. function setAttendee( $value, $params=FALSE, $index=FALSE ) {
  2166. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2167. // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero:// may exist.. . also in params
  2168. if( !empty( $value )) {
  2169. if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
  2170. $value = 'MAILTO:'.$value;
  2171. elseif( !empty( $value ))
  2172. $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
  2173. $value = str_replace( 'mailto:', 'MAILTO:', $value );
  2174. }
  2175. $params2 = array();
  2176. if( is_array($params )) {
  2177. $optarrays = array();
  2178. $params = array_change_key_case( $params, CASE_UPPER );
  2179. foreach( $params as $optparamlabel => $optparamvalue ) {
  2180. if(( 'X-' != substr( $optparamlabel, 0, 2 )) && (( 'vfreebusy' == $this->objName ) || ( 'valarm' == $this->objName )))
  2181. continue;
  2182. switch( $optparamlabel ) {
  2183. case 'MEMBER':
  2184. case 'DELEGATED-TO':
  2185. case 'DELEGATED-FROM':
  2186. if( !is_array( $optparamvalue ))
  2187. $optparamvalue = array( $optparamvalue );
  2188. foreach( $optparamvalue as $part ) {
  2189. $part = trim( $part );
  2190. if(( '"' == substr( $part, 0, 1 )) &&
  2191. ( '"' == substr( $part, -1 )))
  2192. $part = substr( $part, 1, ( strlen( $part ) - 2 ));
  2193. if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
  2194. $part = "MAILTO:$part";
  2195. else
  2196. $part = 'MAILTO:'.substr( $part, 7 );
  2197. $optarrays[$optparamlabel][] = $part;
  2198. }
  2199. break;
  2200. default:
  2201. if(( '"' == substr( $optparamvalue, 0, 1 )) &&
  2202. ( '"' == substr( $optparamvalue, -1 )))
  2203. $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
  2204. if( 'SENT-BY' == $optparamlabel ) {
  2205. if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
  2206. $optparamvalue = "MAILTO:$optparamvalue";
  2207. else
  2208. $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
  2209. }
  2210. $params2[$optparamlabel] = $optparamvalue;
  2211. break;
  2212. } // end switch( $optparamlabel.. .
  2213. } // end foreach( $optparam.. .
  2214. foreach( $optarrays as $optparamlabel => $optparams )
  2215. $params2[$optparamlabel] = $optparams;
  2216. }
  2217. // remove defaults
  2218. iCalUtilityFunctions::_existRem( $params2, 'CUTYPE', 'INDIVIDUAL' );
  2219. iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
  2220. iCalUtilityFunctions::_existRem( $params2, 'ROLE', 'REQ-PARTICIPANT' );
  2221. iCalUtilityFunctions::_existRem( $params2, 'RSVP', 'FALSE' );
  2222. // check language setting
  2223. if( isset( $params2['CN' ] )) {
  2224. $lang = $this->getConfig( 'language' );
  2225. if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
  2226. $params2['LANGUAGE' ] = $lang;
  2227. }
  2228. iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
  2229. return TRUE;
  2230. }
  2231. /*********************************************************************************/
  2232. /**
  2233. * Property Name: CATEGORIES
  2234. */
  2235. /**
  2236. * creates formatted output for calendar component property categories
  2237. *
  2238. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2239. * @since 2.16.2 - 2012-12-18
  2240. * @return string
  2241. */
  2242. function createCategories() {
  2243. if( empty( $this->categories )) return FALSE;
  2244. $output = null;
  2245. foreach( $this->categories as $category ) {
  2246. if( empty( $category['value'] )) {
  2247. if ( $this->getConfig( 'allowEmpty' ))
  2248. $output .= $this->_createElement( 'CATEGORIES' );
  2249. continue;
  2250. }
  2251. $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
  2252. if( is_array( $category['value'] )) {
  2253. foreach( $category['value'] as $cix => $categoryPart )
  2254. $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
  2255. $content = implode( ',', $category['value'] );
  2256. }
  2257. else
  2258. $content = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
  2259. $output .= $this->_createElement( 'CATEGORIES', $attributes, $content );
  2260. }
  2261. return $output;
  2262. }
  2263. /**
  2264. * set calendar component property categories
  2265. *
  2266. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2267. * @since 2.16.21 - 2013-06-23
  2268. * @param mixed $value
  2269. * @param array $params, optional
  2270. * @param integer $index, optional
  2271. * @return bool
  2272. */
  2273. function setCategories( $value, $params=FALSE, $index=FALSE ) {
  2274. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2275. iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
  2276. return TRUE;
  2277. }
  2278. /*********************************************************************************/
  2279. /**
  2280. * Property Name: CLASS
  2281. */
  2282. /**
  2283. * creates formatted output for calendar component property class
  2284. *
  2285. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2286. * @since 0.9.7 - 2006-11-20
  2287. * @return string
  2288. */
  2289. function createClass() {
  2290. if( empty( $this->class )) return FALSE;
  2291. if( empty( $this->class['value'] ))
  2292. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
  2293. $attributes = $this->_createParams( $this->class['params'] );
  2294. return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
  2295. }
  2296. /**
  2297. * set calendar component property class
  2298. *
  2299. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2300. * @since 2.16.21 - 2013-06-23
  2301. * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
  2302. * @param array $params optional
  2303. * @return bool
  2304. */
  2305. function setClass( $value, $params=FALSE ) {
  2306. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2307. $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  2308. return TRUE;
  2309. }
  2310. /*********************************************************************************/
  2311. /**
  2312. * Property Name: COMMENT
  2313. */
  2314. /**
  2315. * creates formatted output for calendar component property comment
  2316. *
  2317. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2318. * @since 2.16.2 - 2012-12-18
  2319. * @return string
  2320. */
  2321. function createComment() {
  2322. if( empty( $this->comment )) return FALSE;
  2323. $output = null;
  2324. foreach( $this->comment as $commentPart ) {
  2325. if( empty( $commentPart['value'] )) {
  2326. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
  2327. continue;
  2328. }
  2329. $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
  2330. $content = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
  2331. $output .= $this->_createElement( 'COMMENT', $attributes, $content );
  2332. }
  2333. return $output;
  2334. }
  2335. /**
  2336. * set calendar component property comment
  2337. *
  2338. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2339. * @since 2.16.21 - 2013-06-23
  2340. * @param string $value
  2341. * @param array $params, optional
  2342. * @param integer $index, optional
  2343. * @return bool
  2344. */
  2345. function setComment( $value, $params=FALSE, $index=FALSE ) {
  2346. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2347. iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
  2348. return TRUE;
  2349. }
  2350. /*********************************************************************************/
  2351. /**
  2352. * Property Name: COMPLETED
  2353. */
  2354. /**
  2355. * creates formatted output for calendar component property completed
  2356. *
  2357. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2358. * @since 2.4.8 - 2008-10-22
  2359. * @return string
  2360. */
  2361. function createCompleted( ) {
  2362. if( empty( $this->completed )) return FALSE;
  2363. if( !isset( $this->completed['value']['year'] ) &&
  2364. !isset( $this->completed['value']['month'] ) &&
  2365. !isset( $this->completed['value']['day'] ) &&
  2366. !isset( $this->completed['value']['hour'] ) &&
  2367. !isset( $this->completed['value']['min'] ) &&
  2368. !isset( $this->completed['value']['sec'] ))
  2369. if( $this->getConfig( 'allowEmpty' ))
  2370. return $this->_createElement( 'COMPLETED' );
  2371. else return FALSE;
  2372. $formatted = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
  2373. $attributes = $this->_createParams( $this->completed['params'] );
  2374. return $this->_createElement( 'COMPLETED', $attributes, $formatted );
  2375. }
  2376. /**
  2377. * set calendar component property completed
  2378. *
  2379. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2380. * @since 2.16.21 - 2013-06-23
  2381. * @param mixed $year
  2382. * @param mixed $month optional
  2383. * @param int $day optional
  2384. * @param int $hour optional
  2385. * @param int $min optional
  2386. * @param int $sec optional
  2387. * @param array $params optional
  2388. * @return bool
  2389. */
  2390. function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  2391. if( empty( $year )) {
  2392. if( $this->getConfig( 'allowEmpty' )) {
  2393. $this->completed = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
  2394. return TRUE;
  2395. }
  2396. else
  2397. return FALSE;
  2398. }
  2399. $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
  2400. return TRUE;
  2401. }
  2402. /*********************************************************************************/
  2403. /**
  2404. * Property Name: CONTACT
  2405. */
  2406. /**
  2407. * creates formatted output for calendar component property contact
  2408. *
  2409. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2410. * @since 2.16.2 - 2012-12-18
  2411. * @return string
  2412. */
  2413. function createContact() {
  2414. if( empty( $this->contact )) return FALSE;
  2415. $output = null;
  2416. foreach( $this->contact as $contact ) {
  2417. if( !empty( $contact['value'] )) {
  2418. $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
  2419. $content = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
  2420. $output .= $this->_createElement( 'CONTACT', $attributes, $content );
  2421. }
  2422. elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
  2423. }
  2424. return $output;
  2425. }
  2426. /**
  2427. * set calendar component property contact
  2428. *
  2429. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2430. * @since 2.16.21 - 2013-06-23
  2431. * @param string $value
  2432. * @param array $params, optional
  2433. * @param integer $index, optional
  2434. * @return bool
  2435. */
  2436. function setContact( $value, $params=FALSE, $index=FALSE ) {
  2437. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  2438. iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
  2439. return TRUE;
  2440. }
  2441. /*********************************************************************************/
  2442. /**
  2443. * Property Name: CREATED
  2444. */
  2445. /**
  2446. * creates formatted output for calendar component property created
  2447. *
  2448. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2449. * @since 2.4.8 - 2008-10-21
  2450. * @return string
  2451. */
  2452. function createCreated() {
  2453. if( empty( $this->created )) return FALSE;
  2454. $formatted = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
  2455. $attributes = $this->_createParams( $this->created['params'] );
  2456. return $this->_createElement( 'CREATED', $attributes, $formatted );
  2457. }
  2458. /**
  2459. * set calendar component property created
  2460. *
  2461. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2462. * @since 2.19.5 - 2014-03-29
  2463. * @param mixed $year optional
  2464. * @param mixed $month optional
  2465. * @param int $day optional
  2466. * @param int $hour optional
  2467. * @param int $min optional
  2468. * @param int $sec optional
  2469. * @param mixed $params optional
  2470. * @return bool
  2471. */
  2472. function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  2473. if( !isset( $year ))
  2474. $year = gmdate( 'Ymd\THis' );
  2475. $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
  2476. return TRUE;
  2477. }
  2478. /*********************************************************************************/
  2479. /**
  2480. * Property Name: DESCRIPTION
  2481. */
  2482. /**
  2483. * creates formatted output for calendar component property description
  2484. *
  2485. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2486. * @since 2.16.2 - 2012-12-18
  2487. * @return string
  2488. */
  2489. function createDescription() {
  2490. if( empty( $this->description )) return FALSE;
  2491. $output = null;
  2492. foreach( $this->description as $description ) {
  2493. if( !empty( $description['value'] )) {
  2494. $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
  2495. $content = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
  2496. $output .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
  2497. }
  2498. elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
  2499. }
  2500. return $output;
  2501. }
  2502. /**
  2503. * set calendar component property description
  2504. *
  2505. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2506. * @since 2.16.21 - 2013-06-23
  2507. * @param string $value
  2508. * @param array $params, optional
  2509. * @param integer $index, optional
  2510. * @return bool
  2511. */
  2512. function setDescription( $value, $params=FALSE, $index=FALSE ) {
  2513. if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE; }
  2514. if( 'vjournal' != $this->objName )
  2515. $index = 1;
  2516. iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
  2517. return TRUE;
  2518. }
  2519. /*********************************************************************************/
  2520. /**
  2521. * Property Name: DTEND
  2522. */
  2523. /**
  2524. * creates formatted output for calendar component property dtend
  2525. *
  2526. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2527. * @since 2.14.4 - 2012-09-26
  2528. * @return string
  2529. */
  2530. function createDtend() {
  2531. if( empty( $this->dtend )) return FALSE;
  2532. if( !isset( $this->dtend['value']['year'] ) &&
  2533. !isset( $this->dtend['value']['month'] ) &&
  2534. !isset( $this->dtend['value']['day'] ) &&
  2535. !isset( $this->dtend['value']['hour'] ) &&
  2536. !isset( $this->dtend['value']['min'] ) &&
  2537. !isset( $this->dtend['value']['sec'] ))
  2538. if( $this->getConfig( 'allowEmpty' ))
  2539. return $this->_createElement( 'DTEND' );
  2540. else return FALSE;
  2541. $parno = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
  2542. $formatted = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
  2543. $attributes = $this->_createParams( $this->dtend['params'] );
  2544. return $this->_createElement( 'DTEND', $attributes, $formatted );
  2545. }
  2546. /**
  2547. * set calendar component property dtend
  2548. *
  2549. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2550. * @since 2.16.21 - 2013-06-23
  2551. * @param mixed $year
  2552. * @param mixed $month optional
  2553. * @param int $day optional
  2554. * @param int $hour optional
  2555. * @param int $min optional
  2556. * @param int $sec optional
  2557. * @param string $tz optional
  2558. * @param array params optional
  2559. * @return bool
  2560. */
  2561. function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
  2562. if( empty( $year )) {
  2563. if( $this->getConfig( 'allowEmpty' )) {
  2564. $this->dtend = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
  2565. return TRUE;
  2566. }
  2567. else
  2568. return FALSE;
  2569. }
  2570. $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
  2571. return TRUE;
  2572. }
  2573. /*********************************************************************************/
  2574. /**
  2575. * Property Name: DTSTAMP
  2576. */
  2577. /**
  2578. * creates formatted output for calendar component property dtstamp
  2579. *
  2580. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2581. * @since 2.4.4 - 2008-03-07
  2582. * @return string
  2583. */
  2584. function createDtstamp() {
  2585. if( !isset( $this->dtstamp['value']['year'] ) &&
  2586. !isset( $this->dtstamp['value']['month'] ) &&
  2587. !isset( $this->dtstamp['value']['day'] ) &&
  2588. !isset( $this->dtstamp['value']['hour'] ) &&
  2589. !isset( $this->dtstamp['value']['min'] ) &&
  2590. !isset( $this->dtstamp['value']['sec'] ))
  2591. $this->_makeDtstamp();
  2592. $formatted = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
  2593. $attributes = $this->_createParams( $this->dtstamp['params'] );
  2594. return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
  2595. }
  2596. /**
  2597. * computes datestamp for calendar component object instance dtstamp
  2598. *
  2599. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2600. * @since 2.19.5 - 2014-03-29
  2601. * @return void
  2602. */
  2603. function _makeDtstamp() {
  2604. $d = gmdate( 'Y-m-d-H-i-s', time());
  2605. $date = explode( '-', $d );
  2606. $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
  2607. $this->dtstamp['params'] = null;
  2608. }
  2609. /**
  2610. * set calendar component property dtstamp
  2611. *
  2612. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2613. * @since 2.4.8 - 2008-10-23
  2614. * @param mixed $year
  2615. * @param mixed $month optional
  2616. * @param int $day optional
  2617. * @param int $hour optional
  2618. * @param int $min optional
  2619. * @param int $sec optional
  2620. * @param array $params optional
  2621. * @return TRUE
  2622. */
  2623. function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  2624. if( empty( $year ))
  2625. $this->_makeDtstamp();
  2626. else
  2627. $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
  2628. return TRUE;
  2629. }
  2630. /*********************************************************************************/
  2631. /**
  2632. * Property Name: DTSTART
  2633. */
  2634. /**
  2635. * creates formatted output for calendar component property dtstart
  2636. *
  2637. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2638. * @since 2.14.4 - 2012-09-26
  2639. * @return string
  2640. */
  2641. function createDtstart() {
  2642. if( empty( $this->dtstart )) return FALSE;
  2643. if( !isset( $this->dtstart['value']['year'] ) &&
  2644. !isset( $this->dtstart['value']['month'] ) &&
  2645. !isset( $this->dtstart['value']['day'] ) &&
  2646. !isset( $this->dtstart['value']['hour'] ) &&
  2647. !isset( $this->dtstart['value']['min'] ) &&
  2648. !isset( $this->dtstart['value']['sec'] )) {
  2649. if( $this->getConfig( 'allowEmpty' ))
  2650. return $this->_createElement( 'DTSTART' );
  2651. else return FALSE;
  2652. }
  2653. if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
  2654. unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
  2655. $parno = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
  2656. $formatted = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
  2657. $attributes = $this->_createParams( $this->dtstart['params'] );
  2658. return $this->_createElement( 'DTSTART', $attributes, $formatted );
  2659. }
  2660. /**
  2661. * set calendar component property dtstart
  2662. *
  2663. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2664. * @since 2.16.21 - 2013-06-23
  2665. * @param mixed $year
  2666. * @param mixed $month optional
  2667. * @param int $day optional
  2668. * @param int $hour optional
  2669. * @param int $min optional
  2670. * @param int $sec optional
  2671. * @param string $tz optional
  2672. * @param array $params optional
  2673. * @return bool
  2674. */
  2675. function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
  2676. if( empty( $year )) {
  2677. if( $this->getConfig( 'allowEmpty' )) {
  2678. $this->dtstart = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
  2679. return TRUE;
  2680. }
  2681. else
  2682. return FALSE;
  2683. }
  2684. $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
  2685. return TRUE;
  2686. }
  2687. /*********************************************************************************/
  2688. /**
  2689. * Property Name: DUE
  2690. */
  2691. /**
  2692. * creates formatted output for calendar component property due
  2693. *
  2694. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2695. * @since 2.14.4 - 2012-09-26
  2696. * @return string
  2697. */
  2698. function createDue() {
  2699. if( empty( $this->due )) return FALSE;
  2700. if( !isset( $this->due['value']['year'] ) &&
  2701. !isset( $this->due['value']['month'] ) &&
  2702. !isset( $this->due['value']['day'] ) &&
  2703. !isset( $this->due['value']['hour'] ) &&
  2704. !isset( $this->due['value']['min'] ) &&
  2705. !isset( $this->due['value']['sec'] )) {
  2706. if( $this->getConfig( 'allowEmpty' ))
  2707. return $this->_createElement( 'DUE' );
  2708. else
  2709. return FALSE;
  2710. }
  2711. $parno = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
  2712. $formatted = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
  2713. $attributes = $this->_createParams( $this->due['params'] );
  2714. return $this->_createElement( 'DUE', $attributes, $formatted );
  2715. }
  2716. /**
  2717. * set calendar component property due
  2718. *
  2719. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2720. * @since 2.16.21 - 2013-06-23
  2721. * @param mixed $year
  2722. * @param mixed $month optional
  2723. * @param int $day optional
  2724. * @param int $hour optional
  2725. * @param int $min optional
  2726. * @param int $sec optional
  2727. * @param array $params optional
  2728. * @return bool
  2729. */
  2730. function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
  2731. if( empty( $year )) {
  2732. if( $this->getConfig( 'allowEmpty' )) {
  2733. $this->due = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
  2734. return TRUE;
  2735. }
  2736. else
  2737. return FALSE;
  2738. }
  2739. $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
  2740. return TRUE;
  2741. }
  2742. /*********************************************************************************/
  2743. /**
  2744. * Property Name: DURATION
  2745. */
  2746. /**
  2747. * creates formatted output for calendar component property duration
  2748. *
  2749. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2750. * @since 2.16.21 - 2013-05-25
  2751. * @return string
  2752. */
  2753. function createDuration() {
  2754. if( empty( $this->duration )) return FALSE;
  2755. if( !isset( $this->duration['value']['week'] ) &&
  2756. !isset( $this->duration['value']['day'] ) &&
  2757. !isset( $this->duration['value']['hour'] ) &&
  2758. !isset( $this->duration['value']['min'] ) &&
  2759. !isset( $this->duration['value']['sec'] ))
  2760. if( $this->getConfig( 'allowEmpty' ))
  2761. return $this->_createElement( 'DURATION' );
  2762. else return FALSE;
  2763. $attributes = $this->_createParams( $this->duration['params'] );
  2764. return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
  2765. }
  2766. /**
  2767. * set calendar component property duration
  2768. *
  2769. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2770. * @since 2.16.21 - 2013-05-25
  2771. * @param mixed $week
  2772. * @param mixed $day optional
  2773. * @param int $hour optional
  2774. * @param int $min optional
  2775. * @param int $sec optional
  2776. * @param array $params optional
  2777. * @return bool
  2778. */
  2779. function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  2780. if( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec )) {
  2781. if( $this->getConfig( 'allowEmpty' ))
  2782. $week = $day = null;
  2783. else
  2784. return FALSE;
  2785. }
  2786. if( is_array( $week ) && ( 1 <= count( $week )))
  2787. $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
  2788. elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
  2789. $week = trim( $week );
  2790. if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
  2791. $week = substr( $week, 1 );
  2792. $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
  2793. }
  2794. else
  2795. $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( 'week' => $week, 'day' => $day, 'hour' => $hour, 'min' => $min, 'sec' => $sec ))
  2796. , 'params' => iCalUtilityFunctions::_setParams( $params ));
  2797. return TRUE;
  2798. }
  2799. /*********************************************************************************/
  2800. /**
  2801. * Property Name: EXDATE
  2802. */
  2803. /**
  2804. * creates formatted output for calendar component property exdate
  2805. *
  2806. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2807. * @since 2.16.5 - 2012-12-28
  2808. * @return string
  2809. */
  2810. function createExdate() {
  2811. if( empty( $this->exdate )) return FALSE;
  2812. $output = null;
  2813. $exdates = array();
  2814. foreach( $this->exdate as $theExdate ) {
  2815. if( empty( $theExdate['value'] )) {
  2816. if( $this->getConfig( 'allowEmpty' ))
  2817. $output .= $this->_createElement( 'EXDATE' );
  2818. continue;
  2819. }
  2820. if( 1 < count( $theExdate['value'] ))
  2821. usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
  2822. $exdates[] = $theExdate;
  2823. }
  2824. if( 1 < count( $exdates ))
  2825. usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
  2826. foreach( $exdates as $theExdate ) {
  2827. $content = $attributes = null;
  2828. foreach( $theExdate['value'] as $eix => $exdatePart ) {
  2829. $parno = count( $exdatePart );
  2830. $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
  2831. if( isset( $theExdate['params']['TZID'] ))
  2832. $formatted = str_replace( 'Z', '', $formatted);
  2833. if( 0 < $eix ) {
  2834. if( isset( $theExdate['value'][0]['tz'] )) {
  2835. if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
  2836. ( 'Z' == $theExdate['value'][0]['tz'] )) {
  2837. if( 'Z' != substr( $formatted, -1 ))
  2838. $formatted .= 'Z';
  2839. }
  2840. else
  2841. $formatted = str_replace( 'Z', '', $formatted );
  2842. }
  2843. else
  2844. $formatted = str_replace( 'Z', '', $formatted );
  2845. } // end if( 0 < $eix )
  2846. $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
  2847. } // end foreach( $theExdate['value'] as $eix => $exdatePart )
  2848. $attributes .= $this->_createParams( $theExdate['params'] );
  2849. $output .= $this->_createElement( 'EXDATE', $attributes, $content );
  2850. } // end foreach( $exdates as $theExdate )
  2851. return $output;
  2852. }
  2853. /**
  2854. * set calendar component property exdate
  2855. *
  2856. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2857. * @since 2.16.21 - 2013-06-23
  2858. * @param array exdates
  2859. * @param array $params, optional
  2860. * @param integer $index, optional
  2861. * @return bool
  2862. */
  2863. function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
  2864. if( empty( $exdates )) {
  2865. if( $this->getConfig( 'allowEmpty' )) {
  2866. iCalUtilityFunctions::_setMval( $this->exdate, '', $params, FALSE, $index );
  2867. return TRUE;
  2868. }
  2869. else
  2870. return FALSE;
  2871. }
  2872. $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
  2873. $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
  2874. /* ev. check 1:st date and save ev. timezone **/
  2875. iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
  2876. iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
  2877. foreach( $exdates as $eix => $theExdate ) {
  2878. iCalUtilityFunctions::_strDate2arr( $theExdate );
  2879. if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
  2880. if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
  2881. if( isset( $input['params']['TZID'] ))
  2882. $theExdate['tz'] = $input['params']['TZID'];
  2883. else
  2884. $input['params']['TZID'] = $theExdate['tz'];
  2885. }
  2886. $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
  2887. }
  2888. elseif( is_array( $theExdate )) {
  2889. $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
  2890. if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
  2891. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  2892. $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  2893. unset( $exdatea['unparsedtext'] );
  2894. }
  2895. else
  2896. $exdatea = $d;
  2897. }
  2898. elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
  2899. $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
  2900. unset( $exdatea['unparsedtext'] );
  2901. }
  2902. if( 3 == $parno )
  2903. unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
  2904. elseif( isset( $exdatea['tz'] ))
  2905. $exdatea['tz'] = (string) $exdatea['tz'];
  2906. if( isset( $input['params']['TZID'] ) ||
  2907. ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
  2908. ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
  2909. ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
  2910. unset( $exdatea['tz'] );
  2911. if( $toZ ) // time zone Z
  2912. $exdatea['tz'] = 'Z';
  2913. $input['value'][] = $exdatea;
  2914. }
  2915. if( 0 >= count( $input['value'] ))
  2916. return FALSE;
  2917. if( 3 == $parno ) {
  2918. $input['params']['VALUE'] = 'DATE';
  2919. unset( $input['params']['TZID'] );
  2920. }
  2921. if( $toZ ) // time zone Z
  2922. unset( $input['params']['TZID'] );
  2923. iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
  2924. return TRUE;
  2925. }
  2926. /*********************************************************************************/
  2927. /**
  2928. * Property Name: EXRULE
  2929. */
  2930. /**
  2931. * creates formatted output for calendar component property exrule
  2932. *
  2933. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2934. * @since 2.4.8 - 2008-10-22
  2935. * @return string
  2936. */
  2937. function createExrule() {
  2938. if( empty( $this->exrule )) return FALSE;
  2939. return $this->_format_recur( 'EXRULE', $this->exrule );
  2940. }
  2941. /**
  2942. * set calendar component property exdate
  2943. *
  2944. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2945. * @since 2.16.21 - 2013-06-23
  2946. * @param array $exruleset
  2947. * @param array $params, optional
  2948. * @param integer $index, optional
  2949. * @return bool
  2950. */
  2951. function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
  2952. if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = ''; else return FALSE;
  2953. iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
  2954. return TRUE;
  2955. }
  2956. /*********************************************************************************/
  2957. /**
  2958. * Property Name: FREEBUSY
  2959. */
  2960. /**
  2961. * creates formatted output for calendar component property freebusy
  2962. *
  2963. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  2964. * @since 2.16.27 - 2013-07-05
  2965. * @return string
  2966. */
  2967. function createFreebusy() {
  2968. if( empty( $this->freebusy )) return FALSE;
  2969. $output = null;
  2970. foreach( $this->freebusy as $freebusyPart ) {
  2971. if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
  2972. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
  2973. continue;
  2974. }
  2975. $attributes = $content = null;
  2976. if( isset( $freebusyPart['value']['fbtype'] )) {
  2977. $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
  2978. unset( $freebusyPart['value']['fbtype'] );
  2979. $freebusyPart['value'] = array_values( $freebusyPart['value'] );
  2980. }
  2981. else
  2982. $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
  2983. $attributes .= $this->_createParams( $freebusyPart['params'] );
  2984. $fno = 1;
  2985. $cnt = count( $freebusyPart['value']);
  2986. if( 1 < $cnt )
  2987. usort( $freebusyPart['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
  2988. foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
  2989. $formatted = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
  2990. $content .= $formatted;
  2991. $content .= '/';
  2992. $cnt2 = count( $freebusyPeriod[1]);
  2993. if( array_key_exists( 'year', $freebusyPeriod[1] )) // date-time
  2994. $cnt2 = 7;
  2995. elseif( array_key_exists( 'week', $freebusyPeriod[1] )) // duration
  2996. $cnt2 = 5;
  2997. if(( 7 == $cnt2 ) && // period= -> date-time
  2998. isset( $freebusyPeriod[1]['year'] ) &&
  2999. isset( $freebusyPeriod[1]['month'] ) &&
  3000. isset( $freebusyPeriod[1]['day'] )) {
  3001. $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
  3002. }
  3003. else { // period= -> dur-time
  3004. $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
  3005. }
  3006. if( $fno < $cnt )
  3007. $content .= ',';
  3008. $fno++;
  3009. }
  3010. $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
  3011. }
  3012. return $output;
  3013. }
  3014. /**
  3015. * set calendar component property freebusy
  3016. *
  3017. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3018. * @since 2.16.21 - 2013-06-23
  3019. * @param string $fbType
  3020. * @param array $fbValues
  3021. * @param array $params, optional
  3022. * @param integer $index, optional
  3023. * @return bool
  3024. */
  3025. function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
  3026. if( empty( $fbValues )) {
  3027. if( $this->getConfig( 'allowEmpty' )) {
  3028. iCalUtilityFunctions::_setMval( $this->freebusy, '', $params, FALSE, $index );
  3029. return TRUE;
  3030. }
  3031. else
  3032. return FALSE;
  3033. }
  3034. $fbType = strtoupper( $fbType );
  3035. if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
  3036. ( 'X-' != substr( $fbType, 0, 2 )))
  3037. $fbType = 'BUSY';
  3038. $input = array( 'fbtype' => $fbType );
  3039. foreach( $fbValues as $fbPeriod ) { // periods => period
  3040. if( empty( $fbPeriod ))
  3041. continue;
  3042. $freebusyPeriod = array();
  3043. foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
  3044. $freebusyPairMember = array();
  3045. if( is_array( $fbMember )) {
  3046. if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
  3047. $freebusyPairMember = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
  3048. $freebusyPairMember['tz'] = 'Z';
  3049. }
  3050. elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
  3051. $freebusyPairMember = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
  3052. $freebusyPairMember['tz'] = 'Z';
  3053. }
  3054. else { // array format duration
  3055. $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
  3056. }
  3057. }
  3058. elseif(( 3 <= strlen( trim( $fbMember ))) && // string format duration
  3059. ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
  3060. if( 'P' != $fbMember{0} )
  3061. $fbmember = substr( $fbMember, 1 );
  3062. $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
  3063. }
  3064. elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
  3065. $freebusyPairMember = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
  3066. unset( $freebusyPairMember['unparsedtext'] );
  3067. $freebusyPairMember['tz'] = 'Z';
  3068. }
  3069. $freebusyPeriod[] = $freebusyPairMember;
  3070. }
  3071. $input[] = $freebusyPeriod;
  3072. }
  3073. iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
  3074. return TRUE;
  3075. }
  3076. /*********************************************************************************/
  3077. /**
  3078. * Property Name: GEO
  3079. */
  3080. /**
  3081. * creates formatted output for calendar component property geo
  3082. *
  3083. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3084. * @since 2.18.10 - 2013-09-03
  3085. * @return string
  3086. */
  3087. function createGeo() {
  3088. if( empty( $this->geo )) return FALSE;
  3089. if( empty( $this->geo['value'] ))
  3090. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
  3091. return $this->_createElement( 'GEO', $this->_createParams( $this->geo['params'] ),
  3092. iCalUtilityFunctions::_geo2str2( $this->geo['value']['latitude'], iCalUtilityFunctions::$geoLatFmt ).
  3093. ';'.iCalUtilityFunctions::_geo2str2( $this->geo['value']['longitude'], iCalUtilityFunctions::$geoLongFmt ));
  3094. }
  3095. /**
  3096. * set calendar component property geo
  3097. *
  3098. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3099. * @since 2.18.10 - 2013-09-03
  3100. * @param mixed $latitude
  3101. * @param mixed $longitude
  3102. * @param array $params optional
  3103. * @return bool
  3104. */
  3105. function setGeo( $latitude, $longitude, $params=FALSE ) {
  3106. if( isset( $latitude ) && isset( $longitude )) {
  3107. if( !is_array( $this->geo )) $this->geo = array();
  3108. $this->geo['value']['latitude'] = floatval( $latitude );
  3109. $this->geo['value']['longitude'] = floatval( $longitude );
  3110. $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
  3111. }
  3112. elseif( $this->getConfig( 'allowEmpty' ))
  3113. $this->geo = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ) );
  3114. else
  3115. return FALSE;
  3116. return TRUE;
  3117. }
  3118. /*********************************************************************************/
  3119. /**
  3120. * Property Name: LAST-MODIFIED
  3121. */
  3122. /**
  3123. * creates formatted output for calendar component property last-modified
  3124. *
  3125. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3126. * @since 2.4.8 - 2008-10-21
  3127. * @return string
  3128. */
  3129. function createLastModified() {
  3130. if( empty( $this->lastmodified )) return FALSE;
  3131. $attributes = $this->_createParams( $this->lastmodified['params'] );
  3132. $formatted = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
  3133. return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
  3134. }
  3135. /**
  3136. * set calendar component property completed
  3137. *
  3138. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3139. * @since 2.19.5 - 2014-03-29
  3140. * @param mixed $year optional
  3141. * @param mixed $month optional
  3142. * @param int $day optional
  3143. * @param int $hour optional
  3144. * @param int $min optional
  3145. * @param int $sec optional
  3146. * @param array $params optional
  3147. * @return boll
  3148. */
  3149. function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  3150. if( empty( $year ))
  3151. $year = gmdate( 'Ymd\THis' );
  3152. $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
  3153. return TRUE;
  3154. }
  3155. /*********************************************************************************/
  3156. /**
  3157. * Property Name: LOCATION
  3158. */
  3159. /**
  3160. * creates formatted output for calendar component property location
  3161. *
  3162. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3163. * @since 2.16.2 - 2012-12-18
  3164. * @return string
  3165. */
  3166. function createLocation() {
  3167. if( empty( $this->location )) return FALSE;
  3168. if( empty( $this->location['value'] ))
  3169. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
  3170. $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
  3171. $content = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
  3172. return $this->_createElement( 'LOCATION', $attributes, $content );
  3173. }
  3174. /**
  3175. * set calendar component property location
  3176. '
  3177. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3178. * @since 2.16.21 - 2013-06-23
  3179. * @param string $value
  3180. * @param array params optional
  3181. * @return bool
  3182. */
  3183. function setLocation( $value, $params=FALSE ) {
  3184. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3185. $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3186. return TRUE;
  3187. }
  3188. /*********************************************************************************/
  3189. /**
  3190. * Property Name: ORGANIZER
  3191. */
  3192. /**
  3193. * creates formatted output for calendar component property organizer
  3194. *
  3195. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3196. * @since 2.6.33 - 2010-12-17
  3197. * @return string
  3198. */
  3199. function createOrganizer() {
  3200. if( empty( $this->organizer )) return FALSE;
  3201. if( empty( $this->organizer['value'] ))
  3202. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
  3203. $attributes = $this->_createParams( $this->organizer['params']
  3204. , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
  3205. return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
  3206. }
  3207. /**
  3208. * set calendar component property organizer
  3209. *
  3210. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3211. * @since 2.16.21 - 2013-06-23
  3212. * @param string $value
  3213. * @param array params optional
  3214. * @return bool
  3215. */
  3216. function setOrganizer( $value, $params=FALSE ) {
  3217. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3218. if( !empty( $value )) {
  3219. if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
  3220. $value = 'MAILTO:'.$value;
  3221. elseif( !empty( $value ))
  3222. $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
  3223. $value = str_replace( 'mailto:', 'MAILTO:', $value );
  3224. }
  3225. $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3226. if( isset( $this->organizer['params']['SENT-BY'] )){
  3227. if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
  3228. $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
  3229. else
  3230. $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
  3231. }
  3232. return TRUE;
  3233. }
  3234. /*********************************************************************************/
  3235. /**
  3236. * Property Name: PERCENT-COMPLETE
  3237. */
  3238. /**
  3239. * creates formatted output for calendar component property percent-complete
  3240. *
  3241. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3242. * @since 2.9.3 - 2011-05-14
  3243. * @return string
  3244. */
  3245. function createPercentComplete() {
  3246. if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
  3247. if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
  3248. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
  3249. $attributes = $this->_createParams( $this->percentcomplete['params'] );
  3250. return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
  3251. }
  3252. /**
  3253. * set calendar component property percent-complete
  3254. *
  3255. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3256. * @since 2.16.21 - 2013-06-23
  3257. * @param int $value
  3258. * @param array $params optional
  3259. * @return bool
  3260. */
  3261. function setPercentComplete( $value, $params=FALSE ) {
  3262. if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3263. $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3264. return TRUE;
  3265. }
  3266. /*********************************************************************************/
  3267. /**
  3268. * Property Name: PRIORITY
  3269. */
  3270. /**
  3271. * creates formatted output for calendar component property priority
  3272. *
  3273. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3274. * @since 2.9.3 - 2011-05-14
  3275. * @return string
  3276. */
  3277. function createPriority() {
  3278. if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
  3279. if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
  3280. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
  3281. $attributes = $this->_createParams( $this->priority['params'] );
  3282. return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
  3283. }
  3284. /**
  3285. * set calendar component property priority
  3286. *
  3287. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3288. * @since 2.16.21 - 2013-06-23
  3289. * @param int $value
  3290. * @param array $params optional
  3291. * @return bool
  3292. */
  3293. function setPriority( $value, $params=FALSE ) {
  3294. if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3295. $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3296. return TRUE;
  3297. }
  3298. /*********************************************************************************/
  3299. /**
  3300. * Property Name: RDATE
  3301. */
  3302. /**
  3303. * creates formatted output for calendar component property rdate
  3304. *
  3305. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3306. * @since 2.16.9 - 2013-01-09
  3307. * @return string
  3308. */
  3309. function createRdate() {
  3310. if( empty( $this->rdate )) return FALSE;
  3311. $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
  3312. $output = null;
  3313. $rdates = array();
  3314. foreach( $this->rdate as $rpix => $theRdate ) {
  3315. if( empty( $theRdate['value'] )) {
  3316. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
  3317. continue;
  3318. }
  3319. if( $utctime )
  3320. unset( $theRdate['params']['TZID'] );
  3321. if( 1 < count( $theRdate['value'] ))
  3322. usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
  3323. $rdates[] = $theRdate;
  3324. }
  3325. if( 1 < count( $rdates ))
  3326. usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
  3327. foreach( $rdates as $rpix => $theRdate ) {
  3328. $attributes = $this->_createParams( $theRdate['params'] );
  3329. $cnt = count( $theRdate['value'] );
  3330. $content = null;
  3331. $rno = 1;
  3332. foreach( $theRdate['value'] as $rix => $rdatePart ) {
  3333. $contentPart = null;
  3334. if( is_array( $rdatePart ) &&
  3335. isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
  3336. if( $utctime )
  3337. unset( $rdatePart[0]['tz'] );
  3338. $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
  3339. if( $utctime || !empty( $theRdate['params']['TZID'] ))
  3340. $formatted = str_replace( 'Z', '', $formatted);
  3341. $contentPart .= $formatted;
  3342. $contentPart .= '/';
  3343. $cnt2 = count( $rdatePart[1]);
  3344. if( array_key_exists( 'year', $rdatePart[1] )) {
  3345. if( array_key_exists( 'hour', $rdatePart[1] ))
  3346. $cnt2 = 7; // date-time
  3347. else
  3348. $cnt2 = 3; // date
  3349. }
  3350. elseif( array_key_exists( 'week', $rdatePart[1] )) // duration
  3351. $cnt2 = 5;
  3352. if(( 7 == $cnt2 ) && // period= -> date-time
  3353. isset( $rdatePart[1]['year'] ) &&
  3354. isset( $rdatePart[1]['month'] ) &&
  3355. isset( $rdatePart[1]['day'] )) {
  3356. if( $utctime )
  3357. unset( $rdatePart[1]['tz'] );
  3358. $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
  3359. if( $utctime || !empty( $theRdate['params']['TZID'] ))
  3360. $formatted = str_replace( 'Z', '', $formatted );
  3361. $contentPart .= $formatted;
  3362. }
  3363. else { // period= -> dur-time
  3364. $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
  3365. }
  3366. } // PERIOD end
  3367. else { // SINGLE date start
  3368. if( $utctime )
  3369. unset( $rdatePart['tz'] );
  3370. $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
  3371. $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
  3372. if( $utctime || !empty( $theRdate['params']['TZID'] ))
  3373. $formatted = str_replace( 'Z', '', $formatted);
  3374. $contentPart .= $formatted;
  3375. }
  3376. $content .= $contentPart;
  3377. if( $rno < $cnt )
  3378. $content .= ',';
  3379. $rno++;
  3380. }
  3381. $output .= $this->_createElement( 'RDATE', $attributes, $content );
  3382. }
  3383. return $output;
  3384. }
  3385. /**
  3386. * set calendar component property rdate
  3387. *
  3388. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3389. * @since 2.16.21 - 2013-06-23
  3390. * @param array $rdates
  3391. * @param array $params, optional
  3392. * @param integer $index, optional
  3393. * @return bool
  3394. */
  3395. function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
  3396. if( empty( $rdates )) {
  3397. if( $this->getConfig( 'allowEmpty' )) {
  3398. iCalUtilityFunctions::_setMval( $this->rdate, '', $params, FALSE, $index );
  3399. return TRUE;
  3400. }
  3401. else
  3402. return FALSE;
  3403. }
  3404. $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
  3405. if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
  3406. unset( $input['params']['TZID'] );
  3407. $input['params']['VALUE'] = 'DATE-TIME';
  3408. }
  3409. $zArr = array( 'GMT', 'UTC', 'Z' );
  3410. $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
  3411. /* check if PERIOD, if not set */
  3412. if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
  3413. isset( $rdates[0] ) && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
  3414. isset( $rdates[0][0] ) && isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
  3415. (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
  3416. iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
  3417. ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] ))))) &&
  3418. ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
  3419. $input['params']['VALUE'] = 'PERIOD';
  3420. /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
  3421. $date = reset( $rdates );
  3422. if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
  3423. $date = reset( $date );
  3424. iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
  3425. iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
  3426. foreach( $rdates as $rpix => $theRdate ) {
  3427. $inputa = null;
  3428. iCalUtilityFunctions::_strDate2arr( $theRdate );
  3429. if( is_array( $theRdate )) {
  3430. if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
  3431. foreach( $theRdate as $rix => $rPeriod ) {
  3432. iCalUtilityFunctions::_strDate2arr( $theRdate );
  3433. if( is_array( $rPeriod )) {
  3434. if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) { // timestamp
  3435. if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
  3436. if( isset( $input['params']['TZID'] ))
  3437. $rPeriod['tz'] = $input['params']['TZID'];
  3438. else
  3439. $input['params']['TZID'] = $rPeriod['tz'];
  3440. }
  3441. $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
  3442. }
  3443. elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
  3444. $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
  3445. if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
  3446. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  3447. $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  3448. unset( $inputab['unparsedtext'] );
  3449. }
  3450. else
  3451. $inputab = $d;
  3452. }
  3453. elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
  3454. $inputab = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
  3455. unset( $inputab['unparsedtext'] );
  3456. }
  3457. else // array format duration
  3458. $inputab = iCalUtilityFunctions::_duration2arr( $rPeriod );
  3459. }
  3460. elseif(( 3 <= strlen( trim( $rPeriod ))) && // string format duration
  3461. ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
  3462. if( 'P' != $rPeriod[0] )
  3463. $rPeriod = substr( $rPeriod, 1 );
  3464. $inputab = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
  3465. }
  3466. elseif( 8 <= strlen( trim( $rPeriod ))) { // text date ex. 2006-08-03 10:12:18
  3467. $inputab = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
  3468. unset( $inputab['unparsedtext'] );
  3469. }
  3470. if(( 0 == $rpix ) && ( 0 == $rix )) {
  3471. if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
  3472. $inputab['tz'] = 'Z';
  3473. $toZ = TRUE;
  3474. }
  3475. }
  3476. else {
  3477. if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
  3478. $inputab['tz'] = 'Z';
  3479. else
  3480. unset( $inputab['tz'] );
  3481. }
  3482. if( $toZ && isset( $inputab['year'] ) )
  3483. $inputab['tz'] = 'Z';
  3484. $inputa[] = $inputab;
  3485. }
  3486. } // PERIOD end
  3487. elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) { // timestamp
  3488. if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
  3489. if( isset( $input['params']['TZID'] ))
  3490. $theRdate['tz'] = $input['params']['TZID'];
  3491. else
  3492. $input['params']['TZID'] = $theRdate['tz'];
  3493. }
  3494. $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
  3495. }
  3496. else { // date[-time]
  3497. $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
  3498. if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
  3499. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
  3500. $inputa = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  3501. unset( $inputa['unparsedtext'] );
  3502. }
  3503. }
  3504. }
  3505. elseif( 8 <= strlen( trim( $theRdate ))) { // text date ex. 2006-08-03 10:12:18
  3506. $inputa = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
  3507. unset( $inputa['unparsedtext'] );
  3508. if( $toZ )
  3509. $inputa['tz'] = 'Z';
  3510. }
  3511. if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
  3512. if(( 0 == $rpix ) && !$toZ )
  3513. $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
  3514. if( $toZ )
  3515. $inputa['tz'] = 'Z';
  3516. if( 3 == $parno )
  3517. unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
  3518. elseif( isset( $inputa['tz'] ))
  3519. $inputa['tz'] = (string) $inputa['tz'];
  3520. if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
  3521. if( !$toZ )
  3522. unset( $inputa['tz'] );
  3523. }
  3524. $input['value'][] = $inputa;
  3525. }
  3526. if( 3 == $parno ) {
  3527. $input['params']['VALUE'] = 'DATE';
  3528. unset( $input['params']['TZID'] );
  3529. }
  3530. if( $toZ )
  3531. unset( $input['params']['TZID'] );
  3532. iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
  3533. return TRUE;
  3534. }
  3535. /*********************************************************************************/
  3536. /**
  3537. * Property Name: RECURRENCE-ID
  3538. */
  3539. /**
  3540. * creates formatted output for calendar component property recurrence-id
  3541. *
  3542. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3543. * @since 2.14.4 - 2012-09-26
  3544. * @return string
  3545. */
  3546. function createRecurrenceid() {
  3547. if( empty( $this->recurrenceid )) return FALSE;
  3548. if( empty( $this->recurrenceid['value'] ))
  3549. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
  3550. $parno = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
  3551. $formatted = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
  3552. $attributes = $this->_createParams( $this->recurrenceid['params'] );
  3553. return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
  3554. }
  3555. /**
  3556. * set calendar component property recurrence-id
  3557. *
  3558. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3559. * @since 2.16.21 - 2013-06-23
  3560. * @param mixed $year
  3561. * @param mixed $month optional
  3562. * @param int $day optional
  3563. * @param int $hour optional
  3564. * @param int $min optional
  3565. * @param int $sec optional
  3566. * @param array $params optional
  3567. * @return bool
  3568. */
  3569. function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
  3570. if( empty( $year )) {
  3571. if( $this->getConfig( 'allowEmpty' )) {
  3572. $this->recurrenceid = array( 'value' => '', 'params' => null );
  3573. return TRUE;
  3574. }
  3575. else
  3576. return FALSE;
  3577. }
  3578. $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
  3579. return TRUE;
  3580. }
  3581. /*********************************************************************************/
  3582. /**
  3583. * Property Name: RELATED-TO
  3584. */
  3585. /**
  3586. * creates formatted output for calendar component property related-to
  3587. *
  3588. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3589. * @since 2.16.21 - 2013-05-25
  3590. * @return string
  3591. */
  3592. function createRelatedTo() {
  3593. if( empty( $this->relatedto )) return FALSE;
  3594. $output = null;
  3595. foreach( $this->relatedto as $relation ) {
  3596. if( !empty( $relation['value'] ))
  3597. $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
  3598. elseif( $this->getConfig( 'allowEmpty' ))
  3599. $output .= $this->_createElement( 'RELATED-TO' );
  3600. }
  3601. return $output;
  3602. }
  3603. /**
  3604. * set calendar component property related-to
  3605. *
  3606. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3607. * @since 2.16.21 - 2013-06-23
  3608. * @param float $relid
  3609. * @param array $params, optional
  3610. * @param index $index, optional
  3611. * @return bool
  3612. */
  3613. function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
  3614. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3615. iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
  3616. iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
  3617. return TRUE;
  3618. }
  3619. /*********************************************************************************/
  3620. /**
  3621. * Property Name: REPEAT
  3622. */
  3623. /**
  3624. * creates formatted output for calendar component property repeat
  3625. *
  3626. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3627. * @since 2.9.3 - 2011-05-14
  3628. * @return string
  3629. */
  3630. function createRepeat() {
  3631. if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
  3632. if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
  3633. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
  3634. $attributes = $this->_createParams( $this->repeat['params'] );
  3635. return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
  3636. }
  3637. /**
  3638. * set calendar component property repeat
  3639. *
  3640. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3641. * @since 2.16.21 - 2013-06-23
  3642. * @param string $value
  3643. * @param array $params optional
  3644. * @return void
  3645. */
  3646. function setRepeat( $value, $params=FALSE ) {
  3647. if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3648. $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3649. return TRUE;
  3650. }
  3651. /*********************************************************************************/
  3652. /**
  3653. * Property Name: REQUEST-STATUS
  3654. */
  3655. /**
  3656. * creates formatted output for calendar component property request-status
  3657. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3658. * @since 2.16.2 - 2012-12-18
  3659. * @return string
  3660. */
  3661. function createRequestStatus() {
  3662. if( empty( $this->requeststatus )) return FALSE;
  3663. $output = null;
  3664. foreach( $this->requeststatus as $rstat ) {
  3665. if( empty( $rstat['value']['statcode'] )) {
  3666. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
  3667. continue;
  3668. }
  3669. $attributes = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
  3670. $content = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
  3671. $content .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
  3672. if( isset( $rstat['value']['extdata'] ))
  3673. $content .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
  3674. $output .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
  3675. }
  3676. return $output;
  3677. }
  3678. /**
  3679. * set calendar component property request-status
  3680. *
  3681. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3682. * @since 2.16.21 - 2013-06-23
  3683. * @param float $statcode
  3684. * @param string $text
  3685. * @param string $extdata, optional
  3686. * @param array $params, optional
  3687. * @param integer $index, optional
  3688. * @return bool
  3689. */
  3690. function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
  3691. if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = ''; else return FALSE;
  3692. $input = array( 'statcode' => $statcode, 'text' => $text );
  3693. if( $extdata )
  3694. $input['extdata'] = $extdata;
  3695. iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
  3696. return TRUE;
  3697. }
  3698. /*********************************************************************************/
  3699. /**
  3700. * Property Name: RESOURCES
  3701. */
  3702. /**
  3703. * creates formatted output for calendar component property resources
  3704. *
  3705. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3706. * @since 2.16.2 - 2012-12-18
  3707. * @return string
  3708. */
  3709. function createResources() {
  3710. if( empty( $this->resources )) return FALSE;
  3711. $output = null;
  3712. foreach( $this->resources as $resource ) {
  3713. if( empty( $resource['value'] )) {
  3714. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
  3715. continue;
  3716. }
  3717. $attributes = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
  3718. if( is_array( $resource['value'] )) {
  3719. foreach( $resource['value'] as $rix => $resourcePart )
  3720. $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
  3721. $content = implode( ',', $resource['value'] );
  3722. }
  3723. else
  3724. $content = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
  3725. $output .= $this->_createElement( 'RESOURCES', $attributes, $content );
  3726. }
  3727. return $output;
  3728. }
  3729. /**
  3730. * set calendar component property recources
  3731. *
  3732. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3733. * @since 2.16.21 - 2013-06-23
  3734. * @param mixed $value
  3735. * @param array $params, optional
  3736. * @param integer $index, optional
  3737. * @return bool
  3738. */
  3739. function setResources( $value, $params=FALSE, $index=FALSE ) {
  3740. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3741. iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
  3742. return TRUE;
  3743. }
  3744. /*********************************************************************************/
  3745. /**
  3746. * Property Name: RRULE
  3747. */
  3748. /**
  3749. * creates formatted output for calendar component property rrule
  3750. *
  3751. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3752. * @since 2.4.8 - 2008-10-21
  3753. * @return string
  3754. */
  3755. function createRrule() {
  3756. if( empty( $this->rrule )) return FALSE;
  3757. return $this->_format_recur( 'RRULE', $this->rrule );
  3758. }
  3759. /**
  3760. * set calendar component property rrule
  3761. *
  3762. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3763. * @since 2.16.21 - 2013-06-23
  3764. * @param array $rruleset
  3765. * @param array $params, optional
  3766. * @param integer $index, optional
  3767. * @return void
  3768. */
  3769. function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
  3770. if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = ''; else return FALSE;
  3771. iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
  3772. return TRUE;
  3773. }
  3774. /*********************************************************************************/
  3775. /**
  3776. * Property Name: SEQUENCE
  3777. */
  3778. /**
  3779. * creates formatted output for calendar component property sequence
  3780. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3781. * @since 2.9.3 - 2011-05-14
  3782. * @return string
  3783. */
  3784. function createSequence() {
  3785. if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
  3786. if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
  3787. ( '0' != $this->sequence['value'] ))
  3788. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
  3789. $attributes = $this->_createParams( $this->sequence['params'] );
  3790. return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
  3791. }
  3792. /**
  3793. * set calendar component property sequence
  3794. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3795. * @since 2.10.8 - 2011-09-19
  3796. * @param int $value optional
  3797. * @param array $params optional
  3798. * @return bool
  3799. */
  3800. function setSequence( $value=FALSE, $params=FALSE ) {
  3801. if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
  3802. $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
  3803. $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3804. return TRUE;
  3805. }
  3806. /*********************************************************************************/
  3807. /**
  3808. * Property Name: STATUS
  3809. */
  3810. /**
  3811. * creates formatted output for calendar component property status
  3812. *
  3813. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3814. * @since 2.4.8 - 2008-10-21
  3815. * @return string
  3816. */
  3817. function createStatus() {
  3818. if( empty( $this->status )) return FALSE;
  3819. if( empty( $this->status['value'] ))
  3820. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
  3821. $attributes = $this->_createParams( $this->status['params'] );
  3822. return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
  3823. }
  3824. /**
  3825. * set calendar component property status
  3826. *
  3827. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3828. * @since 2.16.21 - 2013-06-23
  3829. * @param string $value
  3830. * @param array $params optional
  3831. * @return bool
  3832. */
  3833. function setStatus( $value, $params=FALSE ) {
  3834. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3835. $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3836. return TRUE;
  3837. }
  3838. /*********************************************************************************/
  3839. /**
  3840. * Property Name: SUMMARY
  3841. */
  3842. /**
  3843. * creates formatted output for calendar component property summary
  3844. *
  3845. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3846. * @since 2.16.2 - 2012-12-18
  3847. * @return string
  3848. */
  3849. function createSummary() {
  3850. if( empty( $this->summary )) return FALSE;
  3851. if( empty( $this->summary['value'] ))
  3852. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
  3853. $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
  3854. $content = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
  3855. return $this->_createElement( 'SUMMARY', $attributes, $content );
  3856. }
  3857. /**
  3858. * set calendar component property summary
  3859. *
  3860. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3861. * @since 2.16.21 - 2013-06-23
  3862. * @param string $value
  3863. * @param string $params optional
  3864. * @return bool
  3865. */
  3866. function setSummary( $value, $params=FALSE ) {
  3867. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3868. $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3869. return TRUE;
  3870. }
  3871. /*********************************************************************************/
  3872. /**
  3873. * Property Name: TRANSP
  3874. */
  3875. /**
  3876. * creates formatted output for calendar component property transp
  3877. *
  3878. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3879. * @since 2.4.8 - 2008-10-21
  3880. * @return string
  3881. */
  3882. function createTransp() {
  3883. if( empty( $this->transp )) return FALSE;
  3884. if( empty( $this->transp['value'] ))
  3885. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
  3886. $attributes = $this->_createParams( $this->transp['params'] );
  3887. return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
  3888. }
  3889. /**
  3890. * set calendar component property transp
  3891. *
  3892. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3893. * @since 2.16.21 - 2013-06-23
  3894. * @param string $value
  3895. * @param string $params optional
  3896. * @return bool
  3897. */
  3898. function setTransp( $value, $params=FALSE ) {
  3899. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  3900. $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  3901. return TRUE;
  3902. }
  3903. /*********************************************************************************/
  3904. /**
  3905. * Property Name: TRIGGER
  3906. */
  3907. /**
  3908. * creates formatted output for calendar component property trigger
  3909. *
  3910. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3911. * @since 2.4.16 - 2008-10-21
  3912. * @return string
  3913. */
  3914. function createTrigger() {
  3915. if( empty( $this->trigger )) return FALSE;
  3916. if( empty( $this->trigger['value'] ))
  3917. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
  3918. $content = $attributes = null;
  3919. if( isset( $this->trigger['value']['year'] ) &&
  3920. isset( $this->trigger['value']['month'] ) &&
  3921. isset( $this->trigger['value']['day'] ))
  3922. $content .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
  3923. else {
  3924. if( TRUE !== $this->trigger['value']['relatedStart'] )
  3925. $attributes .= $this->intAttrDelimiter.'RELATED=END';
  3926. if( $this->trigger['value']['before'] )
  3927. $content .= '-';
  3928. $content .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
  3929. }
  3930. $attributes .= $this->_createParams( $this->trigger['params'] );
  3931. return $this->_createElement( 'TRIGGER', $attributes, $content );
  3932. }
  3933. /**
  3934. * set calendar component property trigger
  3935. *
  3936. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  3937. * @since 2.16.21 - 2013-06-23
  3938. * @param mixed $year
  3939. * @param mixed $month optional
  3940. * @param int $day optional
  3941. * @param int $week optional
  3942. * @param int $hour optional
  3943. * @param int $min optional
  3944. * @param int $sec optional
  3945. * @param bool $relatedStart optional
  3946. * @param bool $before optional
  3947. * @param array $params optional
  3948. * @return bool
  3949. */
  3950. function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
  3951. if( empty( $year ) && ( empty( $month ) || is_array( $month )) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
  3952. if( $this->getConfig( 'allowEmpty' )) {
  3953. $this->trigger = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $month ) );
  3954. return TRUE;
  3955. }
  3956. else
  3957. return FALSE;
  3958. if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
  3959. $params = iCalUtilityFunctions::_setParams( $month );
  3960. $date = iCalUtilityFunctions::_timestamp2date( $year, 7 );
  3961. foreach( $date as $k => $v )
  3962. $$k = $v;
  3963. }
  3964. elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
  3965. $params = iCalUtilityFunctions::_setParams( $month );
  3966. if(!(array_key_exists( 'year', $year ) && // exclude date-time
  3967. array_key_exists( 'month', $year ) &&
  3968. array_key_exists( 'day', $year ))) { // when this must be a duration
  3969. if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
  3970. $relatedStart = FALSE;
  3971. else
  3972. $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
  3973. $before = ( array_key_exists( 'before', $year ) && ( TRUE !== $year['before'] )) ? FALSE : TRUE;
  3974. }
  3975. $SSYY = ( array_key_exists( 'year', $year )) ? $year['year'] : null;
  3976. $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
  3977. $day = ( array_key_exists( 'day', $year )) ? $year['day'] : null;
  3978. $week = ( array_key_exists( 'week', $year )) ? $year['week'] : null;
  3979. $hour = ( array_key_exists( 'hour', $year )) ? $year['hour'] : 0; //null;
  3980. $min = ( array_key_exists( 'min', $year )) ? $year['min'] : 0; //null;
  3981. $sec = ( array_key_exists( 'sec', $year )) ? $year['sec'] : 0; //null;
  3982. $year = $SSYY;
  3983. }
  3984. elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) { // duration or date in a string
  3985. $params = iCalUtilityFunctions::_setParams( $month );
  3986. if( in_array( $year{0}, array( 'P', '+', '-' ))) { // duration
  3987. $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
  3988. $before = ( '-' == $year[0] ) ? TRUE : FALSE;
  3989. if( 'P' != $year[0] )
  3990. $year = substr( $year, 1 );
  3991. $date = iCalUtilityFunctions::_durationStr2arr( $year);
  3992. }
  3993. else // date
  3994. $date = iCalUtilityFunctions::_strdate2date( $year, 7 );
  3995. unset( $year, $month, $day, $date['unparsedtext'] );
  3996. if( empty( $date ))
  3997. $sec = 0;
  3998. else
  3999. foreach( $date as $k => $v )
  4000. $$k = $v;
  4001. }
  4002. else // single values in function input parameters
  4003. $params = iCalUtilityFunctions::_setParams( $params );
  4004. if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
  4005. $params['VALUE'] = 'DATE-TIME';
  4006. $hour = ( $hour ) ? $hour : 0;
  4007. $min = ( $min ) ? $min : 0;
  4008. $sec = ( $sec ) ? $sec : 0;
  4009. $this->trigger = array( 'params' => $params );
  4010. $this->trigger['value'] = array( 'year' => $year
  4011. , 'month' => $month
  4012. , 'day' => $day
  4013. , 'hour' => $hour
  4014. , 'min' => $min
  4015. , 'sec' => $sec
  4016. , 'tz' => 'Z' );
  4017. return TRUE;
  4018. }
  4019. elseif(( empty( $year ) && empty( $month )) && // duration
  4020. (( !empty( $week ) || ( 0 == $week )) ||
  4021. ( !empty( $day ) || ( 0 == $day )) ||
  4022. ( !empty( $hour ) || ( 0 == $hour )) ||
  4023. ( !empty( $min ) || ( 0 == $min )) ||
  4024. ( !empty( $sec ) || ( 0 == $sec )))) {
  4025. unset( $params['RELATED'] ); // set at output creation (END only)
  4026. unset( $params['VALUE'] ); // 'DURATION' default
  4027. $this->trigger = array( 'params' => $params );
  4028. $this->trigger['value'] = array();
  4029. if( !empty( $week )) $this->trigger['value']['week'] = $week;
  4030. if( !empty( $day )) $this->trigger['value']['day'] = $day;
  4031. if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
  4032. if( !empty( $min )) $this->trigger['value']['min'] = $min;
  4033. if( !empty( $sec )) $this->trigger['value']['sec'] = $sec;
  4034. if( empty( $this->trigger['value'] )) {
  4035. $this->trigger['value']['sec'] = 0;
  4036. $before = FALSE;
  4037. }
  4038. else
  4039. $this->trigger['value'] = iCalUtilityFunctions::_duration2arr( $this->trigger['value'] );
  4040. $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
  4041. $before = ( FALSE !== $before ) ? TRUE : FALSE;
  4042. $this->trigger['value']['relatedStart'] = $relatedStart;
  4043. $this->trigger['value']['before'] = $before;
  4044. return TRUE;
  4045. }
  4046. return FALSE;
  4047. }
  4048. /*********************************************************************************/
  4049. /**
  4050. * Property Name: TZID
  4051. */
  4052. /**
  4053. * creates formatted output for calendar component property tzid
  4054. *
  4055. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4056. * @since 2.16.2 - 2012-12-18
  4057. * @return string
  4058. */
  4059. function createTzid() {
  4060. if( empty( $this->tzid )) return FALSE;
  4061. if( empty( $this->tzid['value'] ))
  4062. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
  4063. $attributes = $this->_createParams( $this->tzid['params'] );
  4064. return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
  4065. }
  4066. /**
  4067. * set calendar component property tzid
  4068. *
  4069. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4070. * @since 2.16.21 - 2013-06-23
  4071. * @param string $value
  4072. * @param array $params optional
  4073. * @return bool
  4074. */
  4075. function setTzid( $value, $params=FALSE ) {
  4076. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4077. $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4078. return TRUE;
  4079. }
  4080. /*********************************************************************************/
  4081. /**
  4082. * .. .
  4083. * Property Name: TZNAME
  4084. */
  4085. /**
  4086. * creates formatted output for calendar component property tzname
  4087. *
  4088. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4089. * @since 2.16.2 - 2012-12-18
  4090. * @return string
  4091. */
  4092. function createTzname() {
  4093. if( empty( $this->tzname )) return FALSE;
  4094. $output = null;
  4095. foreach( $this->tzname as $theName ) {
  4096. if( !empty( $theName['value'] )) {
  4097. $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
  4098. $output .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
  4099. }
  4100. elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
  4101. }
  4102. return $output;
  4103. }
  4104. /**
  4105. * set calendar component property tzname
  4106. *
  4107. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4108. * @since 2.16.21 - 2013-06-23
  4109. * @param string $value
  4110. * @param string $params, optional
  4111. * @param integer $index, optional
  4112. * @return bool
  4113. */
  4114. function setTzname( $value, $params=FALSE, $index=FALSE ) {
  4115. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4116. iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
  4117. return TRUE;
  4118. }
  4119. /*********************************************************************************/
  4120. /**
  4121. * Property Name: TZOFFSETFROM
  4122. */
  4123. /**
  4124. * creates formatted output for calendar component property tzoffsetfrom
  4125. *
  4126. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4127. * @since 2.4.8 - 2008-10-21
  4128. * @return string
  4129. */
  4130. function createTzoffsetfrom() {
  4131. if( empty( $this->tzoffsetfrom )) return FALSE;
  4132. if( empty( $this->tzoffsetfrom['value'] ))
  4133. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
  4134. $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
  4135. return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
  4136. }
  4137. /**
  4138. * set calendar component property tzoffsetfrom
  4139. *
  4140. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4141. * @since 2.16.21 - 2013-06-23
  4142. * @param string $value
  4143. * @param string $params optional
  4144. * @return bool
  4145. */
  4146. function setTzoffsetfrom( $value, $params=FALSE ) {
  4147. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4148. $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4149. return TRUE;
  4150. }
  4151. /*********************************************************************************/
  4152. /**
  4153. * Property Name: TZOFFSETTO
  4154. */
  4155. /**
  4156. * creates formatted output for calendar component property tzoffsetto
  4157. *
  4158. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4159. * @since 2.4.8 - 2008-10-21
  4160. * @return string
  4161. */
  4162. function createTzoffsetto() {
  4163. if( empty( $this->tzoffsetto )) return FALSE;
  4164. if( empty( $this->tzoffsetto['value'] ))
  4165. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
  4166. $attributes = $this->_createParams( $this->tzoffsetto['params'] );
  4167. return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
  4168. }
  4169. /**
  4170. * set calendar component property tzoffsetto
  4171. *
  4172. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4173. * @since 2.16.21 - 2013-06-23
  4174. * @param string $value
  4175. * @param string $params optional
  4176. * @return bool
  4177. */
  4178. function setTzoffsetto( $value, $params=FALSE ) {
  4179. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4180. $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4181. return TRUE;
  4182. }
  4183. /*********************************************************************************/
  4184. /**
  4185. * Property Name: TZURL
  4186. */
  4187. /**
  4188. * creates formatted output for calendar component property tzurl
  4189. *
  4190. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4191. * @since 2.4.8 - 2008-10-21
  4192. * @return string
  4193. */
  4194. function createTzurl() {
  4195. if( empty( $this->tzurl )) return FALSE;
  4196. if( empty( $this->tzurl['value'] ))
  4197. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
  4198. $attributes = $this->_createParams( $this->tzurl['params'] );
  4199. return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
  4200. }
  4201. /**
  4202. * set calendar component property tzurl
  4203. *
  4204. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4205. * @since 2.16.21 - 2013-06-23
  4206. * @param string $value
  4207. * @param string $params optional
  4208. * @return boll
  4209. */
  4210. function setTzurl( $value, $params=FALSE ) {
  4211. if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4212. $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4213. return TRUE;
  4214. }
  4215. /*********************************************************************************/
  4216. /**
  4217. * Property Name: UID
  4218. */
  4219. /**
  4220. * creates formatted output for calendar component property uid
  4221. *
  4222. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4223. * @since 2.19.1 - 2014-02-21
  4224. * @return string
  4225. */
  4226. function createUid() {
  4227. if( empty( $this->uid ))
  4228. $this->_makeuid();
  4229. $attributes = $this->_createParams( $this->uid['params'] );
  4230. return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
  4231. }
  4232. /**
  4233. * create an unique id for this calendar component object instance
  4234. *
  4235. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4236. * @since 2.2.7 - 2007-09-04
  4237. * @return void
  4238. */
  4239. function _makeUid() {
  4240. $date = date('Ymd\THisT');
  4241. $unique = substr(microtime(), 2, 4);
  4242. $base = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
  4243. $start = 0;
  4244. $end = strlen( $base ) - 1;
  4245. $length = 6;
  4246. $str = null;
  4247. for( $p = 0; $p < $length; $p++ )
  4248. $unique .= $base{mt_rand( $start, $end )};
  4249. $this->uid = array( 'params' => null );
  4250. $this->uid['value'] = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
  4251. }
  4252. /**
  4253. * set calendar component property uid
  4254. *
  4255. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4256. * @since 2.19.1 - 2014-02-21
  4257. * @param string $value
  4258. * @param string $params optional
  4259. * @return bool
  4260. */
  4261. function setUid( $value, $params=FALSE ) {
  4262. if( empty( $value ) && ( '0' != $value )) return FALSE; // no allowEmpty check here !!!!
  4263. $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4264. return TRUE;
  4265. }
  4266. /*********************************************************************************/
  4267. /**
  4268. * Property Name: URL
  4269. */
  4270. /**
  4271. * creates formatted output for calendar component property url
  4272. *
  4273. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4274. * @since 2.4.8 - 2008-10-21
  4275. * @return string
  4276. */
  4277. function createUrl() {
  4278. if( empty( $this->url )) return FALSE;
  4279. if( empty( $this->url['value'] ))
  4280. return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
  4281. $attributes = $this->_createParams( $this->url['params'] );
  4282. return $this->_createElement( 'URL', $attributes, $this->url['value'] );
  4283. }
  4284. /**
  4285. * set calendar component property url
  4286. *
  4287. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4288. * @since 2.16.21 - 2013-06-23
  4289. * @param string $value
  4290. * @param string $params optional
  4291. * @return bool
  4292. */
  4293. function setUrl( $value, $params=FALSE ) {
  4294. if( !empty( $value )) {
  4295. if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
  4296. return FALSE;
  4297. }
  4298. elseif( $this->getConfig( 'allowEmpty' ))
  4299. $value = '';
  4300. else
  4301. return FALSE;
  4302. $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
  4303. return TRUE;
  4304. }
  4305. /*********************************************************************************/
  4306. /**
  4307. * Property Name: x-prop
  4308. */
  4309. /**
  4310. * creates formatted output for calendar component property x-prop
  4311. *
  4312. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4313. * @since 2.16.2 - 2012-12-18
  4314. * @return string
  4315. */
  4316. function createXprop() {
  4317. if( empty( $this->xprop )) return FALSE;
  4318. $output = null;
  4319. foreach( $this->xprop as $label => $xpropPart ) {
  4320. if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
  4321. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
  4322. continue;
  4323. }
  4324. $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
  4325. if( is_array( $xpropPart['value'] )) {
  4326. foreach( $xpropPart['value'] as $pix => $theXpart )
  4327. $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
  4328. $xpropPart['value'] = implode( ',', $xpropPart['value'] );
  4329. }
  4330. else
  4331. $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
  4332. $output .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
  4333. }
  4334. return $output;
  4335. }
  4336. /**
  4337. * set calendar component property x-prop
  4338. *
  4339. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4340. * @since 2.18.10 - 2013-09-04
  4341. * @param string $label
  4342. * @param mixed $value
  4343. * @param array $params optional
  4344. * @return bool
  4345. */
  4346. function setXprop( $label, $value, $params=FALSE ) {
  4347. if( empty( $label ))
  4348. return FALSE;
  4349. $label = strtoupper( $label );
  4350. if( 'X-' != substr( $label, 0, 2 ))
  4351. return FALSE;
  4352. if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
  4353. $xprop = array( 'value' => $value );
  4354. $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
  4355. if( !is_array( $this->xprop )) $this->xprop = array();
  4356. $this->xprop[$label] = $xprop;
  4357. return TRUE;
  4358. }
  4359. /*********************************************************************************/
  4360. /*********************************************************************************/
  4361. /**
  4362. * create element format parts
  4363. *
  4364. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4365. * @since 2.0.6 - 2006-06-20
  4366. * @return string
  4367. */
  4368. function _createFormat() {
  4369. $objectname = null;
  4370. switch( $this->format ) {
  4371. case 'xcal':
  4372. $objectname = ( isset( $this->timezonetype )) ?
  4373. strtolower( $this->timezonetype ) : strtolower( $this->objName );
  4374. $this->componentStart1 = $this->elementStart1 = '<';
  4375. $this->componentStart2 = $this->elementStart2 = '>';
  4376. $this->componentEnd1 = $this->elementEnd1 = '</';
  4377. $this->componentEnd2 = $this->elementEnd2 = '>'.$this->nl;
  4378. $this->intAttrDelimiter = '<!-- -->';
  4379. $this->attributeDelimiter = $this->nl;
  4380. $this->valueInit = null;
  4381. break;
  4382. default:
  4383. $objectname = ( isset( $this->timezonetype )) ?
  4384. strtoupper( $this->timezonetype ) : strtoupper( $this->objName );
  4385. $this->componentStart1 = 'BEGIN:';
  4386. $this->componentStart2 = null;
  4387. $this->componentEnd1 = 'END:';
  4388. $this->componentEnd2 = $this->nl;
  4389. $this->elementStart1 = null;
  4390. $this->elementStart2 = null;
  4391. $this->elementEnd1 = null;
  4392. $this->elementEnd2 = $this->nl;
  4393. $this->intAttrDelimiter = '<!-- -->';
  4394. $this->attributeDelimiter = ';';
  4395. $this->valueInit = ':';
  4396. break;
  4397. }
  4398. return $objectname;
  4399. }
  4400. /**
  4401. * creates formatted output for calendar component property
  4402. *
  4403. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4404. * @since 2.16.2 - 2012-12-18
  4405. * @param string $label property name
  4406. * @param string $attributes property attributes
  4407. * @param string $content property content (optional)
  4408. * @return string
  4409. */
  4410. function _createElement( $label, $attributes=null, $content=FALSE ) {
  4411. switch( $this->format ) {
  4412. case 'xcal':
  4413. $label = strtolower( $label );
  4414. break;
  4415. default:
  4416. $label = strtoupper( $label );
  4417. break;
  4418. }
  4419. $output = $this->elementStart1.$label;
  4420. $categoriesAttrLang = null;
  4421. $attachInlineBinary = FALSE;
  4422. $attachfmttype = null;
  4423. if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
  4424. $this->xcaldecl[] = array( 'xmldecl' => 'ELEMENT'
  4425. , 'ref' => $label
  4426. , 'type2' => '(#PCDATA)' );
  4427. }
  4428. if( !empty( $attributes )) {
  4429. $attributes = trim( $attributes );
  4430. if ( 'xcal' == $this->format ) {
  4431. $attributes2 = explode( $this->intAttrDelimiter, $attributes );
  4432. $attributes = null;
  4433. foreach( $attributes2 as $aix => $attribute ) {
  4434. $attrKVarr = explode( '=', $attribute );
  4435. if( empty( $attrKVarr[0] ))
  4436. continue;
  4437. if( !isset( $attrKVarr[1] )) {
  4438. $attrValue = $attrKVarr[0];
  4439. $attrKey = $aix;
  4440. }
  4441. elseif( 2 == count( $attrKVarr)) {
  4442. $attrKey = strtolower( $attrKVarr[0] );
  4443. $attrValue = $attrKVarr[1];
  4444. }
  4445. else {
  4446. $attrKey = strtolower( $attrKVarr[0] );
  4447. unset( $attrKVarr[0] );
  4448. $attrValue = implode( '=', $attrKVarr );
  4449. }
  4450. if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
  4451. $attachInlineBinary = TRUE;
  4452. if( 'fmttype' == $attrKey )
  4453. $attachfmttype = $attrKey.'='.$attrValue;
  4454. continue;
  4455. }
  4456. elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
  4457. $categoriesAttrLang = $attrKey.'='.$attrValue;
  4458. else {
  4459. $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
  4460. $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
  4461. if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
  4462. $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
  4463. $attrValue = str_replace( '"', '', $attrValue );
  4464. }
  4465. $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
  4466. }
  4467. }
  4468. }
  4469. else {
  4470. $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
  4471. }
  4472. }
  4473. if(( 'xcal' == $this->format) &&
  4474. ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
  4475. $pos = strrpos($content, "/");
  4476. $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
  4477. $this->xcaldecl[] = array( 'xmldecl' => 'ENTITY'
  4478. , 'uri' => $docname
  4479. , 'ref' => 'SYSTEM'
  4480. , 'external' => $content
  4481. , 'type' => 'NDATA'
  4482. , 'type2' => 'BINERY' );
  4483. $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
  4484. $attributes .= 'uri="'.$docname.'"';
  4485. $content = null;
  4486. if( 'attach' == $label ) {
  4487. $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
  4488. $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
  4489. $attributes = null;
  4490. }
  4491. }
  4492. elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
  4493. $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
  4494. }
  4495. $output .= $attributes;
  4496. if( !$content && ( '0' != $content )) {
  4497. switch( $this->format ) {
  4498. case 'xcal':
  4499. $output .= ' /';
  4500. $output .= $this->elementStart2.$this->nl;
  4501. return $output;
  4502. break;
  4503. default:
  4504. $output .= $this->elementStart2.$this->valueInit;
  4505. return iCalUtilityFunctions::_size75( $output, $this->nl );
  4506. break;
  4507. }
  4508. }
  4509. $output .= $this->elementStart2;
  4510. $output .= $this->valueInit.$content;
  4511. switch( $this->format ) {
  4512. case 'xcal':
  4513. return $output.$this->elementEnd1.$label.$this->elementEnd2;
  4514. break;
  4515. default:
  4516. return iCalUtilityFunctions::_size75( $output, $this->nl );
  4517. break;
  4518. }
  4519. }
  4520. /**
  4521. * creates formatted output for calendar component property parameters
  4522. *
  4523. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4524. * @since 2.18.10 - 2012-09-04
  4525. * @param array $params optional
  4526. * @param array $ctrKeys optional
  4527. * @return string
  4528. */
  4529. function _createParams( $params=array(), $ctrKeys=array() ) {
  4530. if( !is_array( $params ) || empty( $params ))
  4531. $params = array();
  4532. $attrLANG = $attr1 = $attr2 = $lang = null;
  4533. $CNattrKey = ( in_array( 'CN', $ctrKeys )) ? TRUE : FALSE ;
  4534. $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
  4535. $CNattrExist = $LANGattrExist = FALSE;
  4536. $xparams = array();
  4537. $params = array_change_key_case( $params, CASE_UPPER );
  4538. foreach( $params as $paramKey => $paramValue ) {
  4539. if(( FALSE !== strpos( $paramValue, ':' )) ||
  4540. ( FALSE !== strpos( $paramValue, ';' )) ||
  4541. ( FALSE !== strpos( $paramValue, ',' )))
  4542. $paramValue = '"'.$paramValue.'"';
  4543. if( ctype_digit( (string) $paramKey )) {
  4544. $xparams[] = $paramValue;
  4545. continue;
  4546. }
  4547. if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
  4548. $xparams[$paramKey] = $paramValue;
  4549. else
  4550. $params[$paramKey] = $paramValue;
  4551. }
  4552. ksort( $xparams, SORT_STRING );
  4553. foreach( $xparams as $paramKey => $paramValue ) {
  4554. if( ctype_digit( (string) $paramKey ))
  4555. $attr2 .= $this->intAttrDelimiter.$paramValue;
  4556. else
  4557. $attr2 .= $this->intAttrDelimiter."$paramKey=$paramValue";
  4558. }
  4559. if( isset( $params['FMTTYPE'] ) && !in_array( 'FMTTYPE', $ctrKeys )) {
  4560. $attr1 .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
  4561. $attr2 = null;
  4562. }
  4563. if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING', $ctrKeys )) {
  4564. if( !empty( $attr2 )) {
  4565. $attr1 .= $attr2;
  4566. $attr2 = null;
  4567. }
  4568. $attr1 .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
  4569. }
  4570. if( isset( $params['VALUE'] ) && !in_array( 'VALUE', $ctrKeys ))
  4571. $attr1 .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
  4572. if( isset( $params['TZID'] ) && !in_array( 'TZID', $ctrKeys )) {
  4573. $attr1 .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
  4574. }
  4575. if( isset( $params['RANGE'] ) && !in_array( 'RANGE', $ctrKeys ))
  4576. $attr1 .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
  4577. if( isset( $params['RELTYPE'] ) && !in_array( 'RELTYPE', $ctrKeys ))
  4578. $attr1 .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
  4579. if( isset( $params['CN'] ) && $CNattrKey ) {
  4580. $attr1 = $this->intAttrDelimiter.'CN='.$params['CN'];
  4581. $CNattrExist = TRUE;
  4582. }
  4583. if( isset( $params['DIR'] ) && in_array( 'DIR', $ctrKeys )) {
  4584. $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
  4585. $attr1 .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
  4586. }
  4587. if( isset( $params['SENT-BY'] ) && in_array( 'SENT-BY', $ctrKeys ))
  4588. $attr1 .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
  4589. if( isset( $params['ALTREP'] ) && in_array( 'ALTREP', $ctrKeys )) {
  4590. $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
  4591. $attr1 .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
  4592. }
  4593. if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
  4594. $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
  4595. $LANGattrExist = TRUE;
  4596. }
  4597. if( !$LANGattrExist ) {
  4598. $lang = $this->getConfig( 'language' );
  4599. if(( $CNattrExist || $LANGattrKey ) && $lang )
  4600. $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
  4601. }
  4602. return $attr1.$attrLANG.$attr2;
  4603. }
  4604. /**
  4605. * creates formatted output for calendar component property data value type recur
  4606. *
  4607. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4608. * @since 2.16.25 - 2013-06-30
  4609. * @param array $recurlabel
  4610. * @param array $recurdata
  4611. * @return string
  4612. */
  4613. function _format_recur( $recurlabel, $recurdata ) {
  4614. $output = null;
  4615. foreach( $recurdata as $therule ) {
  4616. if( empty( $therule['value'] )) {
  4617. if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
  4618. continue;
  4619. }
  4620. $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
  4621. $content1 = $content2 = null;
  4622. foreach( $therule['value'] as $rulelabel => $rulevalue ) {
  4623. switch( strtoupper( $rulelabel )) {
  4624. case 'FREQ': {
  4625. $content1 .= "FREQ=$rulevalue";
  4626. break;
  4627. }
  4628. case 'UNTIL': {
  4629. $parno = ( isset( $rulevalue['hour'] )) ? 7 : 3;
  4630. $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno );
  4631. break;
  4632. }
  4633. case 'COUNT':
  4634. case 'INTERVAL':
  4635. case 'WKST': {
  4636. $content2 .= ";$rulelabel=$rulevalue";
  4637. break;
  4638. }
  4639. case 'BYSECOND':
  4640. case 'BYMINUTE':
  4641. case 'BYHOUR':
  4642. case 'BYMONTHDAY':
  4643. case 'BYYEARDAY':
  4644. case 'BYWEEKNO':
  4645. case 'BYMONTH':
  4646. case 'BYSETPOS': {
  4647. $content2 .= ";$rulelabel=";
  4648. if( is_array( $rulevalue )) {
  4649. foreach( $rulevalue as $vix => $valuePart ) {
  4650. $content2 .= ( $vix ) ? ',' : null;
  4651. $content2 .= $valuePart;
  4652. }
  4653. }
  4654. else
  4655. $content2 .= $rulevalue;
  4656. break;
  4657. }
  4658. case 'BYDAY': {
  4659. $byday = array( '' );
  4660. $bx = 0;
  4661. foreach( $rulevalue as $bix => $bydayPart ) {
  4662. if( ! empty( $byday[$bx] ) && ! ctype_digit( substr( $byday[$bx], -1 ))) // new day
  4663. $byday[++$bx] = '';
  4664. if( ! is_array( $bydayPart )) // day without order number
  4665. $byday[$bx] .= (string) $bydayPart;
  4666. else { // day with order number
  4667. foreach( $bydayPart as $bix2 => $bydayPart2 )
  4668. $byday[$bx] .= (string) $bydayPart2;
  4669. }
  4670. } // end foreach( $rulevalue as $bix => $bydayPart )
  4671. if( 1 < count( $byday ))
  4672. usort( $byday, array( 'iCalUtilityFunctions', '_recurBydaySort' ));
  4673. $content2 .= ';BYDAY='.implode( ',', $byday );
  4674. break;
  4675. }
  4676. default: {
  4677. $content2 .= ";$rulelabel=$rulevalue";
  4678. break;
  4679. }
  4680. }
  4681. }
  4682. $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
  4683. }
  4684. return $output;
  4685. }
  4686. /**
  4687. * check if property not exists within component
  4688. *
  4689. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4690. * @since 2.5.1 - 2008-10-15
  4691. * @param string $propName
  4692. * @return bool
  4693. */
  4694. function _notExistProp( $propName ) {
  4695. if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
  4696. $propName = strtolower( $propName );
  4697. if( 'last-modified' == $propName ) { if( !isset( $this->lastmodified )) return TRUE; }
  4698. elseif( 'percent-complete' == $propName ) { if( !isset( $this->percentcomplete )) return TRUE; }
  4699. elseif( 'recurrence-id' == $propName ) { if( !isset( $this->recurrenceid )) return TRUE; }
  4700. elseif( 'related-to' == $propName ) { if( !isset( $this->relatedto )) return TRUE; }
  4701. elseif( 'request-status' == $propName ) { if( !isset( $this->requeststatus )) return TRUE; }
  4702. elseif(( 'x-' != substr($propName,0,2)) && !isset( $this->$propName )) return TRUE;
  4703. return FALSE;
  4704. }
  4705. /*********************************************************************************/
  4706. /*********************************************************************************/
  4707. /**
  4708. * get general component config variables or info about subcomponents
  4709. *
  4710. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4711. * @since 2.19.1 - 2014-02-21
  4712. * @param mixed $config
  4713. * @return value
  4714. */
  4715. function getConfig( $config = FALSE) {
  4716. if( !$config ) {
  4717. $return = array();
  4718. $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' );
  4719. $return['FORMAT'] = $this->getConfig( 'FORMAT' );
  4720. if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' )))
  4721. $return['LANGUAGE'] = $lang;
  4722. $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
  4723. $return['TZTD'] = $this->getConfig( 'TZID' );
  4724. $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' );
  4725. return $return;
  4726. }
  4727. switch( strtoupper( $config )) {
  4728. case 'ALLOWEMPTY':
  4729. return $this->allowEmpty;
  4730. break;
  4731. case 'COMPSINFO':
  4732. unset( $this->compix );
  4733. $info = array();
  4734. if( isset( $this->components )) {
  4735. foreach( $this->components as $cix => $component ) {
  4736. if( empty( $component )) continue;
  4737. $info[$cix]['ordno'] = $cix + 1;
  4738. $info[$cix]['type'] = $component->objName;
  4739. $info[$cix]['uid'] = $component->getProperty( 'uid' );
  4740. $info[$cix]['props'] = $component->getConfig( 'propinfo' );
  4741. $info[$cix]['sub'] = $component->getConfig( 'compsinfo' );
  4742. }
  4743. }
  4744. return $info;
  4745. break;
  4746. case 'FORMAT':
  4747. return $this->format;
  4748. break;
  4749. case 'LANGUAGE':
  4750. // get language for calendar component as defined in [RFC 1766]
  4751. return $this->language;
  4752. break;
  4753. case 'NL':
  4754. case 'NEWLINECHAR':
  4755. return $this->nl;
  4756. break;
  4757. case 'PROPINFO':
  4758. $output = array();
  4759. if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
  4760. if( empty( $this->uid )) $this->_makeuid();
  4761. $output['UID'] = 1;
  4762. if( empty( $this->dtstamp )) $this->_makeDtstamp();
  4763. $output['DTSTAMP'] = 1;
  4764. }
  4765. if( !empty( $this->summary )) $output['SUMMARY'] = 1;
  4766. if( !empty( $this->description )) $output['DESCRIPTION'] = count( $this->description );
  4767. if( !empty( $this->dtstart )) $output['DTSTART'] = 1;
  4768. if( !empty( $this->dtend )) $output['DTEND'] = 1;
  4769. if( !empty( $this->due )) $output['DUE'] = 1;
  4770. if( !empty( $this->duration )) $output['DURATION'] = 1;
  4771. if( !empty( $this->rrule )) $output['RRULE'] = count( $this->rrule );
  4772. if( !empty( $this->rdate )) $output['RDATE'] = count( $this->rdate );
  4773. if( !empty( $this->exdate )) $output['EXDATE'] = count( $this->exdate );
  4774. if( !empty( $this->exrule )) $output['EXRULE'] = count( $this->exrule );
  4775. if( !empty( $this->action )) $output['ACTION'] = 1;
  4776. if( !empty( $this->attach )) $output['ATTACH'] = count( $this->attach );
  4777. if( !empty( $this->attendee )) $output['ATTENDEE'] = count( $this->attendee );
  4778. if( !empty( $this->categories )) $output['CATEGORIES'] = count( $this->categories );
  4779. if( !empty( $this->class )) $output['CLASS'] = 1;
  4780. if( !empty( $this->comment )) $output['COMMENT'] = count( $this->comment );
  4781. if( !empty( $this->completed )) $output['COMPLETED'] = 1;
  4782. if( !empty( $this->contact )) $output['CONTACT'] = count( $this->contact );
  4783. if( !empty( $this->created )) $output['CREATED'] = 1;
  4784. if( !empty( $this->freebusy )) $output['FREEBUSY'] = count( $this->freebusy );
  4785. if( !empty( $this->geo )) $output['GEO'] = 1;
  4786. if( !empty( $this->lastmodified )) $output['LAST-MODIFIED'] = 1;
  4787. if( !empty( $this->location )) $output['LOCATION'] = 1;
  4788. if( !empty( $this->organizer )) $output['ORGANIZER'] = 1;
  4789. if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
  4790. if( !empty( $this->priority )) $output['PRIORITY'] = 1;
  4791. if( !empty( $this->recurrenceid )) $output['RECURRENCE-ID'] = 1;
  4792. if( !empty( $this->relatedto )) $output['RELATED-TO'] = count( $this->relatedto );
  4793. if( !empty( $this->repeat )) $output['REPEAT'] = 1;
  4794. if( !empty( $this->requeststatus )) $output['REQUEST-STATUS'] = count( $this->requeststatus );
  4795. if( !empty( $this->resources )) $output['RESOURCES'] = count( $this->resources );
  4796. if( !empty( $this->sequence )) $output['SEQUENCE'] = 1;
  4797. if( !empty( $this->sequence )) $output['SEQUENCE'] = 1;
  4798. if( !empty( $this->status )) $output['STATUS'] = 1;
  4799. if( !empty( $this->transp )) $output['TRANSP'] = 1;
  4800. if( !empty( $this->trigger )) $output['TRIGGER'] = 1;
  4801. if( !empty( $this->tzid )) $output['TZID'] = 1;
  4802. if( !empty( $this->tzname )) $output['TZNAME'] = count( $this->tzname );
  4803. if( !empty( $this->tzoffsetfrom )) $output['TZOFFSETFROM'] = 1;
  4804. if( !empty( $this->tzoffsetto )) $output['TZOFFSETTO'] = 1;
  4805. if( !empty( $this->tzurl )) $output['TZURL'] = 1;
  4806. if( !empty( $this->url )) $output['URL'] = 1;
  4807. if( !empty( $this->xprop )) $output['X-PROP'] = count( $this->xprop );
  4808. return $output;
  4809. break;
  4810. case 'SETPROPERTYNAMES':
  4811. return array_keys( $this->getConfig( 'propinfo' ));
  4812. break;
  4813. case 'TZID':
  4814. return $this->dtzid;
  4815. break;
  4816. case 'UNIQUE_ID':
  4817. if( empty( $this->unique_id ))
  4818. $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
  4819. return $this->unique_id;
  4820. break;
  4821. }
  4822. }
  4823. /**
  4824. * general component config setting
  4825. *
  4826. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4827. * @since 2.10.18 - 2013-09-06
  4828. * @param mixed $config
  4829. * @param string $value
  4830. * @param bool $softUpdate
  4831. * @return void
  4832. */
  4833. function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
  4834. if( is_array( $config )) {
  4835. $config = array_change_key_case( $config, CASE_UPPER );
  4836. if( isset( $config['NEWLINECHAR'] ) || isset( $config['NL'] )) {
  4837. $k = ( isset( $config['NEWLINECHAR'] )) ? 'NEWLINECHAR' : 'NL';
  4838. if( FALSE === $this->setConfig( 'NL', $config[$k] ))
  4839. return FALSE;
  4840. unset( $config[$k] );
  4841. }
  4842. foreach( $config as $cKey => $cValue ) {
  4843. if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
  4844. return FALSE;
  4845. }
  4846. return TRUE;
  4847. }
  4848. else
  4849. $config = strtoupper( $config );
  4850. $res = FALSE;
  4851. switch( $config ) {
  4852. case 'ALLOWEMPTY':
  4853. $this->allowEmpty = $value;
  4854. $subcfg = array( 'ALLOWEMPTY' => $value );
  4855. $res = TRUE;
  4856. break;
  4857. case 'FORMAT':
  4858. $value = trim( strtolower( $value ));
  4859. $this->format = $value;
  4860. $this->_createFormat();
  4861. $subcfg = array( 'FORMAT' => $value );
  4862. $res = TRUE;
  4863. break;
  4864. case 'LANGUAGE':
  4865. // set language for calendar component as defined in [RFC 1766]
  4866. $value = trim( $value );
  4867. if( empty( $this->language ) || !$softUpdate )
  4868. $this->language = $value;
  4869. $subcfg = array( 'LANGUAGE' => $value );
  4870. $res = TRUE;
  4871. break;
  4872. case 'NL':
  4873. case 'NEWLINECHAR':
  4874. $this->nl = $value;
  4875. $this->_createFormat();
  4876. $subcfg = array( 'NL' => $value );
  4877. $res = TRUE;
  4878. break;
  4879. case 'TZID':
  4880. $this->dtzid = $value;
  4881. $subcfg = array( 'TZID' => $value );
  4882. $res = TRUE;
  4883. break;
  4884. case 'UNIQUE_ID':
  4885. $value = trim( $value );
  4886. $this->unique_id = $value;
  4887. $subcfg = array( 'UNIQUE_ID' => $value );
  4888. $res = TRUE;
  4889. break;
  4890. default: // any unvalid config key.. .
  4891. return TRUE;
  4892. }
  4893. if( !$res ) return FALSE;
  4894. if( isset( $subcfg ) && !empty( $this->components )) {
  4895. foreach( $subcfg as $cfgkey => $cfgvalue ) {
  4896. foreach( $this->components as $cix => $component ) {
  4897. $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
  4898. if( !$res )
  4899. break 2;
  4900. $this->components[$cix] = $component->copy(); // PHP4 compliant
  4901. }
  4902. }
  4903. }
  4904. return $res;
  4905. }
  4906. /*********************************************************************************/
  4907. /**
  4908. * delete component property value
  4909. *
  4910. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  4911. * @since 2.8.8 - 2011-03-15
  4912. * @param mixed $propName, bool FALSE => X-property
  4913. * @param int $propix, optional, if specific property is wanted in case of multiply occurences
  4914. * @return bool, if successfull delete TRUE
  4915. */
  4916. function deleteProperty( $propName=FALSE, $propix=FALSE ) {
  4917. if( $this->_notExistProp( $propName )) return FALSE;
  4918. $propName = strtoupper( $propName );
  4919. if( in_array( $propName, array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'EXDATE', 'EXRULE',
  4920. 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP' ))) {
  4921. if( !$propix )
  4922. $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
  4923. $this->propdelix[$propName] = --$propix;
  4924. }
  4925. $return = FALSE;
  4926. switch( $propName ) {
  4927. case 'ACTION':
  4928. if( !empty( $this->action )) {
  4929. $this->action = '';
  4930. $return = TRUE;
  4931. }
  4932. break;
  4933. case 'ATTACH':
  4934. return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
  4935. break;
  4936. case 'ATTENDEE':
  4937. return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
  4938. break;
  4939. case 'CATEGORIES':
  4940. return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
  4941. break;
  4942. case 'CLASS':
  4943. if( !empty( $this->class )) {
  4944. $this->class = '';
  4945. $return = TRUE;
  4946. }
  4947. break;
  4948. case 'COMMENT':
  4949. return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
  4950. break;
  4951. case 'COMPLETED':
  4952. if( !empty( $this->completed )) {
  4953. $this->completed = '';
  4954. $return = TRUE;
  4955. }
  4956. break;
  4957. case 'CONTACT':
  4958. return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
  4959. break;
  4960. case 'CREATED':
  4961. if( !empty( $this->created )) {
  4962. $this->created = '';
  4963. $return = TRUE;
  4964. }
  4965. break;
  4966. case 'DESCRIPTION':
  4967. return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
  4968. break;
  4969. case 'DTEND':
  4970. if( !empty( $this->dtend )) {
  4971. $this->dtend = '';
  4972. $return = TRUE;
  4973. }
  4974. break;
  4975. case 'DTSTAMP':
  4976. if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
  4977. return FALSE;
  4978. if( !empty( $this->dtstamp )) {
  4979. $this->dtstamp = '';
  4980. $return = TRUE;
  4981. }
  4982. break;
  4983. case 'DTSTART':
  4984. if( !empty( $this->dtstart )) {
  4985. $this->dtstart = '';
  4986. $return = TRUE;
  4987. }
  4988. break;
  4989. case 'DUE':
  4990. if( !empty( $this->due )) {
  4991. $this->due = '';
  4992. $return = TRUE;
  4993. }
  4994. break;
  4995. case 'DURATION':
  4996. if( !empty( $this->duration )) {
  4997. $this->duration = '';
  4998. $return = TRUE;
  4999. }
  5000. break;
  5001. case 'EXDATE':
  5002. return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
  5003. break;
  5004. case 'EXRULE':
  5005. return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
  5006. break;
  5007. case 'FREEBUSY':
  5008. return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
  5009. break;
  5010. case 'GEO':
  5011. if( !empty( $this->geo )) {
  5012. $this->geo = '';
  5013. $return = TRUE;
  5014. }
  5015. break;
  5016. case 'LAST-MODIFIED':
  5017. if( !empty( $this->lastmodified )) {
  5018. $this->lastmodified = '';
  5019. $return = TRUE;
  5020. }
  5021. break;
  5022. case 'LOCATION':
  5023. if( !empty( $this->location )) {
  5024. $this->location = '';
  5025. $return = TRUE;
  5026. }
  5027. break;
  5028. case 'ORGANIZER':
  5029. if( !empty( $this->organizer )) {
  5030. $this->organizer = '';
  5031. $return = TRUE;
  5032. }
  5033. break;
  5034. case 'PERCENT-COMPLETE':
  5035. if( !empty( $this->percentcomplete )) {
  5036. $this->percentcomplete = '';
  5037. $return = TRUE;
  5038. }
  5039. break;
  5040. case 'PRIORITY':
  5041. if( !empty( $this->priority )) {
  5042. $this->priority = '';
  5043. $return = TRUE;
  5044. }
  5045. break;
  5046. case 'RDATE':
  5047. return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
  5048. break;
  5049. case 'RECURRENCE-ID':
  5050. if( !empty( $this->recurrenceid )) {
  5051. $this->recurrenceid = '';
  5052. $return = TRUE;
  5053. }
  5054. break;
  5055. case 'RELATED-TO':
  5056. return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
  5057. break;
  5058. case 'REPEAT':
  5059. if( !empty( $this->repeat )) {
  5060. $this->repeat = '';
  5061. $return = TRUE;
  5062. }
  5063. break;
  5064. case 'REQUEST-STATUS':
  5065. return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
  5066. break;
  5067. case 'RESOURCES':
  5068. return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
  5069. break;
  5070. case 'RRULE':
  5071. return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
  5072. break;
  5073. case 'SEQUENCE':
  5074. if( !empty( $this->sequence )) {
  5075. $this->sequence = '';
  5076. $return = TRUE;
  5077. }
  5078. break;
  5079. case 'STATUS':
  5080. if( !empty( $this->status )) {
  5081. $this->status = '';
  5082. $return = TRUE;
  5083. }
  5084. break;
  5085. case 'SUMMARY':
  5086. if( !empty( $this->summary )) {
  5087. $this->summary = '';
  5088. $return = TRUE;
  5089. }
  5090. break;
  5091. case 'TRANSP':
  5092. if( !empty( $this->transp )) {
  5093. $this->transp = '';
  5094. $return = TRUE;
  5095. }
  5096. break;
  5097. case 'TRIGGER':
  5098. if( !empty( $this->trigger )) {
  5099. $this->trigger = '';
  5100. $return = TRUE;
  5101. }
  5102. break;
  5103. case 'TZID':
  5104. if( !empty( $this->tzid )) {
  5105. $this->tzid = '';
  5106. $return = TRUE;
  5107. }
  5108. break;
  5109. case 'TZNAME':
  5110. return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
  5111. break;
  5112. case 'TZOFFSETFROM':
  5113. if( !empty( $this->tzoffsetfrom )) {
  5114. $this->tzoffsetfrom = '';
  5115. $return = TRUE;
  5116. }
  5117. break;
  5118. case 'TZOFFSETTO':
  5119. if( !empty( $this->tzoffsetto )) {
  5120. $this->tzoffsetto = '';
  5121. $return = TRUE;
  5122. }
  5123. break;
  5124. case 'TZURL':
  5125. if( !empty( $this->tzurl )) {
  5126. $this->tzurl = '';
  5127. $return = TRUE;
  5128. }
  5129. break;
  5130. case 'UID':
  5131. if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
  5132. return FALSE;
  5133. if( ! empty( $this->uid )) {
  5134. $this->uid = '';
  5135. $return = TRUE;
  5136. }
  5137. break;
  5138. case 'URL':
  5139. if( !empty( $this->url )) {
  5140. $this->url = '';
  5141. $return = TRUE;
  5142. }
  5143. break;
  5144. default:
  5145. $reduced = '';
  5146. if( $propName != 'X-PROP' ) {
  5147. if( !isset( $this->xprop[$propName] )) return FALSE;
  5148. foreach( $this->xprop as $k => $a ) {
  5149. if(( $k != $propName ) && !empty( $a ))
  5150. $reduced[$k] = $a;
  5151. }
  5152. }
  5153. else {
  5154. if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
  5155. $xpropno = 0;
  5156. foreach( $this->xprop as $xpropkey => $xpropvalue ) {
  5157. if( $propix != $xpropno )
  5158. $reduced[$xpropkey] = $xpropvalue;
  5159. $xpropno++;
  5160. }
  5161. }
  5162. $this->xprop = $reduced;
  5163. if( empty( $this->xprop )) {
  5164. unset( $this->propdelix[$propName] );
  5165. return FALSE;
  5166. }
  5167. return TRUE;
  5168. }
  5169. return $return;
  5170. }
  5171. /*********************************************************************************/
  5172. /**
  5173. * delete component property value, fixing components with multiple occurencies
  5174. *
  5175. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5176. * @since 2.8.8 - 2011-03-15
  5177. * @param array $multiprop, reference to a component property
  5178. * @param int $propix, reference to removal counter
  5179. * @return bool TRUE
  5180. */
  5181. function deletePropertyM( & $multiprop, & $propix ) {
  5182. if( isset( $multiprop[$propix] ))
  5183. unset( $multiprop[$propix] );
  5184. if( empty( $multiprop )) {
  5185. $multiprop = '';
  5186. unset( $propix );
  5187. return FALSE;
  5188. }
  5189. else
  5190. return TRUE;
  5191. }
  5192. /**
  5193. * get component property value/params
  5194. *
  5195. * if property has multiply values, consequtive function calls are needed
  5196. *
  5197. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5198. * @since 2.19.1 - 2014-02-21
  5199. * @param string $propName, optional
  5200. * @param int @propix, optional, if specific property is wanted in case of multiply occurences
  5201. * @param bool $inclParam=FALSE
  5202. * @param bool $specform=FALSE
  5203. * @return mixed
  5204. */
  5205. function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
  5206. if( 'GEOLOCATION' == strtoupper( $propName )) {
  5207. $content = ( FALSE === ( $loc = $this->getProperty( 'LOCATION' ))) ? '' : $loc.' ';
  5208. if( FALSE === ( $geo = $this->getProperty( 'GEO' )))
  5209. return FALSE;
  5210. return $content.
  5211. iCalUtilityFunctions::_geo2str2( $geo['latitude'], iCalUtilityFunctions::$geoLatFmt ).
  5212. iCalUtilityFunctions::_geo2str2( $geo['longitude'], iCalUtilityFunctions::$geoLongFmt ).'/';
  5213. }
  5214. if( $this->_notExistProp( $propName )) return FALSE;
  5215. $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
  5216. if( in_array( $propName, array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'EXDATE', 'EXRULE',
  5217. 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP' ))) {
  5218. if( !$propix )
  5219. $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
  5220. $this->propix[$propName] = --$propix;
  5221. }
  5222. switch( $propName ) {
  5223. case 'ACTION':
  5224. if( isset( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
  5225. break;
  5226. case 'ATTACH':
  5227. $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
  5228. while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
  5229. $propix++;
  5230. $this->propix[$propName] = $propix;
  5231. if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5232. return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
  5233. break;
  5234. case 'ATTENDEE':
  5235. $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
  5236. while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
  5237. $propix++;
  5238. $this->propix[$propName] = $propix;
  5239. if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5240. return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
  5241. break;
  5242. case 'CATEGORIES':
  5243. $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
  5244. while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
  5245. $propix++;
  5246. $this->propix[$propName] = $propix;
  5247. if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5248. return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
  5249. break;
  5250. case 'CLASS':
  5251. if( isset( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
  5252. break;
  5253. case 'COMMENT':
  5254. $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
  5255. while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
  5256. $propix++;
  5257. $this->propix[$propName] = $propix;
  5258. if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5259. return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
  5260. break;
  5261. case 'COMPLETED':
  5262. if( isset( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
  5263. break;
  5264. case 'CONTACT':
  5265. $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
  5266. while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
  5267. $propix++;
  5268. $this->propix[$propName] = $propix;
  5269. if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5270. return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
  5271. break;
  5272. case 'CREATED':
  5273. if( isset( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
  5274. break;
  5275. case 'DESCRIPTION':
  5276. $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
  5277. while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
  5278. $propix++;
  5279. $this->propix[$propName] = $propix;
  5280. if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5281. return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
  5282. break;
  5283. case 'DTEND':
  5284. if( isset( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
  5285. break;
  5286. case 'DTSTAMP':
  5287. if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
  5288. return;
  5289. if( !isset( $this->dtstamp['value'] ))
  5290. $this->_makeDtstamp();
  5291. return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
  5292. break;
  5293. case 'DTSTART':
  5294. if( isset( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
  5295. break;
  5296. case 'DUE':
  5297. if( isset( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
  5298. break;
  5299. case 'DURATION':
  5300. if( ! isset( $this->duration['value'] )) return FALSE;
  5301. $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
  5302. return ( $inclParam ) ? array( 'value' => $value, 'params' => $this->duration['params'] ) : $value;
  5303. break;
  5304. case 'EXDATE':
  5305. $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
  5306. while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
  5307. $propix++;
  5308. $this->propix[$propName] = $propix;
  5309. if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5310. return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
  5311. break;
  5312. case 'EXRULE':
  5313. $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
  5314. while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
  5315. $propix++;
  5316. $this->propix[$propName] = $propix;
  5317. if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5318. return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
  5319. break;
  5320. case 'FREEBUSY':
  5321. $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
  5322. while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
  5323. $propix++;
  5324. $this->propix[$propName] = $propix;
  5325. if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5326. return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
  5327. break;
  5328. case 'GEO':
  5329. if( isset( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
  5330. break;
  5331. case 'LAST-MODIFIED':
  5332. if( isset( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
  5333. break;
  5334. case 'LOCATION':
  5335. if( isset( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
  5336. break;
  5337. case 'ORGANIZER':
  5338. if( isset( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
  5339. break;
  5340. case 'PERCENT-COMPLETE':
  5341. if( isset( $this->percentcomplete['value'] )) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
  5342. break;
  5343. case 'PRIORITY':
  5344. if( isset( $this->priority['value'] )) return ( $inclParam ) ? $this->priority : $this->priority['value'];
  5345. break;
  5346. case 'RDATE':
  5347. $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
  5348. while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
  5349. $propix++;
  5350. $this->propix[$propName] = $propix;
  5351. if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5352. return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
  5353. break;
  5354. case 'RECURRENCE-ID':
  5355. if( isset( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
  5356. break;
  5357. case 'RELATED-TO':
  5358. $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
  5359. while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
  5360. $propix++;
  5361. $this->propix[$propName] = $propix;
  5362. if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5363. return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
  5364. break;
  5365. case 'REPEAT':
  5366. if( isset( $this->repeat['value'] )) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
  5367. break;
  5368. case 'REQUEST-STATUS':
  5369. $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
  5370. while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
  5371. $propix++;
  5372. $this->propix[$propName] = $propix;
  5373. if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5374. return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
  5375. break;
  5376. case 'RESOURCES':
  5377. $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
  5378. while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
  5379. $propix++;
  5380. $this->propix[$propName] = $propix;
  5381. if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5382. return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
  5383. break;
  5384. case 'RRULE':
  5385. $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
  5386. while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
  5387. $propix++;
  5388. $this->propix[$propName] = $propix;
  5389. if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5390. return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
  5391. break;
  5392. case 'SEQUENCE':
  5393. if( isset( $this->sequence['value'] )) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
  5394. break;
  5395. case 'STATUS':
  5396. if( isset( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
  5397. break;
  5398. case 'SUMMARY':
  5399. if( isset( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
  5400. break;
  5401. case 'TRANSP':
  5402. if( isset( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
  5403. break;
  5404. case 'TRIGGER':
  5405. if( isset( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
  5406. break;
  5407. case 'TZID':
  5408. if( isset( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
  5409. break;
  5410. case 'TZNAME':
  5411. $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
  5412. while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
  5413. $propix++;
  5414. $this->propix[$propName] = $propix;
  5415. if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
  5416. return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
  5417. break;
  5418. case 'TZOFFSETFROM':
  5419. if( isset( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
  5420. break;
  5421. case 'TZOFFSETTO':
  5422. if( isset( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
  5423. break;
  5424. case 'TZURL':
  5425. if( isset( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
  5426. break;
  5427. case 'UID':
  5428. if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
  5429. return FALSE;
  5430. if( empty( $this->uid ))
  5431. $this->_makeuid();
  5432. return ( $inclParam ) ? $this->uid : $this->uid['value'];
  5433. break;
  5434. case 'URL':
  5435. if( isset( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
  5436. break;
  5437. default:
  5438. if( $propName != 'X-PROP' ) {
  5439. if( !isset( $this->xprop[$propName] )) return FALSE;
  5440. return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
  5441. : array( $propName, $this->xprop[$propName]['value'] );
  5442. }
  5443. else {
  5444. if( empty( $this->xprop )) return FALSE;
  5445. $xpropno = 0;
  5446. foreach( $this->xprop as $xpropkey => $xpropvalue ) {
  5447. if( $propix == $xpropno )
  5448. return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
  5449. : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
  5450. else
  5451. $xpropno++;
  5452. }
  5453. return FALSE; // not found ??
  5454. }
  5455. }
  5456. return FALSE;
  5457. }
  5458. /**
  5459. * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence
  5460. *
  5461. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5462. * @since 2.13.4 - 2012-08-07
  5463. * @param string $propName
  5464. * @param array $output, incremented result array
  5465. */
  5466. function _getProperties( $propName, & $output ) {
  5467. if( empty( $output ))
  5468. $output = array();
  5469. if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' )))
  5470. return $output;
  5471. while( FALSE !== ( $content = $this->getProperty( $propName ))) {
  5472. if( empty( $content ))
  5473. continue;
  5474. if( is_array( $content )) {
  5475. foreach( $content as $part ) {
  5476. if( FALSE !== strpos( $part, ',' )) {
  5477. $part = explode( ',', $part );
  5478. foreach( $part as $thePart ) {
  5479. $thePart = trim( $thePart );
  5480. if( !empty( $thePart )) {
  5481. if( !isset( $output[$thePart] ))
  5482. $output[$thePart] = 1;
  5483. else
  5484. $output[$thePart] += 1;
  5485. }
  5486. }
  5487. }
  5488. else {
  5489. $part = trim( $part );
  5490. if( !isset( $output[$part] ))
  5491. $output[$part] = 1;
  5492. else
  5493. $output[$part] += 1;
  5494. }
  5495. }
  5496. } // end if( is_array( $content ))
  5497. elseif( FALSE !== strpos( $content, ',' )) {
  5498. $content = explode( ',', $content );
  5499. foreach( $content as $thePart ) {
  5500. $thePart = trim( $thePart );
  5501. if( !empty( $thePart )) {
  5502. if( !isset( $output[$thePart] ))
  5503. $output[$thePart] = 1;
  5504. else
  5505. $output[$thePart] += 1;
  5506. }
  5507. }
  5508. } // end elseif( FALSE !== strpos( $content, ',' ))
  5509. else {
  5510. $content = trim( $content );
  5511. if( !empty( $content )) {
  5512. if( !isset( $output[$content] ))
  5513. $output[$content] = 1;
  5514. else
  5515. $output[$content] += 1;
  5516. }
  5517. }
  5518. }
  5519. ksort( $output );
  5520. }
  5521. /**
  5522. * general component property setting
  5523. *
  5524. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5525. * @since 2.5.1 - 2008-11-05
  5526. * @param mixed $args variable number of function arguments,
  5527. * first argument is ALWAYS component name,
  5528. * second ALWAYS component value!
  5529. * @return void
  5530. */
  5531. function setProperty() {
  5532. $numargs = func_num_args();
  5533. if( 1 > $numargs ) return FALSE;
  5534. $arglist = func_get_args();
  5535. if( $this->_notExistProp( $arglist[0] )) return FALSE;
  5536. if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
  5537. return FALSE;
  5538. $arglist[0] = strtoupper( $arglist[0] );
  5539. for( $argix=$numargs; $argix < 12; $argix++ ) {
  5540. if( !isset( $arglist[$argix] ))
  5541. $arglist[$argix] = null;
  5542. }
  5543. switch( $arglist[0] ) {
  5544. case 'ACTION':
  5545. return $this->setAction( $arglist[1], $arglist[2] );
  5546. case 'ATTACH':
  5547. return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
  5548. case 'ATTENDEE':
  5549. return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
  5550. case 'CATEGORIES':
  5551. return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
  5552. case 'CLASS':
  5553. return $this->setClass( $arglist[1], $arglist[2] );
  5554. case 'COMMENT':
  5555. return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
  5556. case 'COMPLETED':
  5557. return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
  5558. case 'CONTACT':
  5559. return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
  5560. case 'CREATED':
  5561. return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
  5562. case 'DESCRIPTION':
  5563. return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
  5564. case 'DTEND':
  5565. return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
  5566. case 'DTSTAMP':
  5567. return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
  5568. case 'DTSTART':
  5569. return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
  5570. case 'DUE':
  5571. return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
  5572. case 'DURATION':
  5573. return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
  5574. case 'EXDATE':
  5575. return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
  5576. case 'EXRULE':
  5577. return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
  5578. case 'FREEBUSY':
  5579. return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
  5580. case 'GEO':
  5581. return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
  5582. case 'LAST-MODIFIED':
  5583. return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
  5584. case 'LOCATION':
  5585. return $this->setLocation( $arglist[1], $arglist[2] );
  5586. case 'ORGANIZER':
  5587. return $this->setOrganizer( $arglist[1], $arglist[2] );
  5588. case 'PERCENT-COMPLETE':
  5589. return $this->setPercentComplete( $arglist[1], $arglist[2] );
  5590. case 'PRIORITY':
  5591. return $this->setPriority( $arglist[1], $arglist[2] );
  5592. case 'RDATE':
  5593. return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
  5594. case 'RECURRENCE-ID':
  5595. return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
  5596. case 'RELATED-TO':
  5597. return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
  5598. case 'REPEAT':
  5599. return $this->setRepeat( $arglist[1], $arglist[2] );
  5600. case 'REQUEST-STATUS':
  5601. return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
  5602. case 'RESOURCES':
  5603. return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
  5604. case 'RRULE':
  5605. return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
  5606. case 'SEQUENCE':
  5607. return $this->setSequence( $arglist[1], $arglist[2] );
  5608. case 'STATUS':
  5609. return $this->setStatus( $arglist[1], $arglist[2] );
  5610. case 'SUMMARY':
  5611. return $this->setSummary( $arglist[1], $arglist[2] );
  5612. case 'TRANSP':
  5613. return $this->setTransp( $arglist[1], $arglist[2] );
  5614. case 'TRIGGER':
  5615. return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
  5616. case 'TZID':
  5617. return $this->setTzid( $arglist[1], $arglist[2] );
  5618. case 'TZNAME':
  5619. return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
  5620. case 'TZOFFSETFROM':
  5621. return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
  5622. case 'TZOFFSETTO':
  5623. return $this->setTzoffsetto( $arglist[1], $arglist[2] );
  5624. case 'TZURL':
  5625. return $this->setTzurl( $arglist[1], $arglist[2] );
  5626. case 'UID':
  5627. return $this->setUid( $arglist[1], $arglist[2] );
  5628. case 'URL':
  5629. return $this->setUrl( $arglist[1], $arglist[2] );
  5630. default:
  5631. return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
  5632. }
  5633. return FALSE;
  5634. }
  5635. /*********************************************************************************/
  5636. /**
  5637. * parse component unparsed data into properties
  5638. *
  5639. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5640. * @since 2.18.16 - 2014-04-04
  5641. * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
  5642. * @return bool FALSE if error occurs during parsing
  5643. */
  5644. function parse( $unparsedtext=null ) {
  5645. $nl = $this->getConfig( 'nl' );
  5646. if( !empty( $unparsedtext )) {
  5647. if( is_array( $unparsedtext ))
  5648. $unparsedtext = implode( '\n'.$nl, $unparsedtext );
  5649. $unparsedtext = iCalUtilityFunctions::convEolChar( $unparsedtext, $nl );
  5650. }
  5651. elseif( !isset( $this->unparsed ))
  5652. $unparsedtext = array();
  5653. else
  5654. $unparsedtext = $this->unparsed;
  5655. /* skip leading (empty/invalid) lines */
  5656. foreach( $unparsedtext as $lix => $line ) {
  5657. $tst = trim( $line );
  5658. if(( '\n' == $tst ) || empty( $tst ))
  5659. unset( $unparsedtext[$lix] );
  5660. else
  5661. break;
  5662. }
  5663. $this->unparsed = array();
  5664. $comp = & $this;
  5665. $config = $this->getConfig();
  5666. $compsync = $subsync = 0;
  5667. foreach ( $unparsedtext as $lix => $line ) {
  5668. if( 'END:VALARM' == strtoupper( substr( $line, 0, 10 ))) {
  5669. if( 1 != $subsync ) return FALSE;
  5670. $this->components[] = $comp->copy();
  5671. $subsync--;
  5672. }
  5673. elseif( 'END:DAYLIGHT' == strtoupper( substr( $line, 0, 12 ))) {
  5674. if( 1 != $subsync ) return FALSE;
  5675. $this->components[] = $comp->copy();
  5676. $subsync--;
  5677. }
  5678. elseif( 'END:STANDARD' == strtoupper( substr( $line, 0, 12 ))) {
  5679. if( 1 != $subsync ) return FALSE;
  5680. array_unshift( $this->components, $comp->copy());
  5681. $subsync--;
  5682. }
  5683. elseif( 'END:' == strtoupper( substr( $line, 0, 4 ))) { // end:<component>
  5684. if( 1 != $compsync ) return FALSE;
  5685. if( 0 < $subsync )
  5686. $this->components[] = $comp->copy();
  5687. $compsync--;
  5688. break; /* skip trailing empty lines */
  5689. }
  5690. elseif( 'BEGIN:VALARM' == strtoupper( substr( $line, 0, 12 ))) {
  5691. $comp = new valarm( $config);
  5692. $subsync++;
  5693. }
  5694. elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) {
  5695. $comp = new vtimezone( 'standard', $config );
  5696. $subsync++;
  5697. }
  5698. elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) {
  5699. $comp = new vtimezone( 'daylight', $config );
  5700. $subsync++;
  5701. }
  5702. elseif( 'BEGIN:' == strtoupper( substr( $line, 0, 6 ))) // begin:<component>
  5703. $compsync++;
  5704. else
  5705. $comp->unparsed[] = $line;
  5706. }
  5707. if( 0 < $subsync )
  5708. $this->components[] = $comp->copy();
  5709. unset( $config );
  5710. /* concatenate property values spread over several lines */
  5711. $lastix = -1;
  5712. $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
  5713. , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
  5714. , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
  5715. , 'last-modified', 'location', 'organizer', 'percent-complete'
  5716. , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
  5717. , 'request-status', 'resources', 'rrule', 'sequence', 'status'
  5718. , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
  5719. , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
  5720. $proprows = array();
  5721. for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
  5722. $line = rtrim( $this->unparsed[$i], $nl );
  5723. while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
  5724. $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
  5725. $proprows[] = $line;
  5726. }
  5727. /* parse each property 'line' */
  5728. foreach( $proprows as $line ) {
  5729. if( '\n' == substr( $line, -2 ))
  5730. $line = substr( $line, 0, -2 );
  5731. /* get propname */
  5732. $propname = null;
  5733. $cix = 0;
  5734. while( isset( $line[$cix] )) {
  5735. if( in_array( $line[$cix], array( ':', ';' )))
  5736. break;
  5737. else
  5738. $propname .= $line[$cix];
  5739. $cix++;
  5740. }
  5741. if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
  5742. $propname2 = $propname;
  5743. $propname = 'X-';
  5744. }
  5745. if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names
  5746. continue;
  5747. /* rest of the line is opt.params and value */
  5748. $line = substr( $line, $cix );
  5749. /* separate attributes from value */
  5750. iCalUtilityFunctions::_splitContent( $line, $propAttr );
  5751. /* call setProperty( $propname.. . */
  5752. switch( strtoupper( $propname )) {
  5753. case 'ATTENDEE':
  5754. foreach( $propAttr as $pix => $attr ) {
  5755. if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
  5756. continue;
  5757. $attr2 = explode( ',', $attr );
  5758. if( 1 < count( $attr2 ))
  5759. $propAttr[$pix] = $attr2;
  5760. }
  5761. $this->setProperty( $propname, $line, $propAttr );
  5762. break;
  5763. case 'CATEGORIES':
  5764. case 'RESOURCES':
  5765. if( FALSE !== strpos( $line, ',' )) {
  5766. $content = array( 0 => '' );
  5767. $cix = $lix = 0;
  5768. while( FALSE !== substr( $line, $lix, 1 )) {
  5769. if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
  5770. $cix++;
  5771. $content[$cix] = '';
  5772. }
  5773. else
  5774. $content[$cix] .= $line[$lix];
  5775. $lix++;
  5776. }
  5777. if( 1 < count( $content )) {
  5778. $content = array_values( $content );
  5779. foreach( $content as $cix => $contentPart )
  5780. $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
  5781. $this->setProperty( $propname, $content, $propAttr );
  5782. break;
  5783. }
  5784. else
  5785. $line = reset( $content );
  5786. }
  5787. case 'COMMENT':
  5788. case 'CONTACT':
  5789. case 'DESCRIPTION':
  5790. case 'LOCATION':
  5791. case 'SUMMARY':
  5792. if( empty( $line ))
  5793. $propAttr = null;
  5794. $this->setProperty( $propname, iCalUtilityFunctions::_strunrep( $line ), $propAttr );
  5795. break;
  5796. case 'REQUEST-STATUS':
  5797. $values = explode( ';', $line, 3 );
  5798. $values[1] = ( !isset( $values[1] )) ? null : iCalUtilityFunctions::_strunrep( $values[1] );
  5799. $values[2] = ( !isset( $values[2] )) ? null : iCalUtilityFunctions::_strunrep( $values[2] );
  5800. $this->setProperty( $propname
  5801. , $values[0] // statcode
  5802. , $values[1] // statdesc
  5803. , $values[2] // extdata
  5804. , $propAttr );
  5805. break;
  5806. case 'FREEBUSY':
  5807. $fbtype = ( isset( $propAttr['FBTYPE'] )) ? $propAttr['FBTYPE'] : ''; // force setting default, if missing
  5808. unset( $propAttr['FBTYPE'] );
  5809. $values = explode( ',', $line );
  5810. foreach( $values as $vix => $value ) {
  5811. $value2 = explode( '/', $value );
  5812. if( 1 < count( $value2 ))
  5813. $values[$vix] = $value2;
  5814. }
  5815. $this->setProperty( $propname, $fbtype, $values, $propAttr );
  5816. break;
  5817. case 'GEO':
  5818. $value = explode( ';', $line, 2 );
  5819. if( 2 > count( $value ))
  5820. $value[1] = null;
  5821. $this->setProperty( $propname, $value[0], $value[1], $propAttr );
  5822. break;
  5823. case 'EXDATE':
  5824. $values = ( !empty( $line )) ? explode( ',', $line ) : null;
  5825. $this->setProperty( $propname, $values, $propAttr );
  5826. break;
  5827. case 'RDATE':
  5828. if( empty( $line )) {
  5829. $this->setProperty( $propname, $line, $propAttr );
  5830. break;
  5831. }
  5832. $values = explode( ',', $line );
  5833. foreach( $values as $vix => $value ) {
  5834. $value2 = explode( '/', $value );
  5835. if( 1 < count( $value2 ))
  5836. $values[$vix] = $value2;
  5837. }
  5838. $this->setProperty( $propname, $values, $propAttr );
  5839. break;
  5840. case 'EXRULE':
  5841. case 'RRULE':
  5842. $values = explode( ';', $line );
  5843. $recur = array();
  5844. foreach( $values as $value2 ) {
  5845. if( empty( $value2 ))
  5846. continue; // ;-char in ending position ???
  5847. $value3 = explode( '=', $value2, 2 );
  5848. $rulelabel = strtoupper( $value3[0] );
  5849. switch( $rulelabel ) {
  5850. case 'BYDAY': {
  5851. $value4 = explode( ',', $value3[1] );
  5852. if( 1 < count( $value4 )) {
  5853. foreach( $value4 as $v5ix => $value5 ) {
  5854. $value6 = array();
  5855. $dayno = $dayname = null;
  5856. $value5 = trim( (string) $value5 );
  5857. if(( ctype_alpha( substr( $value5, -1 ))) &&
  5858. ( ctype_alpha( substr( $value5, -2, 1 )))) {
  5859. $dayname = substr( $value5, -2, 2 );
  5860. if( 2 < strlen( $value5 ))
  5861. $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
  5862. }
  5863. if( $dayno )
  5864. $value6[] = $dayno;
  5865. if( $dayname )
  5866. $value6['DAY'] = $dayname;
  5867. $value4[$v5ix] = $value6;
  5868. }
  5869. }
  5870. else {
  5871. $value4 = array();
  5872. $dayno = $dayname = null;
  5873. $value5 = trim( (string) $value3[1] );
  5874. if(( ctype_alpha( substr( $value5, -1 ))) &&
  5875. ( ctype_alpha( substr( $value5, -2, 1 )))) {
  5876. $dayname = substr( $value5, -2, 2 );
  5877. if( 2 < strlen( $value5 ))
  5878. $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
  5879. }
  5880. if( $dayno )
  5881. $value4[] = $dayno;
  5882. if( $dayname )
  5883. $value4['DAY'] = $dayname;
  5884. }
  5885. $recur[$rulelabel] = $value4;
  5886. break;
  5887. }
  5888. default: {
  5889. $value4 = explode( ',', $value3[1] );
  5890. if( 1 < count( $value4 ))
  5891. $value3[1] = $value4;
  5892. $recur[$rulelabel] = $value3[1];
  5893. break;
  5894. }
  5895. } // end - switch $rulelabel
  5896. } // end - foreach( $values.. .
  5897. $this->setProperty( $propname, $recur, $propAttr );
  5898. break;
  5899. case 'X-':
  5900. $propname = ( isset( $propname2 )) ? $propname2 : $propname;
  5901. unset( $propname2 );
  5902. case 'ACTION':
  5903. case 'CLASSIFICATION':
  5904. case 'STATUS':
  5905. case 'TRANSP':
  5906. case 'UID':
  5907. case 'TZID':
  5908. case 'RELATED-TO':
  5909. case 'TZNAME':
  5910. $line = iCalUtilityFunctions::_strunrep( $line );
  5911. default:
  5912. $this->setProperty( $propname, $line, $propAttr );
  5913. break;
  5914. } // end switch( $propname.. .
  5915. } // end - foreach( $proprows.. .
  5916. unset( $unparsedtext, $this->unparsed, $proprows );
  5917. if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
  5918. $ckeys = array_keys( $this->components );
  5919. foreach( $ckeys as $ckey ) {
  5920. if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
  5921. $this->components[$ckey]->parse();
  5922. }
  5923. }
  5924. }
  5925. return TRUE;
  5926. }
  5927. /*********************************************************************************/
  5928. /*********************************************************************************/
  5929. /**
  5930. * return a copy of this component
  5931. *
  5932. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5933. * @since 2.15.4 - 2012-10-18
  5934. * @return object
  5935. */
  5936. function copy() {
  5937. return unserialize( serialize( $this ));
  5938. }
  5939. /*********************************************************************************/
  5940. /*********************************************************************************/
  5941. /**
  5942. * delete calendar subcomponent from component container
  5943. *
  5944. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5945. * @since 2.8.8 - 2011-03-15
  5946. * @param mixed $arg1 ordno / component type / component uid
  5947. * @param mixed $arg2 optional, ordno if arg1 = component type
  5948. * @return void
  5949. */
  5950. function deleteComponent( $arg1, $arg2=FALSE ) {
  5951. if( !isset( $this->components )) return FALSE;
  5952. $argType = $index = null;
  5953. if ( ctype_digit( (string) $arg1 )) {
  5954. $argType = 'INDEX';
  5955. $index = (int) $arg1 - 1;
  5956. }
  5957. elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
  5958. $argType = strtolower( $arg1 );
  5959. $index = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
  5960. }
  5961. $cix2dC = 0;
  5962. foreach ( $this->components as $cix => $component) {
  5963. if( empty( $component )) continue;
  5964. if(( 'INDEX' == $argType ) && ( $index == $cix )) {
  5965. unset( $this->components[$cix] );
  5966. return TRUE;
  5967. }
  5968. elseif( $argType == $component->objName ) {
  5969. if( $index == $cix2dC ) {
  5970. unset( $this->components[$cix] );
  5971. return TRUE;
  5972. }
  5973. $cix2dC++;
  5974. }
  5975. elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
  5976. unset( $this->components[$cix] );
  5977. return TRUE;
  5978. }
  5979. }
  5980. return FALSE;
  5981. }
  5982. /**
  5983. * get calendar component subcomponent from component container
  5984. *
  5985. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  5986. * @since 2.8.8 - 2011-03-15
  5987. * @param mixed $arg1 optional, ordno/component type/ component uid
  5988. * @param mixed $arg2 optional, ordno if arg1 = component type
  5989. * @return object
  5990. */
  5991. function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
  5992. if( !isset( $this->components )) return FALSE;
  5993. $index = $argType = null;
  5994. if ( !$arg1 ) {
  5995. $argType = 'INDEX';
  5996. $index = $this->compix['INDEX'] =
  5997. ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
  5998. }
  5999. elseif ( ctype_digit( (string) $arg1 )) {
  6000. $argType = 'INDEX';
  6001. $index = (int) $arg1;
  6002. unset( $this->compix );
  6003. }
  6004. elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
  6005. unset( $this->compix['INDEX'] );
  6006. $argType = strtolower( $arg1 );
  6007. if( !$arg2 )
  6008. $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
  6009. else
  6010. $index = (int) $arg2;
  6011. }
  6012. $index -= 1;
  6013. $ckeys = array_keys( $this->components );
  6014. if( !empty( $index) && ( $index > end( $ckeys )))
  6015. return FALSE;
  6016. $cix2gC = 0;
  6017. foreach( $this->components as $cix => $component ) {
  6018. if( empty( $component )) continue;
  6019. if(( 'INDEX' == $argType ) && ( $index == $cix ))
  6020. return $component->copy();
  6021. elseif( $argType == $component->objName ) {
  6022. if( $index == $cix2gC )
  6023. return $component->copy();
  6024. $cix2gC++;
  6025. }
  6026. elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
  6027. return $component->copy();
  6028. }
  6029. /* not found.. . */
  6030. unset( $this->compix );
  6031. return false;
  6032. }
  6033. /**
  6034. * add calendar component as subcomponent to container for subcomponents
  6035. *
  6036. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6037. * @since 1.x.x - 2007-04-24
  6038. * @param object $component calendar component
  6039. * @return void
  6040. */
  6041. function addSubComponent ( $component ) {
  6042. $this->setComponent( $component );
  6043. }
  6044. /**
  6045. * create new calendar component subcomponent, already included within component
  6046. *
  6047. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6048. * @since 2.6.33 - 2011-01-03
  6049. * @param string $compType subcomponent type
  6050. * @return object (reference)
  6051. */
  6052. function & newComponent( $compType ) {
  6053. $config = $this->getConfig();
  6054. $keys = array_keys( $this->components );
  6055. $ix = end( $keys) + 1;
  6056. switch( strtoupper( $compType )) {
  6057. case 'ALARM':
  6058. case 'VALARM':
  6059. $this->components[$ix] = new valarm( $config );
  6060. break;
  6061. case 'STANDARD':
  6062. array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
  6063. $ix = 0;
  6064. break;
  6065. case 'DAYLIGHT':
  6066. $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
  6067. break;
  6068. default:
  6069. return FALSE;
  6070. }
  6071. return $this->components[$ix];
  6072. }
  6073. /**
  6074. * add calendar component as subcomponent to container for subcomponents
  6075. *
  6076. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6077. * @since 2.8.8 - 2011-03-15
  6078. * @param object $component calendar component
  6079. * @param mixed $arg1 optional, ordno/component type/ component uid
  6080. * @param mixed $arg2 optional, ordno if arg1 = component type
  6081. * @return bool
  6082. */
  6083. function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) {
  6084. if( !isset( $this->components )) return FALSE;
  6085. $component->setConfig( $this->getConfig(), FALSE, TRUE );
  6086. if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
  6087. /* make sure dtstamp and uid is set */
  6088. $dummy = $component->getProperty( 'dtstamp' );
  6089. $dummy = $component->getProperty( 'uid' );
  6090. }
  6091. if( !$arg1 ) { // plain insert, last in chain
  6092. $this->components[] = $component->copy();
  6093. return TRUE;
  6094. }
  6095. $argType = $index = null;
  6096. if ( ctype_digit( (string) $arg1 )) { // index insert/replace
  6097. $argType = 'INDEX';
  6098. $index = (int) $arg1 - 1;
  6099. }
  6100. elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
  6101. $argType = strtolower( $arg1 );
  6102. $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
  6103. }
  6104. // else if arg1 is set, arg1 must be an UID
  6105. $cix2sC = 0;
  6106. foreach ( $this->components as $cix => $component2 ) {
  6107. if( empty( $component2 )) continue;
  6108. if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
  6109. $this->components[$cix] = $component->copy();
  6110. return TRUE;
  6111. }
  6112. elseif( $argType == $component2->objName ) { // component Type index insert/replace
  6113. if( $index == $cix2sC ) {
  6114. $this->components[$cix] = $component->copy();
  6115. return TRUE;
  6116. }
  6117. $cix2sC++;
  6118. }
  6119. elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
  6120. $this->components[$cix] = $component->copy();
  6121. return TRUE;
  6122. }
  6123. }
  6124. /* arg1=index and not found.. . insert at index .. .*/
  6125. if( 'INDEX' == $argType ) {
  6126. $this->components[$index] = $component->copy();
  6127. ksort( $this->components, SORT_NUMERIC );
  6128. }
  6129. else /* not found.. . insert last in chain anyway .. .*/
  6130. $this->components[] = $component->copy();
  6131. return TRUE;
  6132. }
  6133. /**
  6134. * creates formatted output for subcomponents
  6135. *
  6136. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6137. * @since 2.11.20 - 2012-02-06
  6138. * @param array $xcaldecl
  6139. * @return string
  6140. */
  6141. function createSubComponent() {
  6142. $output = null;
  6143. if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
  6144. $stdarr = $dlarr = array();
  6145. foreach( $this->components as $component ) {
  6146. if( empty( $component ))
  6147. continue;
  6148. $dt = $component->getProperty( 'dtstart' );
  6149. $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
  6150. if( 'standard' == $component->objName ) {
  6151. while( isset( $stdarr[$key] ))
  6152. $key += 1;
  6153. $stdarr[$key] = $component->copy();
  6154. }
  6155. elseif( 'daylight' == $component->objName ) {
  6156. while( isset( $dlarr[$key] ))
  6157. $key += 1;
  6158. $dlarr[$key] = $component->copy();
  6159. }
  6160. } // end foreach( $this->components as $component )
  6161. $this->components = array();
  6162. ksort( $stdarr, SORT_NUMERIC );
  6163. foreach( $stdarr as $std )
  6164. $this->components[] = $std->copy();
  6165. unset( $stdarr );
  6166. ksort( $dlarr, SORT_NUMERIC );
  6167. foreach( $dlarr as $dl )
  6168. $this->components[] = $dl->copy();
  6169. unset( $dlarr );
  6170. } // end if( 'vtimezone' == $this->objName )
  6171. foreach( $this->components as $component ) {
  6172. $component->setConfig( $this->getConfig(), FALSE, TRUE );
  6173. $output .= $component->createComponent( $this->xcaldecl );
  6174. }
  6175. return $output;
  6176. }
  6177. }
  6178. /*********************************************************************************/
  6179. /*********************************************************************************/
  6180. /**
  6181. * class for calendar component VEVENT
  6182. *
  6183. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6184. * @since 2.5.1 - 2008-10-12
  6185. */
  6186. class vevent extends calendarComponent {
  6187. var $attach;
  6188. var $attendee;
  6189. var $categories;
  6190. var $comment;
  6191. var $contact;
  6192. var $class;
  6193. var $created;
  6194. var $description;
  6195. var $dtend;
  6196. var $dtstart;
  6197. var $duration;
  6198. var $exdate;
  6199. var $exrule;
  6200. var $geo;
  6201. var $lastmodified;
  6202. var $location;
  6203. var $organizer;
  6204. var $priority;
  6205. var $rdate;
  6206. var $recurrenceid;
  6207. var $relatedto;
  6208. var $requeststatus;
  6209. var $resources;
  6210. var $rrule;
  6211. var $sequence;
  6212. var $status;
  6213. var $summary;
  6214. var $transp;
  6215. var $url;
  6216. var $xprop;
  6217. // component subcomponents container
  6218. var $components;
  6219. /**
  6220. * constructor for calendar component VEVENT object
  6221. *
  6222. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6223. * @since 2.8.2 - 2011-05-01
  6224. * @param array $config
  6225. * @return void
  6226. */
  6227. function vevent( $config = array()) {
  6228. $this->calendarComponent();
  6229. $this->attach = '';
  6230. $this->attendee = '';
  6231. $this->categories = '';
  6232. $this->class = '';
  6233. $this->comment = '';
  6234. $this->contact = '';
  6235. $this->created = '';
  6236. $this->description = '';
  6237. $this->dtstart = '';
  6238. $this->dtend = '';
  6239. $this->duration = '';
  6240. $this->exdate = '';
  6241. $this->exrule = '';
  6242. $this->geo = '';
  6243. $this->lastmodified = '';
  6244. $this->location = '';
  6245. $this->organizer = '';
  6246. $this->priority = '';
  6247. $this->rdate = '';
  6248. $this->recurrenceid = '';
  6249. $this->relatedto = '';
  6250. $this->requeststatus = '';
  6251. $this->resources = '';
  6252. $this->rrule = '';
  6253. $this->sequence = '';
  6254. $this->status = '';
  6255. $this->summary = '';
  6256. $this->transp = '';
  6257. $this->url = '';
  6258. $this->xprop = '';
  6259. $this->components = array();
  6260. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6261. $config['language'] = ICAL_LANG;
  6262. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6263. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6264. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6265. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6266. $this->setConfig( $config );
  6267. }
  6268. /**
  6269. * create formatted output for calendar component VEVENT object instance
  6270. *
  6271. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6272. * @since 2.10.16 - 2011-10-28
  6273. * @param array $xcaldecl
  6274. * @return string
  6275. */
  6276. function createComponent( &$xcaldecl ) {
  6277. $objectname = $this->_createFormat();
  6278. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6279. $component .= $this->createUid();
  6280. $component .= $this->createDtstamp();
  6281. $component .= $this->createAttach();
  6282. $component .= $this->createAttendee();
  6283. $component .= $this->createCategories();
  6284. $component .= $this->createComment();
  6285. $component .= $this->createContact();
  6286. $component .= $this->createClass();
  6287. $component .= $this->createCreated();
  6288. $component .= $this->createDescription();
  6289. $component .= $this->createDtstart();
  6290. $component .= $this->createDtend();
  6291. $component .= $this->createDuration();
  6292. $component .= $this->createExdate();
  6293. $component .= $this->createExrule();
  6294. $component .= $this->createGeo();
  6295. $component .= $this->createLastModified();
  6296. $component .= $this->createLocation();
  6297. $component .= $this->createOrganizer();
  6298. $component .= $this->createPriority();
  6299. $component .= $this->createRdate();
  6300. $component .= $this->createRrule();
  6301. $component .= $this->createRelatedTo();
  6302. $component .= $this->createRequestStatus();
  6303. $component .= $this->createRecurrenceid();
  6304. $component .= $this->createResources();
  6305. $component .= $this->createSequence();
  6306. $component .= $this->createStatus();
  6307. $component .= $this->createSummary();
  6308. $component .= $this->createTransp();
  6309. $component .= $this->createUrl();
  6310. $component .= $this->createXprop();
  6311. $component .= $this->createSubComponent();
  6312. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6313. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6314. foreach( $this->xcaldecl as $localxcaldecl )
  6315. $xcaldecl[] = $localxcaldecl;
  6316. }
  6317. return $component;
  6318. }
  6319. }
  6320. /*********************************************************************************/
  6321. /*********************************************************************************/
  6322. /**
  6323. * class for calendar component VTODO
  6324. *
  6325. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6326. * @since 2.5.1 - 2008-10-12
  6327. */
  6328. class vtodo extends calendarComponent {
  6329. var $attach;
  6330. var $attendee;
  6331. var $categories;
  6332. var $comment;
  6333. var $completed;
  6334. var $contact;
  6335. var $class;
  6336. var $created;
  6337. var $description;
  6338. var $dtstart;
  6339. var $due;
  6340. var $duration;
  6341. var $exdate;
  6342. var $exrule;
  6343. var $geo;
  6344. var $lastmodified;
  6345. var $location;
  6346. var $organizer;
  6347. var $percentcomplete;
  6348. var $priority;
  6349. var $rdate;
  6350. var $recurrenceid;
  6351. var $relatedto;
  6352. var $requeststatus;
  6353. var $resources;
  6354. var $rrule;
  6355. var $sequence;
  6356. var $status;
  6357. var $summary;
  6358. var $url;
  6359. var $xprop;
  6360. // component subcomponents container
  6361. var $components;
  6362. /**
  6363. * constructor for calendar component VTODO object
  6364. *
  6365. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6366. * @since 2.8.2 - 2011-05-01
  6367. * @param array $config
  6368. * @return void
  6369. */
  6370. function vtodo( $config = array()) {
  6371. $this->calendarComponent();
  6372. $this->attach = '';
  6373. $this->attendee = '';
  6374. $this->categories = '';
  6375. $this->class = '';
  6376. $this->comment = '';
  6377. $this->completed = '';
  6378. $this->contact = '';
  6379. $this->created = '';
  6380. $this->description = '';
  6381. $this->dtstart = '';
  6382. $this->due = '';
  6383. $this->duration = '';
  6384. $this->exdate = '';
  6385. $this->exrule = '';
  6386. $this->geo = '';
  6387. $this->lastmodified = '';
  6388. $this->location = '';
  6389. $this->organizer = '';
  6390. $this->percentcomplete = '';
  6391. $this->priority = '';
  6392. $this->rdate = '';
  6393. $this->recurrenceid = '';
  6394. $this->relatedto = '';
  6395. $this->requeststatus = '';
  6396. $this->resources = '';
  6397. $this->rrule = '';
  6398. $this->sequence = '';
  6399. $this->status = '';
  6400. $this->summary = '';
  6401. $this->url = '';
  6402. $this->xprop = '';
  6403. $this->components = array();
  6404. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6405. $config['language'] = ICAL_LANG;
  6406. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6407. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6408. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6409. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6410. $this->setConfig( $config );
  6411. }
  6412. /**
  6413. * create formatted output for calendar component VTODO object instance
  6414. *
  6415. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6416. * @since 2.5.1 - 2008-11-07
  6417. * @param array $xcaldecl
  6418. * @return string
  6419. */
  6420. function createComponent( &$xcaldecl ) {
  6421. $objectname = $this->_createFormat();
  6422. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6423. $component .= $this->createUid();
  6424. $component .= $this->createDtstamp();
  6425. $component .= $this->createAttach();
  6426. $component .= $this->createAttendee();
  6427. $component .= $this->createCategories();
  6428. $component .= $this->createClass();
  6429. $component .= $this->createComment();
  6430. $component .= $this->createCompleted();
  6431. $component .= $this->createContact();
  6432. $component .= $this->createCreated();
  6433. $component .= $this->createDescription();
  6434. $component .= $this->createDtstart();
  6435. $component .= $this->createDue();
  6436. $component .= $this->createDuration();
  6437. $component .= $this->createExdate();
  6438. $component .= $this->createExrule();
  6439. $component .= $this->createGeo();
  6440. $component .= $this->createLastModified();
  6441. $component .= $this->createLocation();
  6442. $component .= $this->createOrganizer();
  6443. $component .= $this->createPercentComplete();
  6444. $component .= $this->createPriority();
  6445. $component .= $this->createRdate();
  6446. $component .= $this->createRelatedTo();
  6447. $component .= $this->createRequestStatus();
  6448. $component .= $this->createRecurrenceid();
  6449. $component .= $this->createResources();
  6450. $component .= $this->createRrule();
  6451. $component .= $this->createSequence();
  6452. $component .= $this->createStatus();
  6453. $component .= $this->createSummary();
  6454. $component .= $this->createUrl();
  6455. $component .= $this->createXprop();
  6456. $component .= $this->createSubComponent();
  6457. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6458. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6459. foreach( $this->xcaldecl as $localxcaldecl )
  6460. $xcaldecl[] = $localxcaldecl;
  6461. }
  6462. return $component;
  6463. }
  6464. }
  6465. /*********************************************************************************/
  6466. /*********************************************************************************/
  6467. /**
  6468. * class for calendar component VJOURNAL
  6469. *
  6470. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6471. * @since 2.5.1 - 2008-10-12
  6472. */
  6473. class vjournal extends calendarComponent {
  6474. var $attach;
  6475. var $attendee;
  6476. var $categories;
  6477. var $comment;
  6478. var $contact;
  6479. var $class;
  6480. var $created;
  6481. var $description;
  6482. var $dtstart;
  6483. var $exdate;
  6484. var $exrule;
  6485. var $lastmodified;
  6486. var $organizer;
  6487. var $rdate;
  6488. var $recurrenceid;
  6489. var $relatedto;
  6490. var $requeststatus;
  6491. var $rrule;
  6492. var $sequence;
  6493. var $status;
  6494. var $summary;
  6495. var $url;
  6496. var $xprop;
  6497. /**
  6498. * constructor for calendar component VJOURNAL object
  6499. *
  6500. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6501. * @since 2.8.2 - 2011-05-01
  6502. * @param array $config
  6503. * @return void
  6504. */
  6505. function vjournal( $config = array()) {
  6506. $this->calendarComponent();
  6507. $this->attach = '';
  6508. $this->attendee = '';
  6509. $this->categories = '';
  6510. $this->class = '';
  6511. $this->comment = '';
  6512. $this->contact = '';
  6513. $this->created = '';
  6514. $this->description = '';
  6515. $this->dtstart = '';
  6516. $this->exdate = '';
  6517. $this->exrule = '';
  6518. $this->lastmodified = '';
  6519. $this->organizer = '';
  6520. $this->rdate = '';
  6521. $this->recurrenceid = '';
  6522. $this->relatedto = '';
  6523. $this->requeststatus = '';
  6524. $this->rrule = '';
  6525. $this->sequence = '';
  6526. $this->status = '';
  6527. $this->summary = '';
  6528. $this->url = '';
  6529. $this->xprop = '';
  6530. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6531. $config['language'] = ICAL_LANG;
  6532. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6533. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6534. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6535. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6536. $this->setConfig( $config );
  6537. }
  6538. /**
  6539. * create formatted output for calendar component VJOURNAL object instance
  6540. *
  6541. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6542. * @since 2.5.1 - 2008-10-12
  6543. * @param array $xcaldecl
  6544. * @return string
  6545. */
  6546. function createComponent( &$xcaldecl ) {
  6547. $objectname = $this->_createFormat();
  6548. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6549. $component .= $this->createUid();
  6550. $component .= $this->createDtstamp();
  6551. $component .= $this->createAttach();
  6552. $component .= $this->createAttendee();
  6553. $component .= $this->createCategories();
  6554. $component .= $this->createClass();
  6555. $component .= $this->createComment();
  6556. $component .= $this->createContact();
  6557. $component .= $this->createCreated();
  6558. $component .= $this->createDescription();
  6559. $component .= $this->createDtstart();
  6560. $component .= $this->createExdate();
  6561. $component .= $this->createExrule();
  6562. $component .= $this->createLastModified();
  6563. $component .= $this->createOrganizer();
  6564. $component .= $this->createRdate();
  6565. $component .= $this->createRequestStatus();
  6566. $component .= $this->createRecurrenceid();
  6567. $component .= $this->createRelatedTo();
  6568. $component .= $this->createRrule();
  6569. $component .= $this->createSequence();
  6570. $component .= $this->createStatus();
  6571. $component .= $this->createSummary();
  6572. $component .= $this->createUrl();
  6573. $component .= $this->createXprop();
  6574. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6575. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6576. foreach( $this->xcaldecl as $localxcaldecl )
  6577. $xcaldecl[] = $localxcaldecl;
  6578. }
  6579. return $component;
  6580. }
  6581. }
  6582. /*********************************************************************************/
  6583. /*********************************************************************************/
  6584. /**
  6585. * class for calendar component VFREEBUSY
  6586. *
  6587. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6588. * @since 2.5.1 - 2008-10-12
  6589. */
  6590. class vfreebusy extends calendarComponent {
  6591. var $attendee;
  6592. var $comment;
  6593. var $contact;
  6594. var $dtend;
  6595. var $dtstart;
  6596. var $duration;
  6597. var $freebusy;
  6598. var $organizer;
  6599. var $requeststatus;
  6600. var $url;
  6601. var $xprop;
  6602. // component subcomponents container
  6603. var $components;
  6604. /**
  6605. * constructor for calendar component VFREEBUSY object
  6606. *
  6607. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6608. * @since 2.8.2 - 2011-05-01
  6609. * @param array $config
  6610. * @return void
  6611. */
  6612. function vfreebusy( $config = array()) {
  6613. $this->calendarComponent();
  6614. $this->attendee = '';
  6615. $this->comment = '';
  6616. $this->contact = '';
  6617. $this->dtend = '';
  6618. $this->dtstart = '';
  6619. $this->duration = '';
  6620. $this->freebusy = '';
  6621. $this->organizer = '';
  6622. $this->requeststatus = '';
  6623. $this->url = '';
  6624. $this->xprop = '';
  6625. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6626. $config['language'] = ICAL_LANG;
  6627. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6628. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6629. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6630. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6631. $this->setConfig( $config );
  6632. }
  6633. /**
  6634. * create formatted output for calendar component VFREEBUSY object instance
  6635. *
  6636. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6637. * @since 2.3.1 - 2007-11-19
  6638. * @param array $xcaldecl
  6639. * @return string
  6640. */
  6641. function createComponent( &$xcaldecl ) {
  6642. $objectname = $this->_createFormat();
  6643. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6644. $component .= $this->createUid();
  6645. $component .= $this->createDtstamp();
  6646. $component .= $this->createAttendee();
  6647. $component .= $this->createComment();
  6648. $component .= $this->createContact();
  6649. $component .= $this->createDtstart();
  6650. $component .= $this->createDtend();
  6651. $component .= $this->createDuration();
  6652. $component .= $this->createFreebusy();
  6653. $component .= $this->createOrganizer();
  6654. $component .= $this->createRequestStatus();
  6655. $component .= $this->createUrl();
  6656. $component .= $this->createXprop();
  6657. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6658. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6659. foreach( $this->xcaldecl as $localxcaldecl )
  6660. $xcaldecl[] = $localxcaldecl;
  6661. }
  6662. return $component;
  6663. }
  6664. }
  6665. /*********************************************************************************/
  6666. /*********************************************************************************/
  6667. /**
  6668. * class for calendar component VALARM
  6669. *
  6670. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6671. * @since 2.5.1 - 2008-10-12
  6672. */
  6673. class valarm extends calendarComponent {
  6674. var $action;
  6675. var $attach;
  6676. var $attendee;
  6677. var $description;
  6678. var $duration;
  6679. var $repeat;
  6680. var $summary;
  6681. var $trigger;
  6682. var $xprop;
  6683. /**
  6684. * constructor for calendar component VALARM object
  6685. *
  6686. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6687. * @since 2.8.2 - 2011-05-01
  6688. * @param array $config
  6689. * @return void
  6690. */
  6691. function valarm( $config = array()) {
  6692. $this->calendarComponent();
  6693. $this->action = '';
  6694. $this->attach = '';
  6695. $this->attendee = '';
  6696. $this->description = '';
  6697. $this->duration = '';
  6698. $this->repeat = '';
  6699. $this->summary = '';
  6700. $this->trigger = '';
  6701. $this->xprop = '';
  6702. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6703. $config['language'] = ICAL_LANG;
  6704. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6705. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6706. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6707. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6708. $this->setConfig( $config );
  6709. }
  6710. /**
  6711. * create formatted output for calendar component VALARM object instance
  6712. *
  6713. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6714. * @since 2.5.1 - 2008-10-22
  6715. * @param array $xcaldecl
  6716. * @return string
  6717. */
  6718. function createComponent( &$xcaldecl ) {
  6719. $objectname = $this->_createFormat();
  6720. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6721. $component .= $this->createAction();
  6722. $component .= $this->createAttach();
  6723. $component .= $this->createAttendee();
  6724. $component .= $this->createDescription();
  6725. $component .= $this->createDuration();
  6726. $component .= $this->createRepeat();
  6727. $component .= $this->createSummary();
  6728. $component .= $this->createTrigger();
  6729. $component .= $this->createXprop();
  6730. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6731. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6732. foreach( $this->xcaldecl as $localxcaldecl )
  6733. $xcaldecl[] = $localxcaldecl;
  6734. }
  6735. return $component;
  6736. }
  6737. }
  6738. /**********************************************************************************
  6739. /*********************************************************************************/
  6740. /**
  6741. * class for calendar component VTIMEZONE
  6742. *
  6743. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6744. * @since 2.5.1 - 2008-10-12
  6745. */
  6746. class vtimezone extends calendarComponent {
  6747. var $timezonetype;
  6748. var $comment;
  6749. var $dtstart;
  6750. var $lastmodified;
  6751. var $rdate;
  6752. var $rrule;
  6753. var $tzid;
  6754. var $tzname;
  6755. var $tzoffsetfrom;
  6756. var $tzoffsetto;
  6757. var $tzurl;
  6758. var $xprop;
  6759. // component subcomponents container
  6760. var $components;
  6761. /**
  6762. * constructor for calendar component VTIMEZONE object
  6763. *
  6764. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6765. * @since 2.8.2 - 2011-05-01
  6766. * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
  6767. * @param array $config
  6768. * @return void
  6769. */
  6770. function vtimezone( $timezonetype=FALSE, $config = array()) {
  6771. if( is_array( $timezonetype )) {
  6772. $config = $timezonetype;
  6773. $timezonetype = FALSE;
  6774. }
  6775. if( !$timezonetype )
  6776. $this->timezonetype = 'VTIMEZONE';
  6777. else
  6778. $this->timezonetype = strtoupper( $timezonetype );
  6779. $this->calendarComponent();
  6780. $this->comment = '';
  6781. $this->dtstart = '';
  6782. $this->lastmodified = '';
  6783. $this->rdate = '';
  6784. $this->rrule = '';
  6785. $this->tzid = '';
  6786. $this->tzname = '';
  6787. $this->tzoffsetfrom = '';
  6788. $this->tzoffsetto = '';
  6789. $this->tzurl = '';
  6790. $this->xprop = '';
  6791. $this->components = array();
  6792. if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
  6793. $config['language'] = ICAL_LANG;
  6794. if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
  6795. if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
  6796. if( !isset( $config['format'] )) $config['format'] = 'iCal';
  6797. if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
  6798. $this->setConfig( $config );
  6799. }
  6800. /**
  6801. * create formatted output for calendar component VTIMEZONE object instance
  6802. *
  6803. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6804. * @since 2.5.1 - 2008-10-25
  6805. * @param array $xcaldecl
  6806. * @return string
  6807. */
  6808. function createComponent( &$xcaldecl ) {
  6809. $objectname = $this->_createFormat();
  6810. $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
  6811. $component .= $this->createTzid();
  6812. $component .= $this->createLastModified();
  6813. $component .= $this->createTzurl();
  6814. $component .= $this->createDtstart();
  6815. $component .= $this->createTzoffsetfrom();
  6816. $component .= $this->createTzoffsetto();
  6817. $component .= $this->createComment();
  6818. $component .= $this->createRdate();
  6819. $component .= $this->createRrule();
  6820. $component .= $this->createTzname();
  6821. $component .= $this->createXprop();
  6822. $component .= $this->createSubComponent();
  6823. $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
  6824. if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
  6825. foreach( $this->xcaldecl as $localxcaldecl )
  6826. $xcaldecl[] = $localxcaldecl;
  6827. }
  6828. return $component;
  6829. }
  6830. }
  6831. /*********************************************************************************/
  6832. /*********************************************************************************/
  6833. /**
  6834. * moving all utility (static) functions to a utility class
  6835. * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
  6836. *
  6837. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6838. * @since 2.18.10 - 2013-09-02
  6839. */
  6840. class iCalUtilityFunctions {
  6841. // Store the single instance of iCalUtilityFunctions
  6842. private static $m_pInstance;
  6843. // Private constructor to limit object instantiation to within the class
  6844. private function __construct() {
  6845. $m_pInstance = FALSE;
  6846. }
  6847. // tmp line delimiter, used in convEolChar (parse)
  6848. private static $baseDelim = null;
  6849. // output format for geo latitude and longitude (before rtrim)
  6850. public static $geoLatFmt = '%09.6f';
  6851. public static $geoLongFmt = '%8.6f';
  6852. // Getter method for creating/returning the single instance of this class
  6853. public static function getInstance() {
  6854. if (!self::$m_pInstance)
  6855. self::$m_pInstance = new iCalUtilityFunctions();
  6856. return self::$m_pInstance;
  6857. }
  6858. /**
  6859. * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
  6860. *
  6861. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6862. * @since 2.16.24 - 2013-06-26
  6863. * @param array $datetime
  6864. * @param int $parno optional, default FALSE
  6865. * @return array
  6866. */
  6867. public static function _date_time_array( $datetime, $parno=FALSE ) {
  6868. return iCalUtilityFunctions::_chkDateArr( $datetime, $parno );
  6869. }
  6870. public static function _chkDateArr( $datetime, $parno=FALSE ) {
  6871. $output = array();
  6872. if(( !$parno || ( 6 <= $parno )) && isset( $datetime[3] ) && !isset( $datetime[4] )) { // Y-m-d with tz
  6873. $temp = $datetime[3];
  6874. $datetime[3] = $datetime[4] = $datetime[5] = 0;
  6875. $datetime[6] = $temp;
  6876. }
  6877. foreach( $datetime as $dateKey => $datePart ) {
  6878. switch ( $dateKey ) {
  6879. case '0': case 'year': $output['year'] = $datePart; break;
  6880. case '1': case 'month': $output['month'] = $datePart; break;
  6881. case '2': case 'day': $output['day'] = $datePart; break;
  6882. }
  6883. if( 3 != $parno ) {
  6884. switch ( $dateKey ) {
  6885. case '0':
  6886. case '1':
  6887. case '2': break;
  6888. case '3': case 'hour': $output['hour'] = $datePart; break;
  6889. case '4': case 'min' : $output['min'] = $datePart; break;
  6890. case '5': case 'sec' : $output['sec'] = $datePart; break;
  6891. case '6': case 'tz' : $output['tz'] = $datePart; break;
  6892. }
  6893. }
  6894. }
  6895. if( 3 != $parno ) {
  6896. if( !isset( $output['hour'] )) $output['hour'] = 0;
  6897. if( !isset( $output['min'] )) $output['min'] = 0;
  6898. if( !isset( $output['sec'] )) $output['sec'] = 0;
  6899. if( isset( $output['tz'] ) &&
  6900. (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
  6901. $output['tz'] = 'Z';
  6902. }
  6903. return $output;
  6904. }
  6905. /**
  6906. * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
  6907. *
  6908. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6909. * @since 2.10.30 - 2012-01-16
  6910. * @param array $date, date to check
  6911. * @param int $parno, no of date parts (i.e. year, month.. .)
  6912. * @param array $params, property parameters
  6913. * @return void
  6914. */
  6915. public static function _chkdatecfg( $theDate, & $parno, & $params ) {
  6916. if( isset( $params['TZID'] ))
  6917. $parno = 6;
  6918. elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
  6919. $parno = 3;
  6920. else {
  6921. if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
  6922. $parno = 7;
  6923. if( is_array( $theDate )) {
  6924. if( isset( $theDate['timestamp'] ))
  6925. $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
  6926. else
  6927. $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
  6928. if( !empty( $tzid )) {
  6929. $parno = 7;
  6930. if( !iCalUtilityFunctions::_isOffset( $tzid ))
  6931. $params['TZID'] = $tzid; // save only timezone
  6932. }
  6933. elseif( !$parno && ( 3 == count( $theDate )) &&
  6934. ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
  6935. $parno = 3;
  6936. else
  6937. $parno = 6;
  6938. }
  6939. else { // string
  6940. $date = trim( $theDate );
  6941. if( 'Z' == substr( $date, -1 ))
  6942. $parno = 7; // UTC DATE-TIME
  6943. elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
  6944. ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
  6945. $parno = 3; // DATE
  6946. $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
  6947. unset( $date['unparsedtext'] );
  6948. if( !empty( $date['tz'] )) {
  6949. $parno = 7;
  6950. if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
  6951. $params['TZID'] = $date['tz']; // save only timezone
  6952. }
  6953. elseif( empty( $parno ))
  6954. $parno = 6;
  6955. }
  6956. if( isset( $params['TZID'] ))
  6957. $parno = 6;
  6958. }
  6959. }
  6960. /**
  6961. * vcalendar sort callback function
  6962. *
  6963. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  6964. * @since 2.16.2 - 2012-12-17
  6965. * @param array $a
  6966. * @param array $b
  6967. * @return int
  6968. */
  6969. public static function _cmpfcn( $a, $b ) {
  6970. if( empty( $a )) return -1;
  6971. if( empty( $b )) return 1;
  6972. if( 'vtimezone' == $a->objName ) {
  6973. if( 'vtimezone' != $b->objName ) return -1;
  6974. elseif( $a->srtk[0] <= $b->srtk[0] ) return -1;
  6975. else return 1;
  6976. }
  6977. elseif( 'vtimezone' == $b->objName ) return 1;
  6978. $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
  6979. for( $k = 0; $k < 4 ; $k++ ) {
  6980. if( empty( $a->srtk[$k] )) return -1;
  6981. elseif( empty( $b->srtk[$k] )) return 1;
  6982. if( is_array( $a->srtk[$k] )) {
  6983. if( is_array( $b->srtk[$k] )) {
  6984. foreach( $sortkeys as $key ) {
  6985. if ( !isset( $a->srtk[$k][$key] )) return -1;
  6986. elseif( !isset( $b->srtk[$k][$key] )) return 1;
  6987. if ( empty( $a->srtk[$k][$key] )) return -1;
  6988. elseif( empty( $b->srtk[$k][$key] )) return 1;
  6989. if ( $a->srtk[$k][$key] == $b->srtk[$k][$key])
  6990. continue;
  6991. if (( (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
  6992. return -1;
  6993. elseif(( (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
  6994. return 1;
  6995. }
  6996. }
  6997. else return -1;
  6998. }
  6999. elseif( is_array( $b->srtk[$k] )) return 1;
  7000. elseif( $a->srtk[$k] < $b->srtk[$k] ) return -1;
  7001. elseif( $a->srtk[$k] > $b->srtk[$k] ) return 1;
  7002. }
  7003. return 0;
  7004. }
  7005. /**
  7006. * byte oriented line folding fix
  7007. *
  7008. * remove any line-endings that may include spaces or tabs
  7009. * and convert all line endings (iCal default '\r\n'),
  7010. * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
  7011. *
  7012. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7013. * @since 2.18.16 - 2014-04-04
  7014. * @param string $text
  7015. * @param string $nl
  7016. * @return string
  7017. */
  7018. public static function convEolChar( & $text, $nl ) {
  7019. /* fix dummy line separator */
  7020. if( empty( iCalUtilityFunctions::$baseDelim )) {
  7021. iCalUtilityFunctions::$baseDelim = substr( microtime(), 2, 4 );
  7022. $base = 'aAbB!cCdD"eEfF#gGhHiIjJ%kKlL&mMnN/oOpP(rRsS)tTuU=vVxX?uUvV*wWzZ-1234_5678|90';
  7023. $len = strlen( $base ) - 1;
  7024. for( $p = 0; $p < 6; $p++ )
  7025. iCalUtilityFunctions::$baseDelim .= $base{mt_rand( 0, $len )};
  7026. }
  7027. /* fix eol chars */
  7028. $text = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), iCalUtilityFunctions::$baseDelim, $text );
  7029. /* fix empty lines */
  7030. $text = str_replace( iCalUtilityFunctions::$baseDelim.iCalUtilityFunctions::$baseDelim, iCalUtilityFunctions::$baseDelim.str_pad( '', 75 ).iCalUtilityFunctions::$baseDelim, $text );
  7031. /* fix line folding */
  7032. $text = str_replace( iCalUtilityFunctions::$baseDelim, $nl, $text );
  7033. $text = str_replace( array( $nl.' ', $nl."\t" ), '', $text );
  7034. /* split in component/property lines */
  7035. $text = explode( $nl, $text );
  7036. return $text;
  7037. }
  7038. /**
  7039. * create a calendar timezone and standard/daylight components
  7040. *
  7041. * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
  7042. *
  7043. * BEGIN:VTIMEZONE
  7044. * TZID:Europe/Stockholm
  7045. * BEGIN:STANDARD
  7046. * DTSTART:20101031T020000
  7047. * TZOFFSETFROM:+0200
  7048. * TZOFFSETTO:+0100
  7049. * TZNAME:CET
  7050. * END:STANDARD
  7051. * BEGIN:DAYLIGHT
  7052. * DTSTART:20100328T030000
  7053. * TZOFFSETFROM:+0100
  7054. * TZOFFSETTO:+0200
  7055. * TZNAME:CEST
  7056. * END:DAYLIGHT
  7057. * END:VTIMEZONE
  7058. *
  7059. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7060. * @since 2.18.17 - 2013-12-22
  7061. * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
  7062. * Additional changes jpirkey
  7063. * @param object $calendar, reference to an iCalcreator calendar instance
  7064. * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
  7065. * @param array $xProp, *[x-propName => x-propValue], optional
  7066. * @param int $from a unix timestamp
  7067. * @param int $to a unix timestamp
  7068. * @return bool
  7069. */
  7070. public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
  7071. if( empty( $timezone ))
  7072. return FALSE;
  7073. if( !empty( $from ) && !is_int( $from ))
  7074. return FALSE;
  7075. if( !empty( $to ) && !is_int( $to ))
  7076. return FALSE;
  7077. try {
  7078. $dtz = new DateTimeZone( $timezone );
  7079. $transitions = $dtz->getTransitions();
  7080. $utcTz = new DateTimeZone( 'UTC' );
  7081. }
  7082. catch( Exception $e ) { return FALSE; }
  7083. if( empty( $to )) {
  7084. $dates = array_keys( $calendar->getProperty( 'dtstart' ));
  7085. if( empty( $dates ))
  7086. $dates = array( date( 'Ymd' ));
  7087. }
  7088. if( !empty( $from ))
  7089. $dateFrom = new DateTime( "@$from" ); // set lowest date (UTC)
  7090. else {
  7091. $from = reset( $dates ); // set lowest date to the lowest dtstart date
  7092. $dateFrom = new DateTime( $from.'T000000', $dtz );
  7093. $dateFrom->modify( '-7 month' ); // set $dateFrom to seven month before the lowest date
  7094. $dateFrom->setTimezone( $utcTz ); // convert local date to UTC
  7095. }
  7096. $dateFromYmd = $dateFrom->format('Y-m-d' );
  7097. if( !empty( $to ))
  7098. $dateTo = new DateTime( "@$to" ); // set end date (UTC)
  7099. else {
  7100. $to = end( $dates ); // set highest date to the highest dtstart date
  7101. $dateTo = new DateTime( $to.'T235959', $dtz );
  7102. $dateTo->modify( '+7 month' ); // set $dateTo to seven month after the highest date
  7103. $dateTo->setTimezone( $utcTz ); // convert local date to UTC
  7104. }
  7105. $dateToYmd = $dateTo->format('Y-m-d' );
  7106. unset( $dtz );
  7107. $transTemp = array();
  7108. $prevOffsetfrom = 0;
  7109. $stdIx = $dlghtIx = null;
  7110. $prevTrans = FALSE;
  7111. foreach( $transitions as $tix => $trans ) { // all transitions in date-time order!!
  7112. $date = new DateTime( "@{$trans['ts']}" ); // set transition date (UTC)
  7113. $transDateYmd = $date->format('Y-m-d' );
  7114. if ( $transDateYmd < $dateFromYmd ) {
  7115. $prevOffsetfrom = $trans['offset']; // previous trans offset will be 'next' trans offsetFrom
  7116. $prevTrans = $trans; // save it in case we don't find any that match
  7117. $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
  7118. continue;
  7119. }
  7120. if( $transDateYmd > $dateToYmd )
  7121. break; // loop always (?) breaks here
  7122. if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
  7123. $trans['offsetfrom'] = $prevOffsetfrom; // i.e. set previous offsetto as offsetFrom
  7124. $date->modify( $trans['offsetfrom'].'seconds' ); // convert utc date to local date
  7125. $d = $date->format( 'Y-n-j-G-i-s' ); // set date to array to ease up dtstart and (opt) rdate setting
  7126. $d = explode( '-', $d );
  7127. $trans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
  7128. }
  7129. $prevOffsetfrom = $trans['offset'];
  7130. if( TRUE !== $trans['isdst'] ) { // standard timezone
  7131. if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
  7132. ( $transTemp[$stdIx]['abbr'] == $trans['abbr'] ) &&
  7133. ( $transTemp[$stdIx]['offsetfrom'] == $trans['offsetfrom'] ) &&
  7134. ( $transTemp[$stdIx]['offset'] == $trans['offset'] )) {
  7135. $transTemp[$stdIx]['rdate'][] = $trans['time'];
  7136. continue;
  7137. }
  7138. $stdIx = $tix;
  7139. } // end standard timezone
  7140. else { // daylight timezone
  7141. if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
  7142. ( $transTemp[$dlghtIx]['abbr'] == $trans['abbr'] ) &&
  7143. ( $transTemp[$dlghtIx]['offsetfrom'] == $trans['offsetfrom'] ) &&
  7144. ( $transTemp[$dlghtIx]['offset'] == $trans['offset'] )) {
  7145. $transTemp[$dlghtIx]['rdate'][] = $trans['time'];
  7146. continue;
  7147. }
  7148. $dlghtIx = $tix;
  7149. } // end daylight timezone
  7150. $transTemp[$tix] = $trans;
  7151. } // end foreach( $transitions as $tix => $trans )
  7152. $tz = & $calendar->newComponent( 'vtimezone' );
  7153. $tz->setproperty( 'tzid', $timezone );
  7154. if( !empty( $xProp )) {
  7155. foreach( $xProp as $xPropName => $xPropValue )
  7156. if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
  7157. $tz->setproperty( $xPropName, $xPropValue );
  7158. }
  7159. if( empty( $transTemp )) { // if no match found
  7160. if( $prevTrans ) { // then we use the last transition (before startdate) for the tz info
  7161. $date = new DateTime( "@{$prevTrans['ts']}" ); // set transition date (UTC)
  7162. $date->modify( $prevTrans['offsetfrom'].'seconds' ); // convert utc date to local date
  7163. $d = $date->format( 'Y-n-j-G-i-s' ); // set date to array to ease up dtstart setting
  7164. $d = explode( '-', $d );
  7165. $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
  7166. $transTemp[0] = $prevTrans;
  7167. }
  7168. else { // or we use the timezone identifier to BUILD the standard tz info (?)
  7169. $date = new DateTime( 'now', new DateTimeZone( $timezone ));
  7170. $transTemp[0] = array( 'time' => $date->format( 'Y-m-d\TH:i:s O' )
  7171. , 'offset' => $date->format( 'Z' )
  7172. , 'offsetfrom' => $date->format( 'Z' )
  7173. , 'isdst' => FALSE );
  7174. }
  7175. }
  7176. unset( $transitions, $date, $prevTrans );
  7177. foreach( $transTemp as $tix => $trans ) { // create standard/daylight subcomponents
  7178. $type = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
  7179. $scomp = & $tz->newComponent( $type );
  7180. $scomp->setProperty( 'dtstart', $trans['time'] );
  7181. // $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] ); // test ###
  7182. if( !empty( $trans['abbr'] ))
  7183. $scomp->setProperty( 'tzname', $trans['abbr'] );
  7184. if( isset( $trans['offsetfrom'] ))
  7185. $scomp->setProperty( 'tzoffsetfrom', iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
  7186. $scomp->setProperty( 'tzoffsetto', iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
  7187. if( isset( $trans['rdate'] ))
  7188. $scomp->setProperty( 'RDATE', $trans['rdate'] );
  7189. }
  7190. return TRUE;
  7191. }
  7192. /**
  7193. * creates formatted output for calendar component property data value type date/date-time
  7194. *
  7195. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7196. * @since 2.14.1 - 2012-09-17
  7197. * @param array $datetime
  7198. * @param int $parno, optional, default 6
  7199. * @return string
  7200. */
  7201. public static function _format_date_time( $datetime, $parno=6 ) {
  7202. return iCalUtilityFunctions::_date2strdate( $datetime, $parno );
  7203. }
  7204. public static function _date2strdate( $datetime, $parno=6 ) {
  7205. if( !isset( $datetime['year'] ) &&
  7206. !isset( $datetime['month'] ) &&
  7207. !isset( $datetime['day'] ) &&
  7208. !isset( $datetime['hour'] ) &&
  7209. !isset( $datetime['min'] ) &&
  7210. !isset( $datetime['sec'] ))
  7211. return;
  7212. $output = null;
  7213. foreach( $datetime as $dkey => & $dvalue )
  7214. if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
  7215. $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
  7216. if( 3 == $parno )
  7217. return $output;
  7218. if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
  7219. if( !isset( $datetime['min'] )) $datetime['min'] = 0;
  7220. if( !isset( $datetime['sec'] )) $datetime['sec'] = 0;
  7221. $output .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
  7222. if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
  7223. $datetime['tz'] = trim( $datetime['tz'] );
  7224. if( 'Z' == $datetime['tz'] )
  7225. $parno = 7;
  7226. elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
  7227. $parno = 7;
  7228. $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
  7229. try {
  7230. $d = new DateTime( $output, new DateTimeZone( 'UTC' ));
  7231. if( 0 != $offset ) // adjust fรถr offset
  7232. $d->modify( "$offset seconds" );
  7233. $output = $d->format( 'Ymd\THis' );
  7234. }
  7235. catch( Exception $e ) {
  7236. $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
  7237. }
  7238. }
  7239. if( 7 == $parno )
  7240. $output .= 'Z';
  7241. }
  7242. return $output;
  7243. }
  7244. /**
  7245. * convert a date/datetime (array) to timestamp
  7246. *
  7247. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7248. * @since 2.14.1 - 2012-09-29
  7249. * @param array $datetime datetime(/date)
  7250. * @param string $wtz timezone
  7251. * @return int
  7252. */
  7253. public static function _date2timestamp( $datetime, $wtz=null ) {
  7254. if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
  7255. if( !isset( $datetime['min'] )) $datetime['min'] = 0;
  7256. if( !isset( $datetime['sec'] )) $datetime['sec'] = 0;
  7257. if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty( $datetime['tz'] )))
  7258. return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
  7259. $output = $offset = 0;
  7260. if( empty( $wtz )) {
  7261. if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
  7262. $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1;
  7263. $wtz = 'UTC';
  7264. }
  7265. else
  7266. $wtz = $datetime['tz'];
  7267. }
  7268. if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz )))
  7269. $wtz = 'UTC';
  7270. try {
  7271. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
  7272. $d = new DateTime( $strdate, new DateTimeZone( $wtz ));
  7273. if( 0 != $offset ) // adjust for offset
  7274. $d->modify( $offset.' seconds' );
  7275. $output = $d->format( 'U' );
  7276. unset( $d );
  7277. }
  7278. catch( Exception $e ) {
  7279. $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
  7280. }
  7281. return $output;
  7282. }
  7283. /**
  7284. * ensures internal duration format for input in array format
  7285. *
  7286. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7287. * @since 2.19.4 - 2014-03-14
  7288. * @param array $duration
  7289. * @return array
  7290. */
  7291. public static function _duration_array( $duration ) {
  7292. return iCalUtilityFunctions::_duration2arr( $duration );
  7293. }
  7294. public static function _duration2arr( $duration ) {
  7295. $seconds = 0;
  7296. foreach( $duration as $durKey => $durValue ) {
  7297. if( empty( $durValue )) continue;
  7298. switch ( $durKey ) {
  7299. case '0': case 'week':
  7300. $seconds += (((int) $durValue ) * 60 * 60 * 24 * 7 );
  7301. break;
  7302. case '1': case 'day':
  7303. $seconds += (((int) $durValue ) * 60 * 60 * 24 );
  7304. break;
  7305. case '2': case 'hour':
  7306. $seconds += (((int) $durValue ) * 60 * 60 );
  7307. break;
  7308. case '3': case 'min':
  7309. $seconds += (((int) $durValue ) * 60 );
  7310. break;
  7311. case '4': case 'sec':
  7312. $seconds += (int) $durValue;
  7313. break;
  7314. }
  7315. }
  7316. $output = array();
  7317. $output['week'] = (int) floor( $seconds / ( 60 * 60 * 24 * 7 ));
  7318. if(( 0 < $output['week'] ) && ( 0 == ( $seconds % ( 60 * 60 * 24 * 7 ))))
  7319. return $output;
  7320. unset( $output['week'] );
  7321. $output['day'] = (int) floor( $seconds / ( 60 * 60 * 24 ));
  7322. $seconds = ( $seconds % ( 60 * 60 * 24 ));
  7323. $output['hour'] = (int) floor( $seconds / ( 60 * 60 ));
  7324. $seconds = ( $seconds % ( 60 * 60 ));
  7325. $output['min'] = (int) floor( $seconds / 60 );
  7326. $output['sec'] = ( $seconds % 60 );
  7327. if( empty( $output['day'] ))
  7328. unset( $output['day'] );
  7329. if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
  7330. unset( $output['hour'], $output['min'], $output['sec'] );
  7331. return $output;
  7332. }
  7333. /**
  7334. * convert startdate+duration to a array format datetime
  7335. *
  7336. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7337. * @since 2.15.12 - 2012-10-31
  7338. * @param array $startdate
  7339. * @param array $duration
  7340. * @return array, date format
  7341. */
  7342. public static function _duration2date( $startdate, $duration ) {
  7343. $dateOnly = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
  7344. $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
  7345. $startdate['min'] = ( isset( $startdate['min'] )) ? $startdate['min'] : 0;
  7346. $startdate['sec'] = ( isset( $startdate['sec'] )) ? $startdate['sec'] : 0;
  7347. $dtend = 0;
  7348. if( isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
  7349. if( isset( $duration['day'] )) $dtend += ( $duration['day'] * 24 * 60 * 60 );
  7350. if( isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
  7351. if( isset( $duration['min'] )) $dtend += ( $duration['min'] * 60 );
  7352. if( isset( $duration['sec'] )) $dtend += $duration['sec'];
  7353. $date = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
  7354. $d = explode( '-', $date );
  7355. $dtend2 = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
  7356. if( isset( $startdate['tz'] ))
  7357. $dtend2['tz'] = $startdate['tz'];
  7358. if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
  7359. unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
  7360. return $dtend2;
  7361. }
  7362. /**
  7363. * ensures internal duration format for an input string (iCal) formatted duration
  7364. *
  7365. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7366. * @since 2.14.1 - 2012-09-25
  7367. * @param string $duration
  7368. * @return array
  7369. */
  7370. public static function _duration_string( $duration ) {
  7371. return iCalUtilityFunctions::_durationStr2arr( $duration );
  7372. }
  7373. public static function _durationStr2arr( $duration ) {
  7374. $duration = (string) trim( $duration );
  7375. while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
  7376. if( 0 < strlen( $duration ))
  7377. $duration = substr( $duration, 1 );
  7378. else
  7379. return false; // no leading P !?!?
  7380. }
  7381. $duration = substr( $duration, 1 ); // skip P
  7382. $duration = str_replace ( 't', 'T', $duration );
  7383. $duration = str_replace ( 'T', '', $duration );
  7384. $output = array();
  7385. $val = null;
  7386. for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
  7387. switch( strtoupper( substr( $duration, $ix, 1 ))) {
  7388. case 'W':
  7389. $output['week'] = $val;
  7390. $val = null;
  7391. break;
  7392. case 'D':
  7393. $output['day'] = $val;
  7394. $val = null;
  7395. break;
  7396. case 'H':
  7397. $output['hour'] = $val;
  7398. $val = null;
  7399. break;
  7400. case 'M':
  7401. $output['min'] = $val;
  7402. $val = null;
  7403. break;
  7404. case 'S':
  7405. $output['sec'] = $val;
  7406. $val = null;
  7407. break;
  7408. default:
  7409. if( !ctype_digit( substr( $duration, $ix, 1 )))
  7410. return false; // unknown duration control character !?!?
  7411. else
  7412. $val .= substr( $duration, $ix, 1 );
  7413. }
  7414. }
  7415. return iCalUtilityFunctions::_duration2arr( $output );
  7416. }
  7417. /**
  7418. * creates formatted output for calendar component property data value type duration
  7419. *
  7420. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7421. * @since 2.15.8 - 2012-10-30
  7422. * @param array $duration, array( week, day, hour, min, sec )
  7423. * @return string
  7424. */
  7425. public static function _format_duration( $duration ) {
  7426. return iCalUtilityFunctions::_duration2str( $duration );
  7427. }
  7428. public static function _duration2str( $duration ) {
  7429. if( isset( $duration['week'] ) ||
  7430. isset( $duration['day'] ) ||
  7431. isset( $duration['hour'] ) ||
  7432. isset( $duration['min'] ) ||
  7433. isset( $duration['sec'] ))
  7434. $ok = TRUE;
  7435. else
  7436. return;
  7437. if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
  7438. return 'P'.$duration['week'].'W';
  7439. $output = 'P';
  7440. if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
  7441. $output .= $duration['day'].'D';
  7442. if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
  7443. ( isset( $duration['min']) && ( 0 < $duration['min'] )) ||
  7444. ( isset( $duration['sec']) && ( 0 < $duration['sec'] ))) {
  7445. $output .= 'T';
  7446. $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
  7447. $output .= ( isset( $duration['min']) && ( 0 < $duration['min'] )) ? $duration['min']. 'M' : '0M';
  7448. $output .= ( isset( $duration['sec']) && ( 0 < $duration['sec'] )) ? $duration['sec']. 'S' : '0S';
  7449. }
  7450. if( 'P' == $output )
  7451. $output = 'PT0H0M0S';
  7452. return $output;
  7453. }
  7454. /**
  7455. * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
  7456. *
  7457. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7458. * @since 2.4.16 - 2008-11-08
  7459. * @param array $array
  7460. * @param string $expkey, expected key
  7461. * @param string $expval, expected value
  7462. * @param int $hitVal optional, return value if found
  7463. * @param int $elseVal optional, return value if not found
  7464. * @param int $preSet optional, return value if already preset
  7465. * @return int
  7466. */
  7467. public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
  7468. if( $preSet )
  7469. return $preSet;
  7470. if( !is_array( $array ) || ( 0 == count( $array )))
  7471. return $elseVal;
  7472. foreach( $array as $key => $value ) {
  7473. if( strtoupper( $expkey ) == strtoupper( $key )) {
  7474. if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
  7475. unset( $array[$key] );
  7476. return $hitVal;
  7477. }
  7478. }
  7479. }
  7480. return $elseVal;
  7481. }
  7482. /**
  7483. * mgnt geo part output
  7484. *
  7485. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7486. * @since 2.8.10 - 2013-09-02
  7487. * @param float $ll
  7488. * @return string
  7489. */
  7490. public static function _geo2str2( $ll, $format ) {
  7491. if( 0.0 < $ll )
  7492. $sign = '+';
  7493. else
  7494. $sign = ( 0.0 > $ll ) ? '-' : '';
  7495. return rtrim( rtrim( $sign.sprintf( $format, abs( $ll )), '0' ), '.' );
  7496. }
  7497. /**
  7498. * checks if input contains a (array formatted) date/time
  7499. *
  7500. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7501. * @since 2.16.24 - 2013-07-02
  7502. * @param array $input
  7503. * @return bool
  7504. */
  7505. public static function _isArrayDate( $input ) {
  7506. if( !is_array( $input ) || isset( $input['week'] ) || isset( $input['timestamp'] ) || ( 3 > count( $input )))
  7507. return FALSE;
  7508. if( 7 == count( $input ))
  7509. return TRUE;
  7510. if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
  7511. return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
  7512. if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
  7513. return FALSE;
  7514. if(( 0 == $input[0] ) || ( 0 == $input[1] ) || ( 0 == $input[2] ))
  7515. return FALSE;
  7516. if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
  7517. return FALSE;
  7518. if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
  7519. checkdate((int) $input[1], (int) $input[2], (int) $input[0] ))
  7520. return TRUE;
  7521. $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); // m - d - Y
  7522. if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
  7523. return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
  7524. return FALSE;
  7525. }
  7526. /**
  7527. * checks if input array contains a timestamp date
  7528. *
  7529. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7530. * @since 2.4.16 - 2008-10-18
  7531. * @param array $input
  7532. * @return bool
  7533. */
  7534. public static function _isArrayTimestampDate( $input ) {
  7535. return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
  7536. }
  7537. /**
  7538. * controls if input string contains (trailing) UTC/iCal offset
  7539. *
  7540. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7541. * @since 2.14.1 - 2012-09-21
  7542. * @param string $input
  7543. * @return bool
  7544. */
  7545. public static function _isOffset( $input ) {
  7546. $input = trim( (string) $input );
  7547. if( 'Z' == substr( $input, -1 ))
  7548. return TRUE;
  7549. elseif(( 5 <= strlen( $input )) &&
  7550. ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
  7551. ( '0000' <= substr( $input, -4 )) && ( '9999' >= substr( $input, -4 )))
  7552. return TRUE;
  7553. elseif(( 7 <= strlen( $input )) &&
  7554. ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
  7555. ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
  7556. return TRUE;
  7557. return FALSE;
  7558. }
  7559. /**
  7560. * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
  7561. * matching (MS) UCT offset and time zone descriptors
  7562. *
  7563. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7564. * @since 2.14.1 - 2012-09-16
  7565. * @param string $timezone, input/output variable reference
  7566. * @return bool
  7567. */
  7568. public static function ms2phpTZ( & $timezone ) {
  7569. if( empty( $timezone ))
  7570. return FALSE;
  7571. $search = str_replace( '"', '', $timezone );
  7572. $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
  7573. if( '(UTC' != substr( $search, 0, 4 ))
  7574. return FALSE;
  7575. if( FALSE === ( $pos = strpos( $search, ')' )))
  7576. return FALSE;
  7577. $pos = strpos( $search, ')' );
  7578. $searchOffset = substr( $search, 4, ( $pos - 4 ));
  7579. $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
  7580. while( ' ' ==substr( $search, ( $pos + 1 )))
  7581. $pos += 1;
  7582. $searchText = trim( str_replace( array( '(', ')', '&', ',', ' ' ), ' ', substr( $search, ( $pos + 1 )) ));
  7583. $searchWords = explode( ' ', $searchText );
  7584. $timezone_abbreviations = DateTimeZone::listAbbreviations();
  7585. $hits = array();
  7586. foreach( $timezone_abbreviations as $name => $transitions ) {
  7587. foreach( $transitions as $cnt => $transition ) {
  7588. if( empty( $transition['offset'] ) ||
  7589. empty( $transition['timezone_id'] ) ||
  7590. ( $transition['offset'] != $searchOffset ))
  7591. continue;
  7592. $cWords = explode( '/', $transition['timezone_id'] );
  7593. $cPrio = $hitCnt = $rank = 0;
  7594. foreach( $cWords as $cWord ) {
  7595. if( empty( $cWord ))
  7596. continue;
  7597. $cPrio += 1;
  7598. $sPrio = 0;
  7599. foreach( $searchWords as $sWord ) {
  7600. if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
  7601. continue;
  7602. $sPrio += 1;
  7603. if( strtolower( $cWord ) == strtolower( $sWord )) {
  7604. $hitCnt += 1;
  7605. $rank += ( $cPrio + $sPrio );
  7606. }
  7607. else
  7608. $rank += 10;
  7609. }
  7610. }
  7611. if( 0 < $hitCnt ) {
  7612. $hits[$rank][] = $transition['timezone_id'];
  7613. }
  7614. }
  7615. }
  7616. unset( $timezone_abbreviations );
  7617. if( empty( $hits ))
  7618. return FALSE;
  7619. ksort( $hits );
  7620. foreach( $hits as $rank => $tzs ) {
  7621. if( !empty( $tzs )) {
  7622. $timezone = reset( $tzs );
  7623. return TRUE;
  7624. }
  7625. }
  7626. return FALSE;
  7627. }
  7628. /**
  7629. * transforms offset in seconds to [-/+]hhmm[ss]
  7630. *
  7631. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7632. * @since 2011-05-02
  7633. * @param string $seconds
  7634. * @return string
  7635. */
  7636. public static function offsetSec2His( $seconds ) {
  7637. if( '-' == substr( $seconds, 0, 1 )) {
  7638. $prefix = '-';
  7639. $seconds = substr( $seconds, 1 );
  7640. }
  7641. elseif( '+' == substr( $seconds, 0, 1 )) {
  7642. $prefix = '+';
  7643. $seconds = substr( $seconds, 1 );
  7644. }
  7645. else
  7646. $prefix = '+';
  7647. $output = '';
  7648. $hour = (int) floor( $seconds / 3600 );
  7649. if( 10 > $hour )
  7650. $hour = '0'.$hour;
  7651. $seconds = $seconds % 3600;
  7652. $min = (int) floor( $seconds / 60 );
  7653. if( 10 > $min )
  7654. $min = '0'.$min;
  7655. $output = $hour.$min;
  7656. $seconds = $seconds % 60;
  7657. if( 0 < $seconds) {
  7658. if( 9 < $seconds)
  7659. $output .= $seconds;
  7660. else
  7661. $output .= '0'.$seconds;
  7662. }
  7663. return $prefix.$output;
  7664. }
  7665. /**
  7666. * updates an array with dates based on a recur pattern
  7667. *
  7668. * if missing, UNTIL is set 1 year from startdate (emergency break)
  7669. *
  7670. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  7671. * @since 2.10.19 - 2011-10-31
  7672. * @param array $result, array to update, array([timestamp] => timestamp)
  7673. * @param array $recur, pattern for recurrency (only value part, params ignored)
  7674. * @param array $wdate, component start date
  7675. * @param array $startdate, start date
  7676. * @param array $enddate, optional
  7677. * @return void
  7678. * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
  7679. */
  7680. public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
  7681. foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
  7682. $wdateStart = $wdate;
  7683. $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate );
  7684. $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
  7685. if( !$enddate ) {
  7686. $enddate = $startdate;
  7687. $enddate['year'] += 1;
  7688. }
  7689. // echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br>\n";print_r($recur);echo "<br>\n";//test###
  7690. $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
  7691. if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
  7692. $recur['UNTIL'] = $enddate; // create break
  7693. if( isset( $recur['UNTIL'] )) {
  7694. $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
  7695. if( $endDatets > $tdatets ) {
  7696. $endDatets = $tdatets; // emergency break
  7697. $enddate = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
  7698. }
  7699. else
  7700. $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
  7701. }
  7702. if( $wdatets > $endDatets ) {
  7703. // echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
  7704. return array(); // nothing to do.. .
  7705. }
  7706. if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
  7707. $recur['FREQ'] = 'DAILY'; // ??
  7708. $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
  7709. $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
  7710. if( !isset( $recur['INTERVAL'] ))
  7711. $recur['INTERVAL'] = 1;
  7712. $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
  7713. /* find out how to step up dates and set index for interval count */
  7714. $step = array();
  7715. if( 'YEARLY' == $recur['FREQ'] )
  7716. $step['year'] = 1;
  7717. elseif( 'MONTHLY' == $recur['FREQ'] )
  7718. $step['month'] = 1;
  7719. elseif( 'WEEKLY' == $recur['FREQ'] )
  7720. $step['day'] = 7;
  7721. else
  7722. $step['day'] = 1;
  7723. if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
  7724. $step = array( 'month' => 1 );
  7725. if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
  7726. $step = array( 'day' => 7 );
  7727. if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
  7728. $step = array( 'day' => 1 );
  7729. $intervalarr = array();
  7730. if( 1 < $recur['INTERVAL'] ) {
  7731. $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
  7732. $intervalarr = array( $intervalix => 0 );
  7733. }
  7734. if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
  7735. $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
  7736. // echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br>\n"; // test ###
  7737. if( is_array( $recur['BYSETPOS'] )) {
  7738. foreach( $recur['BYSETPOS'] as $bix => $bval )
  7739. $recur['BYSETPOS'][$bix] = (int) $bval;
  7740. }
  7741. else
  7742. $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
  7743. if( 'YEARLY' == $recur['FREQ'] ) {
  7744. $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
  7745. $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate );
  7746. iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
  7747. }
  7748. elseif( 'MONTHLY' == $recur['FREQ'] ) {
  7749. $wdate['day'] = 1; // start from beginning of month
  7750. $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate );
  7751. iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
  7752. }
  7753. else
  7754. iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
  7755. // echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br>\n";//test###
  7756. $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
  7757. $bysetposYold = $wdate['year'];
  7758. $bysetposMold = $wdate['month'];
  7759. $bysetposDold = $wdate['day'];
  7760. }
  7761. else
  7762. iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
  7763. $year_old = null;
  7764. $daynames = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
  7765. /* MAIN LOOP */
  7766. // echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br>\n";//test
  7767. while( TRUE ) {
  7768. if( isset( $endDatets ) && ( $wdatets > $endDatets ))
  7769. break;
  7770. if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
  7771. break;
  7772. if( $year_old != $wdate['year'] ) {
  7773. $year_old = $wdate['year'];
  7774. $daycnts = array();
  7775. $yeardays = $weekno = 0;
  7776. $yeardaycnt = array();
  7777. foreach( $daynames as $dn )
  7778. $yeardaycnt[$dn] = 0;
  7779. for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
  7780. $daycnts[$m] = array();
  7781. $weekdaycnt = array();
  7782. foreach( $daynames as $dn )
  7783. $weekdaycnt[$dn] = 0;
  7784. $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
  7785. for( $d = 1; $d <= $mcnt; $d++ ) {
  7786. $daycnts[$m][$d] = array();
  7787. if( isset( $recur['BYYEARDAY'] )) {
  7788. $yeardays++;
  7789. $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
  7790. }
  7791. if( isset( $recur['BYDAY'] )) {
  7792. $day = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
  7793. $day = $daynames[$day];
  7794. $daycnts[$m][$d]['DAY'] = $day;
  7795. $weekdaycnt[$day]++;
  7796. $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
  7797. $yeardaycnt[$day]++;
  7798. $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
  7799. }
  7800. if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
  7801. $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
  7802. }
  7803. }
  7804. $daycnt = 0;
  7805. $yeardaycnt = array();
  7806. if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
  7807. $weekno = null;
  7808. for( $d=31; $d > 25; $d-- ) { // get last weekno for year
  7809. if( !$weekno )
  7810. $weekno = $daycnts[12][$d]['weekno_up'];
  7811. elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
  7812. $weekno = $daycnts[12][$d]['weekno_up'];
  7813. break;
  7814. }
  7815. }
  7816. }
  7817. for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
  7818. $weekdaycnt = array();
  7819. foreach( $daynames as $dn )
  7820. $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
  7821. $monthcnt = 0;
  7822. $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
  7823. for( $d = $mcnt; $d > 0; $d-- ) {
  7824. if( isset( $recur['BYYEARDAY'] )) {
  7825. $daycnt -= 1;
  7826. $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
  7827. }
  7828. if( isset( $recur['BYMONTHDAY'] )) {
  7829. $monthcnt -= 1;
  7830. $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
  7831. }
  7832. if( isset( $recur['BYDAY'] )) {
  7833. $day = $daycnts[$m][$d]['DAY'];
  7834. $weekdaycnt[$day] -= 1;
  7835. $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
  7836. $yeardaycnt[$day] -= 1;
  7837. $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
  7838. }
  7839. if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
  7840. $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
  7841. }
  7842. }
  7843. }
  7844. /* check interval */
  7845. if( 1 < $recur['INTERVAL'] ) {
  7846. /* create interval index */
  7847. $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
  7848. /* check interval */
  7849. $currentKey = array_keys( $intervalarr );
  7850. $currentKey = end( $currentKey ); // get last index
  7851. if( $currentKey != $intervalix )
  7852. $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
  7853. if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
  7854. ( 0 != $intervalarr[$intervalix] )) {
  7855. /* step up date */
  7856. // echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
  7857. iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
  7858. continue;
  7859. }
  7860. else // continue within the selected interval
  7861. $intervalarr[$intervalix] = 0;
  7862. // echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
  7863. }
  7864. $updateOK = TRUE;
  7865. if( $updateOK && isset( $recur['BYMONTH'] ))
  7866. $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
  7867. , $wdate['month']
  7868. ,($wdate['month'] - 13));
  7869. if( $updateOK && isset( $recur['BYWEEKNO'] ))
  7870. $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
  7871. , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
  7872. , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
  7873. if( $updateOK && isset( $recur['BYYEARDAY'] ))
  7874. $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
  7875. , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
  7876. , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
  7877. if( $updateOK && isset( $recur['BYMONTHDAY'] ))
  7878. $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
  7879. , $wdate['day']
  7880. , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
  7881. // echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n";//test###
  7882. if( $updateOK && isset( $recur['BYDAY'] )) {
  7883. $updateOK = FALSE;
  7884. $m = $wdate['month'];
  7885. $d = $wdate['day'];
  7886. if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
  7887. $daynoexists = $daynosw = $daynamesw = FALSE;
  7888. if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
  7889. $daynamesw = TRUE;
  7890. if( isset( $recur['BYDAY'][0] )) {
  7891. $daynoexists = TRUE;
  7892. if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
  7893. $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
  7894. , $daycnts[$m][$d]['monthdayno_up']
  7895. , $daycnts[$m][$d]['monthdayno_down'] );
  7896. elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
  7897. $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
  7898. , $daycnts[$m][$d]['yeardayno_up']
  7899. , $daycnts[$m][$d]['yeardayno_down'] );
  7900. }
  7901. if(( $daynoexists && $daynosw && $daynamesw ) ||
  7902. ( !$daynoexists && !$daynosw && $daynamesw )) {
  7903. $updateOK = TRUE;
  7904. // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br>\n"; // test ###
  7905. }
  7906. // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br>\n"; // test ###
  7907. }
  7908. else {
  7909. foreach( $recur['BYDAY'] as $bydayvalue ) {
  7910. $daynoexists = $daynosw = $daynamesw = FALSE;
  7911. if( isset( $bydayvalue['DAY'] ) &&
  7912. ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
  7913. $daynamesw = TRUE;
  7914. if( isset( $bydayvalue[0] )) {
  7915. $daynoexists = TRUE;
  7916. if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
  7917. isset( $recur['BYMONTH'] ))
  7918. $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
  7919. , $daycnts[$m][$d]['monthdayno_up']
  7920. , $daycnts[$m][$d]['monthdayno_down'] );
  7921. elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
  7922. $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
  7923. , $daycnts[$m][$d]['yeardayno_up']
  7924. , $daycnts[$m][$d]['yeardayno_down'] );
  7925. }
  7926. // echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br>\n"; // test ###
  7927. if(( $daynoexists && $daynosw && $daynamesw ) ||
  7928. ( !$daynoexists && !$daynosw && $daynamesw )) {
  7929. $updateOK = TRUE;
  7930. break;
  7931. }
  7932. }
  7933. }
  7934. }
  7935. // echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n"; // test ###
  7936. /* check BYSETPOS */
  7937. if( $updateOK ) {
  7938. if( isset( $recur['BYSETPOS'] ) &&
  7939. ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
  7940. if( isset( $recur['WEEKLY'] )) {
  7941. if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
  7942. $bysetposw1[] = $wdatets;
  7943. else
  7944. $bysetposw2[] = $wdatets;
  7945. }
  7946. else {
  7947. if(( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) &&
  7948. ( $bysetposYold == $wdate['year'] )) ||
  7949. ( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] ) &&
  7950. (( $bysetposYold == $wdate['year'] ) &&
  7951. ( $bysetposMold == $wdate['month'] ))) ||
  7952. ( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) &&
  7953. (( $bysetposYold == $wdate['year'] ) &&
  7954. ( $bysetposMold == $wdate['month']) &&
  7955. ( $bysetposDold == $wdate['day'] )))) {
  7956. // echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
  7957. $bysetposymd1[] = $wdatets;
  7958. }
  7959. else {
  7960. // echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
  7961. $bysetposymd2[] = $wdatets;
  7962. }
  7963. }
  7964. }
  7965. else {
  7966. /* update result array if BYSETPOS is set */
  7967. $countcnt++;
  7968. if( $startdatets <= $wdatets ) { // only output within period
  7969. $result[$wdatets] = TRUE;
  7970. // echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
  7971. }
  7972. // echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br>\n";//test
  7973. $updateOK = FALSE;
  7974. }
  7975. }
  7976. /* step up date */
  7977. iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
  7978. /* check if BYSETPOS is set for updating result array */
  7979. if( $updateOK && isset( $recur['BYSETPOS'] )) {
  7980. $bysetpos = FALSE;
  7981. if( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) &&
  7982. ( $bysetposYold != $wdate['year'] )) {
  7983. $bysetpos = TRUE;
  7984. $bysetposYold = $wdate['year'];
  7985. }
  7986. elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
  7987. (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
  7988. $bysetpos = TRUE;
  7989. $bysetposYold = $wdate['year'];
  7990. $bysetposMold = $wdate['month'];
  7991. }
  7992. elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY' == $recur['FREQ'] )) {
  7993. $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
  7994. if( $bysetposWold != $weekno ) {
  7995. $bysetposWold = $weekno;
  7996. $bysetpos = TRUE;
  7997. }
  7998. }
  7999. elseif( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) &&
  8000. (( $bysetposYold != $wdate['year'] ) ||
  8001. ( $bysetposMold != $wdate['month'] ) ||
  8002. ( $bysetposDold != $wdate['day'] ))) {
  8003. $bysetpos = TRUE;
  8004. $bysetposYold = $wdate['year'];
  8005. $bysetposMold = $wdate['month'];
  8006. $bysetposDold = $wdate['day'];
  8007. }
  8008. if( $bysetpos ) {
  8009. if( isset( $recur['BYWEEKNO'] )) {
  8010. $bysetposarr1 = & $bysetposw1;
  8011. $bysetposarr2 = & $bysetposw2;
  8012. }
  8013. else {
  8014. $bysetposarr1 = & $bysetposymd1;
  8015. $bysetposarr2 = & $bysetposymd2;
  8016. }
  8017. // echo 'test fรถre out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
  8018. foreach( $recur['BYSETPOS'] as $ix ) {
  8019. if( 0 > $ix ) // both positive and negative BYSETPOS allowed
  8020. $ix = ( count( $bysetposarr1 ) + $ix + 1);
  8021. $ix--;
  8022. if( isset( $bysetposarr1[$ix] )) {
  8023. if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
  8024. // $testdate = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 ); // test ###
  8025. // $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
  8026. // echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)"; // test ###
  8027. $result[$bysetposarr1[$ix]] = TRUE;
  8028. // echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
  8029. }
  8030. $countcnt++;
  8031. }
  8032. if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
  8033. break;
  8034. }
  8035. // echo "<br>\n"; // test ###
  8036. $bysetposarr1 = $bysetposarr2;
  8037. $bysetposarr2 = array();
  8038. }
  8039. }
  8040. }
  8041. }
  8042. public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
  8043. if( is_array( $BYvalue ) &&
  8044. ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
  8045. return TRUE;
  8046. elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
  8047. return TRUE;
  8048. else
  8049. return FALSE;
  8050. }
  8051. public static function _recurIntervalIx( $freq, $date, $wkst ) {
  8052. /* create interval index */
  8053. switch( $freq ) {
  8054. case 'YEARLY':
  8055. $intervalix = $date['year'];
  8056. break;
  8057. case 'MONTHLY':
  8058. $intervalix = $date['year'].'-'.$date['month'];
  8059. break;
  8060. case 'WEEKLY':
  8061. $wdatets = iCalUtilityFunctions::_date2timestamp( $date );
  8062. $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
  8063. break;
  8064. case 'DAILY':
  8065. default:
  8066. $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
  8067. break;
  8068. }
  8069. return $intervalix;
  8070. }
  8071. public static function _recurBydaySort( $bydaya, $bydayb ) {
  8072. static $days = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 );
  8073. return ( $days[substr( $bydaya, -2 )] < $days[substr( $bydayb, -2 )] ) ? -1 : 1;
  8074. }
  8075. /**
  8076. * helper function for vcalendar::selectComponents, set property X-CURRENT-DTEND/DUE
  8077. *
  8078. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8079. * @since 2.18.7 - 2013-08-30
  8080. * @param object $comp component to update
  8081. * @param array $dateFormat
  8082. * @param int $timestamp1
  8083. * @param string $cmpYMD1 date('Ymd') related to $timestamp1
  8084. * @param int $timestamp2
  8085. * @param string $cmpYMD2 date('Ymd') related to $timestamp2
  8086. * @param array $tz date array opt. with key for 'tz'
  8087. * @param array $SCbools (end) date booleans
  8088. * @return void
  8089. */
  8090. static function _SCsetXCurrentEnd( $comp, $dateFormat, $timestamp1, $cmpYMD1, $timestamp2, $cmpYMD2, $tz, $SCbools ) {
  8091. if( ! $SCbools[ 'dtendExist'] && ! $SCbools[ 'dueExist'] && ! $SCbools[ 'durationExist'] )
  8092. return;
  8093. $H = ( $cmpYMD1 < $cmpYMD2 ) ? 23 : date( 'H', $timestamp2 );
  8094. $i = ( $cmpYMD1 < $cmpYMD2 ) ? 59 : date( 'i', $timestamp2 );
  8095. $s = ( $cmpYMD1 < $cmpYMD2 ) ? 59 : date( 's', $timestamp2 );
  8096. $tend = mktime( $H, $i, $s, date( 'm', $timestamp1 ), date( 'd', $timestamp1 ), date( 'Y', $timestamp1 ) ); // on a day-basis !!!
  8097. if( $SCbools[ 'endAllDayEvent'] && $SCbools[ 'dtendExist'] )
  8098. $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
  8099. $datestring = date( $dateFormat['end'], $tend );
  8100. if( isset( $tz['tz'] ))
  8101. $datestring .= ' '.$tz['tz'];
  8102. $propName = ( ! $SCbools[ 'dueExist'] ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
  8103. $comp->setProperty( $propName, $datestring );
  8104. }
  8105. /**
  8106. * helper function for vcalendar::selectComponents, set property X-CURRENT-DTSTART
  8107. *
  8108. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8109. * @since 2.18.7 - 2013-08-30
  8110. * @param object $comp component to update
  8111. * @param array $dateFormat
  8112. * @param int $timestamp1
  8113. * @param string $cpmYMD1 date('Ymd') related to $timestamp1
  8114. * @param int $timestamp2
  8115. * @param string $cpmYMD2 date('Ymd') related to $timestamp2
  8116. * @param array $tz date array opt. with key for 'tz'
  8117. * @return void
  8118. */
  8119. static function _SCsetXCurrentStart( $comp, $dateFormat, $timestamp1, $cpmYMD1=FALSE, $timestamp2=FALSE, $cpmYMD2=FALSE, $tz=FALSE ) {
  8120. if( $cpmYMD2 && ( $cpmYMD1 <= $cpmYMD2 )) // check date after dtstart
  8121. $timestamp1 = $timestamp2;
  8122. $datestring = date( $dateFormat['start'], $timestamp1 );
  8123. if( isset( $tz['tz'] ))
  8124. $datestring .= ' '.$tz['tz'];
  8125. $comp->setProperty( 'X-CURRENT-DTSTART', $datestring );
  8126. }
  8127. /**
  8128. * helper function for vcalendar::selectComponents, adjust UTC X-CURRENT-x date
  8129. *
  8130. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8131. * @since 2.18.19 - 2014-02-01
  8132. * @param int $timestamp
  8133. * @param array $tz date array opt. with key for 'tz'
  8134. * @return int
  8135. */
  8136. static function _SCsetXCurrentDateZ( $timestamp, $tz=array()) {
  8137. if( ! is_array( $tz ) || ! isset( $tz['tz'] ))
  8138. return $timestamp;
  8139. $tz['tz'] = strtoupper( $tz['tz'] );
  8140. if(( 'Z' == $tz['tz'] ) ||( 'UTC' == $tz['tz'] ) ||( 'GMT' == $tz['tz'] ))
  8141. $timestamp -= date( 'Z', $timestamp );
  8142. return $timestamp;
  8143. }
  8144. /**
  8145. * convert input format for exrule and rrule to internal format
  8146. *
  8147. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8148. * @since 2.18.10 - 2013-09-04
  8149. * @param array $rexrule
  8150. * @return array
  8151. */
  8152. public static function _setRexrule( $rexrule ) {
  8153. $input = array();
  8154. if( empty( $rexrule ))
  8155. return $input;
  8156. $rexrule = array_change_key_case( $rexrule, CASE_UPPER );
  8157. foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
  8158. if( 'UNTIL' != $rexrulelabel )
  8159. $input[$rexrulelabel] = $rexrulevalue;
  8160. else {
  8161. iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
  8162. if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
  8163. $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
  8164. elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
  8165. $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
  8166. $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
  8167. if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
  8168. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  8169. $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8170. unset( $input[$rexrulelabel]['unparsedtext'] );
  8171. }
  8172. else
  8173. $input[$rexrulelabel] = $d;
  8174. }
  8175. elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
  8176. $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
  8177. unset( $input['$rexrulelabel']['unparsedtext'] );
  8178. }
  8179. if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
  8180. $input[$rexrulelabel]['tz'] = 'Z';
  8181. }
  8182. }
  8183. /* set recurrence rule specification in rfc2445 order */
  8184. $input2 = array();
  8185. if( isset( $input['FREQ'] ))
  8186. $input2['FREQ'] = $input['FREQ'];
  8187. if( isset( $input['UNTIL'] ))
  8188. $input2['UNTIL'] = $input['UNTIL'];
  8189. elseif( isset( $input['COUNT'] ))
  8190. $input2['COUNT'] = $input['COUNT'];
  8191. if( isset( $input['INTERVAL'] ))
  8192. $input2['INTERVAL'] = $input['INTERVAL'];
  8193. if( isset( $input['BYSECOND'] ))
  8194. $input2['BYSECOND'] = $input['BYSECOND'];
  8195. if( isset( $input['BYMINUTE'] ))
  8196. $input2['BYMINUTE'] = $input['BYMINUTE'];
  8197. if( isset( $input['BYHOUR'] ))
  8198. $input2['BYHOUR'] = $input['BYHOUR'];
  8199. if( isset( $input['BYDAY'] )) {
  8200. if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
  8201. $input2['BYDAY'] = strtoupper( $input['BYDAY'] );
  8202. else {
  8203. foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
  8204. if( 'DAY' == strtoupper( $BYDAYx ))
  8205. $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
  8206. elseif( !is_array( $BYDAYv )) {
  8207. $input2['BYDAY'][$BYDAYx] = $BYDAYv;
  8208. }
  8209. else {
  8210. foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
  8211. if( 'DAY' == strtoupper( $BYDAYx2 ))
  8212. $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
  8213. else
  8214. $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
  8215. }
  8216. }
  8217. }
  8218. }
  8219. }
  8220. if( isset( $input['BYMONTHDAY'] ))
  8221. $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
  8222. if( isset( $input['BYYEARDAY'] ))
  8223. $input2['BYYEARDAY'] = $input['BYYEARDAY'];
  8224. if( isset( $input['BYWEEKNO'] ))
  8225. $input2['BYWEEKNO'] = $input['BYWEEKNO'];
  8226. if( isset( $input['BYMONTH'] ))
  8227. $input2['BYMONTH'] = $input['BYMONTH'];
  8228. if( isset( $input['BYSETPOS'] ))
  8229. $input2['BYSETPOS'] = $input['BYSETPOS'];
  8230. if( isset( $input['WKST'] ))
  8231. $input2['WKST'] = $input['WKST'];
  8232. return $input2;
  8233. }
  8234. /**
  8235. * convert format for input date to internal date with parameters
  8236. *
  8237. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8238. * @since 2.16.24 - 2013-06-26
  8239. * @param mixed $year
  8240. * @param mixed $month optional
  8241. * @param int $day optional
  8242. * @param int $hour optional
  8243. * @param int $min optional
  8244. * @param int $sec optional
  8245. * @param string $tz optional
  8246. * @param array $params optional
  8247. * @param string $caller optional
  8248. * @param string $objName optional
  8249. * @param string $tzid optional
  8250. * @return array
  8251. */
  8252. public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
  8253. $input = $parno = null;
  8254. $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
  8255. iCalUtilityFunctions::_strDate2arr( $year );
  8256. if( iCalUtilityFunctions::_isArrayDate( $year )) {
  8257. $input['value'] = iCalUtilityFunctions::_chkDateArr( $year, FALSE ); //$parno );
  8258. if( 100 > $input['value']['year'] )
  8259. $input['value']['year'] += 2000;
  8260. if( $localtime )
  8261. unset( $month['VALUE'], $month['TZID'] );
  8262. elseif( !isset( $month['TZID'] ) && isset( $tzid ))
  8263. $month['TZID'] = $tzid;
  8264. if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
  8265. unset( $month['TZID'] );
  8266. elseif( !isset( $input['value']['tz'] ) && isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
  8267. $input['value']['tz'] = $month['TZID'];
  8268. unset( $month['TZID'] );
  8269. }
  8270. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8271. $hitval = ( isset( $input['value']['tz'] )) ? 7 : 6;
  8272. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
  8273. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
  8274. if( 6 > $parno )
  8275. unset( $input['value']['tz'], $input['params']['TZID'], $tzid );
  8276. if(( 6 <= $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
  8277. $d = $input['value'];
  8278. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  8279. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
  8280. unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
  8281. }
  8282. if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
  8283. $input['params']['TZID'] = $input['value']['tz'];
  8284. unset( $input['value']['tz'] );
  8285. }
  8286. } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
  8287. elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
  8288. if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
  8289. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8290. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
  8291. $hitval = 7;
  8292. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
  8293. if( isset( $year['tz'] ) && !empty( $year['tz'] )) {
  8294. if( !iCalUtilityFunctions::_isOffset( $year['tz'] )) {
  8295. $input['params']['TZID'] = $year['tz'];
  8296. unset( $year['tz'], $tzid );
  8297. }
  8298. else {
  8299. if( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
  8300. if( !iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
  8301. unset( $tzid );
  8302. else
  8303. unset( $input['params']['TZID']);
  8304. }
  8305. elseif( isset( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
  8306. $input['params']['TZID'] = $tzid;
  8307. }
  8308. }
  8309. elseif( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
  8310. if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
  8311. $year['tz'] = $input['params']['TZID'];
  8312. unset( $input['params']['TZID']);
  8313. if( isset( $tzid ) && !empty( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
  8314. $input['params']['TZID'] = $tzid;
  8315. }
  8316. }
  8317. elseif( isset( $tzid ) && !empty( $tzid )) {
  8318. if( iCalUtilityFunctions::_isOffset( $tzid )) {
  8319. $year['tz'] = $tzid;
  8320. unset( $input['params']['TZID']);
  8321. }
  8322. else
  8323. $input['params']['TZID'] = $tzid;
  8324. }
  8325. $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, $parno );
  8326. } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
  8327. elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
  8328. if( $localtime )
  8329. unset( $month['VALUE'], $month['TZID'] );
  8330. elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
  8331. $month['TZID'] = $tzid;
  8332. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8333. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
  8334. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
  8335. $input['value'] = iCalUtilityFunctions::_strdate2date( $year, $parno );
  8336. if( 3 == $parno )
  8337. unset( $input['value']['tz'], $input['params']['TZID'] );
  8338. unset( $input['value']['unparsedtext'] );
  8339. if( isset( $input['value']['tz'] )) {
  8340. if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
  8341. $d = $input['value'];
  8342. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  8343. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8344. unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
  8345. }
  8346. else {
  8347. $input['params']['TZID'] = $input['value']['tz'];
  8348. unset( $input['value']['tz'] );
  8349. }
  8350. }
  8351. elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
  8352. $d = $input['value'];
  8353. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
  8354. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8355. unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
  8356. }
  8357. } // end elseif( 8 <= strlen( trim( $year )))
  8358. else {
  8359. if( is_array( $params ))
  8360. $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
  8361. elseif( is_array( $tz )) {
  8362. $input['params'] = iCalUtilityFunctions::_setParams( $tz, array( 'VALUE' => 'DATE-TIME' ));
  8363. $tz = FALSE;
  8364. }
  8365. elseif( is_array( $hour )) {
  8366. $input['params'] = iCalUtilityFunctions::_setParams( $hour, array( 'VALUE' => 'DATE-TIME' ));
  8367. $hour = $min = $sec = $tz = FALSE;
  8368. }
  8369. if( $localtime )
  8370. unset ( $input['params']['VALUE'], $input['params']['TZID'] );
  8371. elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
  8372. $input['params']['TZID'] = $tzid;
  8373. elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
  8374. unset( $input['params']['TZID'] );
  8375. elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
  8376. $tz = $input['params']['TZID'];
  8377. unset( $input['params']['TZID'] );
  8378. }
  8379. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
  8380. $hitval = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
  8381. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
  8382. $input['value'] = array( 'year' => $year, 'month' => $month, 'day' => $day );
  8383. if( 3 != $parno ) {
  8384. $input['value']['hour'] = ( $hour ) ? $hour : '0';
  8385. $input['value']['min'] = ( $min ) ? $min : '0';
  8386. $input['value']['sec'] = ( $sec ) ? $sec : '0';
  8387. if( !empty( $tz ))
  8388. $input['value']['tz'] = $tz;
  8389. $strdate = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
  8390. if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
  8391. $strdate .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
  8392. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
  8393. unset( $input['value']['unparsedtext'] );
  8394. if( isset( $input['value']['tz'] )) {
  8395. if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
  8396. $d = $input['value'];
  8397. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
  8398. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8399. unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
  8400. }
  8401. else {
  8402. $input['params']['TZID'] = $input['value']['tz'];
  8403. unset( $input['value']['tz'] );
  8404. }
  8405. }
  8406. elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
  8407. $d = $input['value'];
  8408. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
  8409. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8410. unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
  8411. }
  8412. }
  8413. } // end else (i.e. using all arguments)
  8414. if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
  8415. $input['params']['VALUE'] = 'DATE';
  8416. unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
  8417. }
  8418. elseif( isset( $input['params']['TZID'] )) {
  8419. if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
  8420. $input['value']['tz'] = 'Z';
  8421. unset( $input['params']['TZID'] );
  8422. }
  8423. else
  8424. unset( $input['value']['tz'] );
  8425. }
  8426. elseif( isset( $input['value']['tz'] )) {
  8427. if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
  8428. $input['value']['tz'] = 'Z';
  8429. if( 'Z' != $input['value']['tz'] ) {
  8430. $input['params']['TZID'] = $input['value']['tz'];
  8431. unset( $input['value']['tz'] );
  8432. }
  8433. else
  8434. unset( $input['params']['TZID'] );
  8435. }
  8436. if( $localtime )
  8437. unset( $input['value']['tz'], $input['params']['TZID'] );
  8438. return $input;
  8439. }
  8440. /**
  8441. * convert format for input date (UTC) to internal date with parameters
  8442. *
  8443. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8444. * @since 2.16.24 - 2013-07-01
  8445. * @param mixed $year
  8446. * @param mixed $month optional
  8447. * @param int $day optional
  8448. * @param int $hour optional
  8449. * @param int $min optional
  8450. * @param int $sec optional
  8451. * @param array $params optional
  8452. * @return array
  8453. */
  8454. public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
  8455. $input = null;
  8456. iCalUtilityFunctions::_strDate2arr( $year );
  8457. if( iCalUtilityFunctions::_isArrayDate( $year )) {
  8458. $input['value'] = iCalUtilityFunctions::_chkDateArr( $year, 7 );
  8459. if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
  8460. $input['value']['year'] += 2000;
  8461. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8462. unset( $input['params']['VALUE'] );
  8463. if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
  8464. $tzid = $input['value']['tz'];
  8465. elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
  8466. $tzid = $input['params']['TZID'];
  8467. else
  8468. $tzid = '';
  8469. unset( $input['params']['VALUE'], $input['params']['TZID'] );
  8470. if( !empty( $tzid ) && ( 'Z' != $tzid ) && iCalUtilityFunctions::_isOffset( $tzid )) {
  8471. $d = $input['value'];
  8472. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $tzid );
  8473. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8474. unset( $input['value']['unparsedtext'] );
  8475. }
  8476. }
  8477. elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
  8478. if( isset( $year['tz'] ) && ! iCalUtilityFunctions::_isOffset( $year['tz'] ))
  8479. $year['tz'] = 'UTC';
  8480. elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
  8481. $year['tz'] = $input['params']['TZID'];
  8482. else
  8483. $year['tz'] = 'UTC';
  8484. $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, 7 );
  8485. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8486. unset( $input['params']['VALUE'], $input['params']['TZID'] );
  8487. }
  8488. elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
  8489. $input['value'] = iCalUtilityFunctions::_strdate2date( $year, 7 );
  8490. unset( $input['value']['unparsedtext'] );
  8491. $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
  8492. if(( !isset( $input['value']['tz'] ) || empty( $input['value']['tz'] )) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
  8493. $d = $input['value'];
  8494. $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
  8495. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8496. unset( $input['value']['unparsedtext'] );
  8497. }
  8498. unset( $input['params']['VALUE'], $input['params']['TZID'] );
  8499. }
  8500. else {
  8501. $input['value'] = array( 'year' => $year
  8502. , 'month' => $month
  8503. , 'day' => $day
  8504. , 'hour' => $hour
  8505. , 'min' => $min
  8506. , 'sec' => $sec );
  8507. if( isset( $tz )) $input['value']['tz'] = $tz;
  8508. if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
  8509. ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
  8510. if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
  8511. $input['value']['tz'] = $input['params']['TZID'];
  8512. unset( $input['params']['TZID'] );
  8513. $strdate = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
  8514. $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
  8515. unset( $input['value']['unparsedtext'] );
  8516. }
  8517. $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
  8518. unset( $input['params']['VALUE'] );
  8519. }
  8520. $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
  8521. if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
  8522. if( !isset( $input['value']['min'] )) $input['value']['min'] = 0;
  8523. if( !isset( $input['value']['sec'] )) $input['value']['sec'] = 0;
  8524. $input['value']['tz'] = 'Z';
  8525. return $input;
  8526. }
  8527. /**
  8528. * check index and set (an indexed) content in multiple value array
  8529. *
  8530. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8531. * @since 2.6.12 - 2011-01-03
  8532. * @param array $valArr
  8533. * @param mixed $value
  8534. * @param array $params
  8535. * @param array $defaults
  8536. * @param int $index
  8537. * @return void
  8538. */
  8539. public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
  8540. if( !is_array( $valArr )) $valArr = array();
  8541. if( $index )
  8542. $index = $index - 1;
  8543. elseif( 0 < count( $valArr )) {
  8544. $keys = array_keys( $valArr );
  8545. $index = end( $keys ) + 1;
  8546. }
  8547. else
  8548. $index = 0;
  8549. $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
  8550. ksort( $valArr );
  8551. }
  8552. /**
  8553. * set input (formatted) parameters- component property attributes
  8554. *
  8555. * default parameters can be set, if missing
  8556. *
  8557. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8558. * @since 2.18.10 - 2013-09-04
  8559. * @param array $params
  8560. * @param array $defaults
  8561. * @return array
  8562. */
  8563. public static function _setParams( $params, $defaults=FALSE ) {
  8564. if( !is_array( $params))
  8565. $params = array();
  8566. $input = array();
  8567. $params = array_change_key_case( $params, CASE_UPPER );
  8568. foreach( $params as $paramKey => $paramValue ) {
  8569. if( is_array( $paramValue )) {
  8570. foreach( $paramValue as $pkey => $pValue ) {
  8571. if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
  8572. $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
  8573. }
  8574. }
  8575. elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
  8576. $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
  8577. if( 'VALUE' == $paramKey )
  8578. $input['VALUE'] = strtoupper( $paramValue );
  8579. else
  8580. $input[$paramKey] = $paramValue;
  8581. }
  8582. if( is_array( $defaults )) {
  8583. foreach( $defaults as $paramKey => $paramValue ) {
  8584. if( !isset( $input[$paramKey] ))
  8585. $input[$paramKey] = $paramValue;
  8586. }
  8587. }
  8588. return (0 < count( $input )) ? $input : null;
  8589. }
  8590. /**
  8591. * set sort arguments/parameters in component
  8592. *
  8593. *
  8594. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8595. * @since 2.18.4 - 2013-08-18
  8596. * @param object $c valendar component
  8597. * @param string $sortArg, optional
  8598. * @return void
  8599. */
  8600. public static function _setSortArgs( & $c, $sortArg=FALSE ) {
  8601. $c->srtk = array( '0', '0', '0', '0' );
  8602. if( 'vtimezone' == $c->objName ) {
  8603. if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
  8604. $c->srtk[0] = 0;
  8605. return;
  8606. }
  8607. elseif( $sortArg ) {
  8608. if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
  8609. $propValues = array();
  8610. $c->_getProperties( $sortArg, $propValues );
  8611. if( !empty( $propValues )) {
  8612. $sk = array_keys( $propValues );
  8613. $c->srtk[0] = $sk[0];
  8614. if( 'RELATED-TO' == $sortArg )
  8615. $c->srtk[0] .= $c->getProperty( 'uid' );
  8616. }
  8617. elseif( 'RELATED-TO' == $sortArg )
  8618. $c->srtk[0] = $c->getProperty( 'uid' );
  8619. }
  8620. elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
  8621. $c->srtk[0] = $d;
  8622. if( 'UID' == $sortArg ) {
  8623. if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
  8624. $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
  8625. if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
  8626. $c->srtk[2] = PHP_INT_MAX;
  8627. }
  8628. else
  8629. $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
  8630. }
  8631. }
  8632. return;
  8633. } // end elseif( $sortArg )
  8634. if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
  8635. $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
  8636. unset( $c->srtk[0]['unparsedtext'] );
  8637. }
  8638. elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
  8639. $c->srtk[0] = 0; // sortkey 0 : dtstart
  8640. if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
  8641. $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] ); // sortkey 1 : dtend/due(/duration)
  8642. unset( $c->srtk[1]['unparsedtext'] );
  8643. }
  8644. elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
  8645. if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
  8646. $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
  8647. unset( $c->srtk[1]['unparsedtext'] );
  8648. }
  8649. elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
  8650. if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
  8651. $c->srtk[1] = 0;
  8652. }
  8653. if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' ))) // sortkey 2 : created/dtstamp
  8654. if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
  8655. $c->srtk[2] = 0;
  8656. if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' ))) // sortkey 3 : uid
  8657. $c->srtk[3] = 0;
  8658. }
  8659. /**
  8660. * break lines at pos 75
  8661. *
  8662. * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
  8663. * break. Long content lines SHOULD be split into a multiple line
  8664. * representations using a line "folding" technique. That is, a long
  8665. * line can be split between any two characters by inserting a CRLF
  8666. * immediately followed by a single linear white space character (i.e.,
  8667. * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
  8668. * of CRLF followed immediately by a single linear white space character
  8669. * is ignored (i.e., removed) when processing the content type.
  8670. *
  8671. * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
  8672. * the reserved expression "\n" in the arg $string could be broken up by the
  8673. * folding of lines, causing ambiguity in the return string.
  8674. *
  8675. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8676. * @since 2.16.2 - 2012-12-18
  8677. * @param string $value
  8678. * @return string
  8679. */
  8680. public static function _size75( $string, $nl ) {
  8681. $tmp = $string;
  8682. $string = '';
  8683. $cCnt = $x = 0;
  8684. while( TRUE ) {
  8685. if( !isset( $tmp[$x] )) {
  8686. $string .= $nl; // loop breakes here
  8687. break;
  8688. }
  8689. elseif(( 74 <= $cCnt ) && ( '\\' == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
  8690. $string .= $nl.' \n'; // don't break lines inside '\n'
  8691. $x += 2;
  8692. if( !isset( $tmp[$x] )) {
  8693. $string .= $nl;
  8694. break;
  8695. }
  8696. $cCnt = 3;
  8697. }
  8698. elseif( 75 <= $cCnt ) {
  8699. $string .= $nl.' ';
  8700. $cCnt = 1;
  8701. }
  8702. $byte = ord( $tmp[$x] );
  8703. $string .= $tmp[$x];
  8704. switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  8705. case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
  8706. $cCnt += 1;
  8707. break; // add a one byte character
  8708. case(( $byte & 0xE0) == 0xC0 ): // characters U-00000080 - U-000007FF, mask 110XXXXX
  8709. if( isset( $tmp[$x+1] )) {
  8710. $cCnt += 1;
  8711. $string .= $tmp[$x+1];
  8712. $x += 1; // add a two bytes character
  8713. }
  8714. break;
  8715. case(( $byte & 0xF0 ) == 0xE0 ): // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  8716. if( isset( $tmp[$x+2] )) {
  8717. $cCnt += 1;
  8718. $string .= $tmp[$x+1].$tmp[$x+2];
  8719. $x += 2; // add a three bytes character
  8720. }
  8721. break;
  8722. case(( $byte & 0xF8 ) == 0xF0 ): // characters U-00010000 - U-001FFFFF, mask 11110XXX
  8723. if( isset( $tmp[$x+3] )) {
  8724. $cCnt += 1;
  8725. $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
  8726. $x += 3; // add a four bytes character
  8727. }
  8728. break;
  8729. case(( $byte & 0xFC ) == 0xF8 ): // characters U-00200000 - U-03FFFFFF, mask 111110XX
  8730. if( isset( $tmp[$x+4] )) {
  8731. $cCnt += 1;
  8732. $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
  8733. $x += 4; // add a five bytes character
  8734. }
  8735. break;
  8736. case(( $byte & 0xFE ) == 0xFC ): // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  8737. if( isset( $tmp[$x+5] )) {
  8738. $cCnt += 1;
  8739. $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
  8740. $x += 5; // add a six bytes character
  8741. }
  8742. default: // add any other byte without counting up $cCnt
  8743. break;
  8744. } // end switch( TRUE )
  8745. $x += 1; // next 'byte' to test
  8746. } // end while( TRUE ) {
  8747. return $string;
  8748. }
  8749. /**
  8750. * sort callback functions for exdate
  8751. *
  8752. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8753. * @since 2.16.11 - 2013-01-12
  8754. * @param array $a
  8755. * @param array $b
  8756. * @return int
  8757. */
  8758. public static function _sortExdate1( $a, $b ) {
  8759. $as = sprintf( '%04d%02d%02d', $a['year'], $a['month'], $a['day'] );
  8760. $as .= ( isset( $a['hour'] )) ? sprintf( '%02d%02d%02d', $a['hour'], $a['min'], $a['sec'] ) : '';
  8761. $bs = sprintf( '%04d%02d%02d', $b['year'], $b['month'], $b['day'] );
  8762. $bs .= ( isset( $b['hour'] )) ? sprintf( '%02d%02d%02d', $b['hour'], $b['min'], $b['sec'] ) : '';
  8763. return strcmp( $as, $bs );
  8764. }
  8765. public static function _sortExdate2( $a, $b ) {
  8766. $val = reset( $a['value'] );
  8767. $as = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8768. $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8769. $val = reset( $b['value'] );
  8770. $bs = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8771. $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8772. return strcmp( $as, $bs );
  8773. }
  8774. /**
  8775. * sort callback functions for rdate
  8776. *
  8777. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8778. * @since 2.16.27 - 2013-07-05
  8779. * @param array $a
  8780. * @param array $b
  8781. * @return int
  8782. */
  8783. public static function _sortRdate1( $a, $b ) {
  8784. $val = isset( $a['year'] ) ? $a : $a[0];
  8785. $as = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8786. $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8787. $val = isset( $b['year'] ) ? $b : $b[0];
  8788. $bs = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8789. $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8790. return strcmp( $as, $bs );
  8791. }
  8792. public static function _sortRdate2( $a, $b ) {
  8793. $val = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
  8794. if( empty( $val ))
  8795. $as = '';
  8796. else {
  8797. $as = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8798. $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8799. }
  8800. $val = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
  8801. if( empty( $val ))
  8802. $bs = '';
  8803. else {
  8804. $bs = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
  8805. $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
  8806. }
  8807. return strcmp( $as, $bs );
  8808. }
  8809. static $parValPrefix = array ( 'MStz' => array( 'utc-', 'utc+', 'gmt-', 'gmt+' )
  8810. , 'Proto3' => array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' )
  8811. , 'Proto4' => array( 'crid:', 'news:', 'pres:' )
  8812. , 'Proto6' => array( 'mailto:' ));
  8813. /**
  8814. * separate property attributes from property value
  8815. *
  8816. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8817. * @since 2.18.6 - 2013-08-29
  8818. * @param string $line, property content
  8819. * @param array $propAttr property parameters
  8820. * @return void
  8821. */
  8822. public static function _splitContent( & $line, & $propAttr=null ) {
  8823. $attr = array();
  8824. $attrix = -1;
  8825. $clen = strlen( $line );
  8826. $WithinQuotes = FALSE;
  8827. $cix = 0;
  8828. while( FALSE !== substr( $line, $cix, 1 )) {
  8829. if( ! $WithinQuotes && ( ':' == $line[$cix] ) &&
  8830. ( substr( $line,$cix, 3 ) != '://' ) &&
  8831. ( ! in_array( strtolower( substr( $line,$cix - 6, 4 )), iCalUtilityFunctions::$parValPrefix['MStz'] )) &&
  8832. ( ! in_array( strtolower( substr( $line,$cix - 3, 4 )), iCalUtilityFunctions::$parValPrefix['Proto3'] )) &&
  8833. ( ! in_array( strtolower( substr( $line,$cix - 4, 5 )), iCalUtilityFunctions::$parValPrefix['Proto4'] )) &&
  8834. ( ! in_array( strtolower( substr( $line,$cix - 6, 7 )), iCalUtilityFunctions::$parValPrefix['Proto6'] ))) {
  8835. $attrEnd = TRUE;
  8836. if(( $cix < ( $clen - 4 )) &&
  8837. ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
  8838. for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
  8839. if( '://' == substr( $line, $c2ix - 2, 3 )) {
  8840. $attrEnd = FALSE;
  8841. break; // an URI with a portnr!!
  8842. }
  8843. }
  8844. }
  8845. if( $attrEnd) {
  8846. $line = substr( $line, ( $cix + 1 ));
  8847. break;
  8848. }
  8849. $cix++;
  8850. }
  8851. if( '"' == $line[$cix] )
  8852. $WithinQuotes = ! $WithinQuotes;
  8853. if( ';' == $line[$cix] )
  8854. $attr[++$attrix] = null;
  8855. else
  8856. $attr[$attrix] .= $line[$cix];
  8857. $cix++;
  8858. }
  8859. /* make attributes in array format */
  8860. $propAttr = array();
  8861. foreach( $attr as $attribute ) {
  8862. $attrsplit = explode( '=', $attribute, 2 );
  8863. if( 1 < count( $attrsplit ))
  8864. $propAttr[$attrsplit[0]] = $attrsplit[1];
  8865. }
  8866. }
  8867. /**
  8868. * step date, return updated date, array and timpstamp
  8869. *
  8870. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8871. * @since 2.14.1 - 2012-09-24
  8872. * @param array $date, date to step
  8873. * @param int $timestamp
  8874. * @param array $step, default array( 'day' => 1 )
  8875. * @return void
  8876. */
  8877. public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
  8878. if( !isset( $date['hour'] )) $date['hour'] = 0;
  8879. if( !isset( $date['min'] )) $date['min'] = 0;
  8880. if( !isset( $date['sec'] )) $date['sec'] = 0;
  8881. foreach( $step as $stepix => $stepvalue )
  8882. $date[$stepix] += $stepvalue;
  8883. $timestamp = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] );
  8884. $d = date( 'Y-m-d-H-i-s', $timestamp);
  8885. $d = explode( '-', $d );
  8886. $date = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
  8887. foreach( $date as $k => $v )
  8888. $date[$k] = (int) $v;
  8889. }
  8890. /**
  8891. * convert a date from specific string to array format
  8892. *
  8893. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8894. * @since 2.11.8 - 2012-01-27
  8895. * @param mixed $input
  8896. * @return bool, TRUE on success
  8897. */
  8898. public static function _strDate2arr( & $input ) {
  8899. if( is_array( $input ))
  8900. return FALSE;
  8901. if( 5 > strlen( (string) $input ))
  8902. return FALSE;
  8903. $work = $input;
  8904. if( 2 == substr_count( $work, '-' ))
  8905. $work = str_replace( '-', '', $work );
  8906. if( 2 == substr_count( $work, '/' ))
  8907. $work = str_replace( '/', '', $work );
  8908. if( !ctype_digit( substr( $work, 0, 8 )))
  8909. return FALSE;
  8910. $temp = array( 'year' => (int) substr( $work, 0, 4 )
  8911. , 'month' => (int) substr( $work, 4, 2 )
  8912. , 'day' => (int) substr( $work, 6, 2 ));
  8913. if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
  8914. return FALSE;
  8915. if( 8 == strlen( $work )) {
  8916. $input = $temp;
  8917. return TRUE;
  8918. }
  8919. if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
  8920. $work = substr( $work, 9 );
  8921. elseif( ctype_digit( substr( $work, 8, 1 )))
  8922. $work = substr( $work, 8 );
  8923. else
  8924. return FALSE;
  8925. if( 2 == substr_count( $work, ':' ))
  8926. $work = str_replace( ':', '', $work );
  8927. if( !ctype_digit( substr( $work, 0, 4 )))
  8928. return FALSE;
  8929. $temp['hour'] = substr( $work, 0, 2 );
  8930. $temp['min'] = substr( $work, 2, 2 );
  8931. if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
  8932. (( 0 > $temp['min'] ) || ( $temp['min'] > 59 )))
  8933. return FALSE;
  8934. if( ctype_digit( substr( $work, 4, 2 ))) {
  8935. $temp['sec'] = substr( $work, 4, 2 );
  8936. if(( 0 > $temp['sec'] ) || ( $temp['sec'] > 59 ))
  8937. return FALSE;
  8938. $len = 6;
  8939. }
  8940. else {
  8941. $temp['sec'] = 0;
  8942. $len = 4;
  8943. }
  8944. if( $len < strlen( $work))
  8945. $temp['tz'] = trim( substr( $work, 6 ));
  8946. $input = $temp;
  8947. return TRUE;
  8948. }
  8949. /**
  8950. * ensures internal date-time/date format for input date-time/date in string fromat
  8951. *
  8952. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  8953. * @since 2.16.24 - 2013-06-26
  8954. * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
  8955. * @param array $datetime
  8956. * @param int $parno optional, default FALSE
  8957. * @param moxed $wtz optional, default null
  8958. * @return array
  8959. */
  8960. public static function _date_time_string( $datetime, $parno=FALSE ) {
  8961. return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null );
  8962. }
  8963. public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
  8964. // save original input string to return it later
  8965. $unparseddatetime = $datetime;
  8966. $datetime = (string) trim( $datetime );
  8967. $tz = null;
  8968. $offset = 0;
  8969. $tzSts = FALSE;
  8970. $len = strlen( $datetime );
  8971. if( 'Z' == substr( $datetime, -1 )) {
  8972. $tz = 'Z';
  8973. $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
  8974. $tzSts = TRUE;
  8975. }
  8976. if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
  8977. $tz = substr( $datetime, -5, 5 );
  8978. $datetime = trim( substr( $datetime, 0, ($len - 5)));
  8979. }
  8980. elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
  8981. $tz = substr( $datetime, -7, 7 );
  8982. $datetime = trim( substr( $datetime, 0, ($len - 7)));
  8983. }
  8984. elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
  8985. $output = $datetime;
  8986. if( !empty( $tz ))
  8987. $output['tz'] = 'Z';
  8988. $output['unparsedtext'] = $unparseddatetime;
  8989. return $output;
  8990. }
  8991. else {
  8992. $cx = $tx = 0; // find any trailing timezone or offset
  8993. $len = strlen( $datetime );
  8994. for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
  8995. $char = substr( $datetime, $cx, 1 );
  8996. if(( ' ' == $char ) || ctype_digit( $char ))
  8997. break; // if exists, tz ends here.. . ?
  8998. else
  8999. $tx--; // tz length counter
  9000. }
  9001. if( 0 > $tx ) { // if any
  9002. $tz = substr( $datetime, $tx );
  9003. $datetime = trim( substr( $datetime, 0, $len + $tx ));
  9004. }
  9005. if(( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' == substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
  9006. ( ctype_digit( substr( $datetime, 0, 14 ))))
  9007. $tzSts = TRUE;
  9008. }
  9009. if( empty( $tz ) && !empty( $wtz ))
  9010. $tz = $wtz;
  9011. if( 3 == $parno )
  9012. $tz = null;
  9013. if( !empty( $tz )) { // tz set
  9014. if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
  9015. $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
  9016. $tz = 'UTC';
  9017. $tzSts = TRUE;
  9018. }
  9019. elseif( !empty( $wtz ))
  9020. $tzSts = TRUE;
  9021. $tz = trim( $tz );
  9022. if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
  9023. $tz = 'UTC';
  9024. if( 0 < substr_count( $datetime, '-' ))
  9025. $datetime = str_replace( '-', '/', $datetime );
  9026. try {
  9027. $d = new DateTime( $datetime, new DateTimeZone( $tz ));
  9028. if( 0 != $offset ) // adjust for offset
  9029. $d->modify( $offset.' seconds' );
  9030. $datestring = $d->format( 'Y-m-d-H-i-s' );
  9031. unset( $d );
  9032. }
  9033. catch( Exception $e ) {
  9034. $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
  9035. }
  9036. } // end if( !empty( $tz ))
  9037. else
  9038. $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
  9039. if( 'UTC' == $tz )
  9040. $tz = 'Z';
  9041. $d = explode( '-', $datestring );
  9042. $output = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
  9043. if( !$parno || ( 3 != $parno )) { // parno is set to 6 or 7
  9044. $output['hour'] = $d[3];
  9045. $output['min'] = $d[4];
  9046. $output['sec'] = $d[5];
  9047. if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
  9048. $output['tz'] = $tz;
  9049. }
  9050. // return original string in the array in case strtotime failed to make sense of it
  9051. $output['unparsedtext'] = $unparseddatetime;
  9052. return $output;
  9053. }
  9054. /********************************************************************************/
  9055. /**
  9056. * special characters management output
  9057. *
  9058. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9059. * @since 2.16.2 - 2012-12-18
  9060. * @param string $string
  9061. * @param string $format
  9062. * @param string $nl
  9063. * @return string
  9064. */
  9065. public static function _strrep( $string, $format, $nl ) {
  9066. switch( $format ) {
  9067. case 'xcal':
  9068. $string = str_replace( '\n', $nl, $string);
  9069. $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
  9070. break;
  9071. default:
  9072. $pos = 0;
  9073. $specChars = array( 'n', 'N', 'r', ',', ';' );
  9074. while( isset( $string[$pos] )) {
  9075. if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
  9076. break;
  9077. if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
  9078. $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
  9079. $pos += 1;
  9080. }
  9081. $pos += 1;
  9082. }
  9083. if( FALSE !== strpos( $string, '"' ))
  9084. $string = str_replace('"', "'", $string);
  9085. if( FALSE !== strpos( $string, ',' ))
  9086. $string = str_replace(',', '\,', $string);
  9087. if( FALSE !== strpos( $string, ';' ))
  9088. $string = str_replace(';', '\;', $string);
  9089. if( FALSE !== strpos( $string, "\r\n" ))
  9090. $string = str_replace( "\r\n", '\n', $string);
  9091. elseif( FALSE !== strpos( $string, "\r" ))
  9092. $string = str_replace( "\r", '\n', $string);
  9093. elseif( FALSE !== strpos( $string, "\n" ))
  9094. $string = str_replace( "\n", '\n', $string);
  9095. if( FALSE !== strpos( $string, '\N' ))
  9096. $string = str_replace( '\N', '\n', $string);
  9097. // if( FALSE !== strpos( $string, $nl ))
  9098. $string = str_replace( $nl, '\n', $string);
  9099. break;
  9100. }
  9101. return $string;
  9102. }
  9103. /**
  9104. * special characters management input (from iCal file)
  9105. *
  9106. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9107. * @since 2.16.2 - 2012-12-18
  9108. * @param string $string
  9109. * @return string
  9110. */
  9111. public static function _strunrep( $string ) {
  9112. $string = str_replace( '\\\\', '\\', $string);
  9113. $string = str_replace( '\,', ',', $string);
  9114. $string = str_replace( '\;', ';', $string);
  9115. // $string = str_replace( '\n', $nl, $string); // ??
  9116. return $string;
  9117. }
  9118. /**
  9119. * convert timestamp to date array, default UTC or adjusted for offset/timezone
  9120. *
  9121. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9122. * @since 2.15.1 - 2012-10-17
  9123. * @param mixed $timestamp
  9124. * @param int $parno
  9125. * @param string $wtz
  9126. * @return array
  9127. */
  9128. public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
  9129. if( is_array( $timestamp )) {
  9130. $tz = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
  9131. $timestamp = $timestamp['timestamp'];
  9132. }
  9133. $tz = ( isset( $tz )) ? $tz : $wtz;
  9134. $offset = 0;
  9135. if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
  9136. $tz = 'UTC';
  9137. elseif( iCalUtilityFunctions::_isOffset( $tz )) {
  9138. $offset = iCalUtilityFunctions::_tz2offset( $tz );
  9139. // $tz = 'UTC';
  9140. }
  9141. try {
  9142. $d = new DateTime( "@$timestamp" ); // set UTC date
  9143. if( 0 != $offset ) // adjust for offset
  9144. $d->modify( $offset.' seconds' );
  9145. elseif( 'UTC' != $tz )
  9146. $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
  9147. $date = $d->format( 'Y-m-d-H-i-s' );
  9148. unset( $d );
  9149. }
  9150. catch( Exception $e ) {
  9151. $date = date( 'Y-m-d-H-i-s', $timestamp );
  9152. }
  9153. $date = explode( '-', $date );
  9154. $output = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
  9155. if( 3 != $parno ) {
  9156. $output['hour'] = $date[3];
  9157. $output['min'] = $date[4];
  9158. $output['sec'] = $date[5];
  9159. if(( 'UTC' == $tz ) || ( 0 == $offset ))
  9160. $output['tz'] = 'Z';
  9161. }
  9162. return $output;
  9163. }
  9164. /**
  9165. * convert timestamp (seconds) to duration in array format
  9166. *
  9167. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9168. * @since 2.6.23 - 2010-10-23
  9169. * @param int $timestamp
  9170. * @return array, duration format
  9171. */
  9172. public static function _timestamp2duration( $timestamp ) {
  9173. $dur = array();
  9174. $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
  9175. $timestamp = $timestamp % ( 7 * 24 * 60 * 60 );
  9176. $dur['day'] = (int) floor( $timestamp / ( 24 * 60 * 60 ));
  9177. $timestamp = $timestamp % ( 24 * 60 * 60 );
  9178. $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
  9179. $timestamp = $timestamp % ( 60 * 60 );
  9180. $dur['min'] = (int) floor( $timestamp / ( 60 ));
  9181. $dur['sec'] = (int) $timestamp % ( 60 );
  9182. return $dur;
  9183. }
  9184. /**
  9185. * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
  9186. *
  9187. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9188. * @since 2.15.1 - 2012-10-17
  9189. * @param mixed $date, date to alter
  9190. * @param string $tzFrom, PHP valid 'from' timezone
  9191. * @param string $tzTo, PHP valid 'to' timezone, default 'UTC'
  9192. * @param string $format, date output format, default 'Ymd\THis'
  9193. * @return bool
  9194. */
  9195. public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
  9196. if( is_array( $date ) && isset( $date['timestamp'] )) {
  9197. try {
  9198. $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
  9199. $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
  9200. }
  9201. catch( Exception $e ) { return FALSE; }
  9202. }
  9203. else {
  9204. if( iCalUtilityFunctions::_isArrayDate( $date )) {
  9205. if( isset( $date['tz'] ))
  9206. unset( $date['tz'] );
  9207. $date = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
  9208. }
  9209. if( 'Z' == substr( $date, -1 ))
  9210. $date = substr( $date, 0, ( strlen( $date ) - 2 ));
  9211. try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
  9212. catch( Exception $e ) { return FALSE; }
  9213. }
  9214. try { $d->setTimezone( new DateTimeZone( $tzTo )); }
  9215. catch( Exception $e ) { return FALSE; }
  9216. $date = $d->format( $format );
  9217. return TRUE;
  9218. }
  9219. /**
  9220. * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
  9221. *
  9222. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9223. * @since 2.11.4 - 2012-01-11
  9224. * @param string $offset
  9225. * @return integer
  9226. */
  9227. public static function _tz2offset( $tz ) {
  9228. $tz = trim( (string) $tz );
  9229. $offset = 0;
  9230. if((( 5 != strlen( $tz )) && ( 7 != strlen( $tz ))) ||
  9231. (( '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
  9232. (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
  9233. (( 7 == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
  9234. return $offset;
  9235. $hours2sec = (int) substr( $tz, 1, 2 ) * 3600;
  9236. $min2sec = (int) substr( $tz, 3, 2 ) * 60;
  9237. $sec = ( 7 == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
  9238. $offset = $hours2sec + $min2sec + $sec;
  9239. $offset = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
  9240. return $offset;
  9241. }
  9242. }
  9243. /*********************************************************************************/
  9244. /* iCalcreator vCard helper functions */
  9245. /*********************************************************************************/
  9246. /**
  9247. * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard
  9248. * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE
  9249. *
  9250. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9251. * @since 2.12.2 - 2012-07-11
  9252. * @param object $email
  9253. * $param string $version, vCard version (default 2.1)
  9254. * $param string $directory, where to save vCards (default FALSE)
  9255. * $param string $ext, vCard file extension (default 'vcf')
  9256. * @return mixed
  9257. */
  9258. function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) {
  9259. if( FALSE === ( $pos = strpos( $email, '@' )))
  9260. return FALSE;
  9261. if( $directory ) {
  9262. if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR ))))
  9263. $directory .= DIRECTORY_SEPARATOR;
  9264. if( !is_dir( $directory ) || !is_writable( $directory ))
  9265. return FALSE;
  9266. }
  9267. /* prepare vCard */
  9268. $email = str_replace( 'MAILTO:', '', $email );
  9269. $name = $person = substr( $email, 0, $pos );
  9270. if( ctype_upper( $name ) || ctype_lower( $name ))
  9271. $name = array( $name );
  9272. else {
  9273. if( FALSE !== ( $pos = strpos( $name, '.' ))) {
  9274. $name = explode( '.', $name );
  9275. foreach( $name as $k => $part )
  9276. $name[$k] = ucfirst( $part );
  9277. }
  9278. else { // split camelCase
  9279. $chars = $name;
  9280. $name = array( $chars[0] );
  9281. $k = 0;
  9282. $x = 1;
  9283. while( FALSE !== ( $char = substr( $chars, $x, 1 ))) {
  9284. if( ctype_upper( $char )) {
  9285. $k += 1;
  9286. $name[$k] = '';
  9287. }
  9288. $name[$k] .= $char;
  9289. $x++;
  9290. }
  9291. }
  9292. }
  9293. $nl = "\r\n";
  9294. $FN = 'FN:'.implode( ' ', $name ).$nl;
  9295. $name = array_reverse( $name );
  9296. $N = 'N:'.array_shift( $name );
  9297. $scCnt = 0;
  9298. while( NULL != ( $part = array_shift( $name ))) {
  9299. if(( '4.0' != $version ) || ( 4 > $scCnt ))
  9300. $scCnt += 1;
  9301. $N .= ';'.$part;
  9302. }
  9303. while(( '4.0' == $version ) && ( 4 > $scCnt )) {
  9304. $N .= ';';
  9305. $scCnt += 1;
  9306. }
  9307. $N .= $nl;
  9308. $EMAIL = 'EMAIL:'.$email.$nl;
  9309. /* create vCard */
  9310. $vCard = 'BEGIN:VCARD'.$nl;
  9311. $vCard .= "VERSION:$version$nl";
  9312. $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl";
  9313. $vCard .= $N;
  9314. $vCard .= $FN;
  9315. $vCard .= $EMAIL;
  9316. $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl;
  9317. $vCard .= 'END:VCARD'.$nl;
  9318. /* save each vCard as (unique) single file */
  9319. if( $directory ) {
  9320. $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email );
  9321. $cnt = 1;
  9322. $dbl = '';
  9323. while( is_file ( $fname.$dbl.'.'.$ext )) {
  9324. $cnt += 1;
  9325. $dbl = "_$cnt";
  9326. }
  9327. if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext ))
  9328. return FALSE;
  9329. return TRUE;
  9330. }
  9331. /* return vCard */
  9332. else
  9333. return $vCard;
  9334. }
  9335. /**
  9336. * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards
  9337. *
  9338. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9339. * @since 2.12.2 - 2012-05-07
  9340. * @param object $calendar, iCalcreator vcalendar instance reference
  9341. * $param string $version, vCard version (default 2.1)
  9342. * $param string $directory, where to save vCards (default FALSE)
  9343. * $param string $ext, vCard file extension (default 'vcf')
  9344. * @return mixed
  9345. */
  9346. function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) {
  9347. $hits = array();
  9348. $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' );
  9349. foreach( $vCardP as $prop ) {
  9350. $hits2 = $calendar->getProperty( $prop );
  9351. foreach( $hits2 as $propValue => $occCnt ) {
  9352. if( FALSE === ( $pos = strpos( $propValue, '@' )))
  9353. continue;
  9354. $propValue = str_replace( 'MAILTO:', '', $propValue );
  9355. if( isset( $hits[$propValue] ))
  9356. $hits[$propValue] += $occCnt;
  9357. else
  9358. $hits[$propValue] = $occCnt;
  9359. }
  9360. }
  9361. if( empty( $hits ))
  9362. return FALSE;
  9363. ksort( $hits );
  9364. $output = '';
  9365. foreach( $hits as $email => $skip ) {
  9366. $res = iCal2vCard( $email, $version, $directory, $ext );
  9367. if( $directory && !$res )
  9368. return FALSE;
  9369. elseif( !$res )
  9370. return $res;
  9371. else
  9372. $output .= $res;
  9373. }
  9374. if( $directory )
  9375. return TRUE;
  9376. if( !empty( $output ))
  9377. return $output;
  9378. return FALSE;
  9379. }
  9380. /*********************************************************************************/
  9381. /* iCalcreator XML (rfc6321) helper functions */
  9382. /*********************************************************************************/
  9383. /**
  9384. * format iCal XML output, rfc6321, using PHP SimpleXMLElement
  9385. *
  9386. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9387. * @since 2.18.1 - 2013-08-18
  9388. * @param object $calendar, iCalcreator vcalendar instance reference
  9389. * @return string
  9390. */
  9391. function iCal2XML( & $calendar ) {
  9392. /** fix an SimpleXMLElement instance and create root element */
  9393. $xmlstr = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
  9394. $xmlstr .= '<!-- created '.gmdate( 'Ymd\THis\Z' );
  9395. $xmlstr .= ' using kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
  9396. $xmlstr .= '</icalendar>';
  9397. $xml = new SimpleXMLElement( $xmlstr );
  9398. $vcalendar = $xml->addChild( 'vcalendar' );
  9399. /** fix calendar properties */
  9400. $properties = $vcalendar->addChild( 'properties' );
  9401. $calProps = array( 'version', 'prodid', 'calscale', 'method' );
  9402. foreach( $calProps as $calProp ) {
  9403. if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
  9404. _addXMLchild( $properties, $calProp, 'text', $content );
  9405. }
  9406. while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
  9407. _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
  9408. $langCal = $calendar->getConfig( 'language' );
  9409. /** prepare to fix components with properties */
  9410. $components = $vcalendar->addChild( 'components' );
  9411. /** fix component properties */
  9412. while( FALSE !== ( $component = $calendar->getComponent())) {
  9413. $compName = $component->objName;
  9414. $child = $components->addChild( $compName );
  9415. $properties = $child->addChild( 'properties' );
  9416. $langComp = $component->getConfig( 'language' );
  9417. $props = $component->getConfig( 'setPropertyNames' );
  9418. foreach( $props as $prop ) {
  9419. switch( strtolower( $prop )) {
  9420. case 'attach': // may occur multiple times, below
  9421. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9422. $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
  9423. unset( $content['params']['VALUE'] );
  9424. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9425. }
  9426. break;
  9427. case 'attendee':
  9428. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9429. if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
  9430. if( $langComp )
  9431. $content['params']['LANGUAGE'] = $langComp;
  9432. elseif( $langCal )
  9433. $content['params']['LANGUAGE'] = $langCal;
  9434. }
  9435. _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
  9436. }
  9437. break;
  9438. case 'exdate':
  9439. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9440. $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
  9441. unset( $content['params']['VALUE'] );
  9442. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9443. }
  9444. break;
  9445. case 'freebusy':
  9446. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9447. if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
  9448. $content['params']['FBTYPE'] = $content['value']['fbtype'];
  9449. unset( $content['value']['fbtype'] );
  9450. }
  9451. _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
  9452. }
  9453. break;
  9454. case 'request-status':
  9455. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9456. if( !isset( $content['params']['LANGUAGE'] )) {
  9457. if( $langComp )
  9458. $content['params']['LANGUAGE'] = $langComp;
  9459. elseif( $langCal )
  9460. $content['params']['LANGUAGE'] = $langCal;
  9461. }
  9462. _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
  9463. }
  9464. break;
  9465. case 'rdate':
  9466. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9467. $type = 'date-time';
  9468. if( isset( $content['params']['VALUE'] )) {
  9469. if( 'DATE' == $content['params']['VALUE'] )
  9470. $type = 'date';
  9471. elseif( 'PERIOD' == $content['params']['VALUE'] )
  9472. $type = 'period';
  9473. }
  9474. unset( $content['params']['VALUE'] );
  9475. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9476. }
  9477. break;
  9478. case 'categories':
  9479. case 'comment':
  9480. case 'contact':
  9481. case 'description':
  9482. case 'related-to':
  9483. case 'resources':
  9484. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9485. if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
  9486. if( $langComp )
  9487. $content['params']['LANGUAGE'] = $langComp;
  9488. elseif( $langCal )
  9489. $content['params']['LANGUAGE'] = $langCal;
  9490. }
  9491. _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
  9492. }
  9493. break;
  9494. case 'x-prop':
  9495. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9496. _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
  9497. break;
  9498. case 'created': // single occurence below, if set
  9499. case 'completed':
  9500. case 'dtstamp':
  9501. case 'last-modified':
  9502. $utcDate = TRUE;
  9503. case 'dtstart':
  9504. case 'dtend':
  9505. case 'due':
  9506. case 'recurrence-id':
  9507. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9508. $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
  9509. unset( $content['params']['VALUE'] );
  9510. if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
  9511. unset( $content['params']['TZID'] );
  9512. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9513. }
  9514. unset( $utcDate );
  9515. break;
  9516. case 'duration':
  9517. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9518. _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
  9519. break;
  9520. case 'exrule':
  9521. case 'rrule':
  9522. while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9523. _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
  9524. break;
  9525. case 'class':
  9526. case 'location':
  9527. case 'status':
  9528. case 'summary':
  9529. case 'transp':
  9530. case 'tzid':
  9531. case 'uid':
  9532. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9533. if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
  9534. if( $langComp )
  9535. $content['params']['LANGUAGE'] = $langComp;
  9536. elseif( $langCal )
  9537. $content['params']['LANGUAGE'] = $langCal;
  9538. }
  9539. _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
  9540. }
  9541. break;
  9542. case 'geo':
  9543. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9544. _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
  9545. break;
  9546. case 'organizer':
  9547. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
  9548. if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
  9549. if( $langComp )
  9550. $content['params']['LANGUAGE'] = $langComp;
  9551. elseif( $langCal )
  9552. $content['params']['LANGUAGE'] = $langCal;
  9553. }
  9554. _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
  9555. }
  9556. break;
  9557. case 'percent-complete':
  9558. case 'priority':
  9559. case 'sequence':
  9560. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9561. _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
  9562. break;
  9563. case 'tzurl':
  9564. case 'url':
  9565. if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
  9566. _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
  9567. break;
  9568. } // end switch( $prop )
  9569. } // end foreach( $props as $prop )
  9570. /** fix subComponent properties, if any */
  9571. while( FALSE !== ( $subcomp = $component->getComponent())) {
  9572. $subCompName = $subcomp->objName;
  9573. $child2 = $child->addChild( $subCompName );
  9574. $properties = $child2->addChild( 'properties' );
  9575. $langComp = $subcomp->getConfig( 'language' );
  9576. $subCompProps = $subcomp->getConfig( 'setPropertyNames' );
  9577. foreach( $subCompProps as $prop ) {
  9578. switch( strtolower( $prop )) {
  9579. case 'attach': // may occur multiple times, below
  9580. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9581. $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
  9582. unset( $content['params']['VALUE'] );
  9583. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9584. }
  9585. break;
  9586. case 'attendee':
  9587. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9588. if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
  9589. if( $langComp )
  9590. $content['params']['LANGUAGE'] = $langComp;
  9591. elseif( $langCal )
  9592. $content['params']['LANGUAGE'] = $langCal;
  9593. }
  9594. _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
  9595. }
  9596. break;
  9597. case 'comment':
  9598. case 'tzname':
  9599. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9600. if( !isset( $content['params']['LANGUAGE'] )) {
  9601. if( $langComp )
  9602. $content['params']['LANGUAGE'] = $langComp;
  9603. elseif( $langCal )
  9604. $content['params']['LANGUAGE'] = $langCal;
  9605. }
  9606. _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
  9607. }
  9608. break;
  9609. case 'rdate':
  9610. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9611. $type = 'date-time';
  9612. if( isset( $content['params']['VALUE'] )) {
  9613. if( 'DATE' == $content['params']['VALUE'] )
  9614. $type = 'date';
  9615. elseif( 'PERIOD' == $content['params']['VALUE'] )
  9616. $type = 'period';
  9617. }
  9618. unset( $content['params']['VALUE'] );
  9619. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9620. }
  9621. break;
  9622. case 'x-prop':
  9623. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
  9624. _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
  9625. break;
  9626. case 'action': // single occurence below, if set
  9627. case 'description':
  9628. case 'summary':
  9629. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9630. if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
  9631. if( $langComp )
  9632. $content['params']['LANGUAGE'] = $langComp;
  9633. elseif( $langCal )
  9634. $content['params']['LANGUAGE'] = $langCal;
  9635. }
  9636. _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
  9637. }
  9638. break;
  9639. case 'dtstart':
  9640. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9641. unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
  9642. _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
  9643. }
  9644. break;
  9645. case 'duration':
  9646. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
  9647. _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
  9648. break;
  9649. case 'repeat':
  9650. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
  9651. _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
  9652. break;
  9653. case 'trigger':
  9654. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
  9655. if( isset( $content['value']['year'] ) &&
  9656. isset( $content['value']['month'] ) &&
  9657. isset( $content['value']['day'] ))
  9658. $type = 'date-time';
  9659. else {
  9660. $type = 'duration';
  9661. if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
  9662. $content['params']['RELATED'] = 'END';
  9663. }
  9664. _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
  9665. }
  9666. break;
  9667. case 'tzoffsetto':
  9668. case 'tzoffsetfrom':
  9669. if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
  9670. _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
  9671. break;
  9672. case 'rrule':
  9673. while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
  9674. _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
  9675. break;
  9676. } // switch( $prop )
  9677. } // end foreach( $subCompProps as $prop )
  9678. } // end while( FALSE !== ( $subcomp = $component->getComponent()))
  9679. } // end while( FALSE !== ( $component = $calendar->getComponent()))
  9680. return $xml->asXML();
  9681. }
  9682. /**
  9683. * Add children to a SimpleXMLelement
  9684. *
  9685. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9686. * @since 2.18.10 - 2013-09-04
  9687. * @param object $parent, reference to a SimpleXMLelement node
  9688. * @param string $name, new element node name
  9689. * @param string $type, content type, subelement(-s) name
  9690. * @param string $content, new subelement content
  9691. * @param array $params, new element 'attributes'
  9692. * @return void
  9693. */
  9694. function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
  9695. /** create new child node */
  9696. $name = strtolower( $name );
  9697. $child = $parent->addChild( $name );
  9698. if( !empty( $params )) {
  9699. $parameters = $child->addChild( 'parameters' );
  9700. foreach( $params as $param => $parVal ) {
  9701. if( 'VALUE' == $param )
  9702. continue;
  9703. $param = strtolower( $param );
  9704. if( 'x-' == substr( $param, 0, 2 )) {
  9705. $p1 = $parameters->addChild( $param );
  9706. $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
  9707. }
  9708. else {
  9709. $p1 = $parameters->addChild( $param );
  9710. switch( $param ) {
  9711. case 'altrep':
  9712. case 'dir': $ptype = 'uri'; break;
  9713. case 'delegated-from':
  9714. case 'delegated-to':
  9715. case 'member':
  9716. case 'sent-by': $ptype = 'cal-address'; break;
  9717. case 'rsvp': $ptype = 'boolean'; break ;
  9718. default: $ptype = 'text'; break;
  9719. }
  9720. if( is_array( $parVal )) {
  9721. foreach( $parVal as $pV )
  9722. $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
  9723. }
  9724. else
  9725. $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
  9726. }
  9727. }
  9728. } // end if( !empty( $params ))
  9729. if(( empty( $content ) && ( '0' != $content )) || ( !is_array( $content) && ( '-' != substr( $content, 0, 1 ) && ( 0 > $content ))))
  9730. return;
  9731. /** store content */
  9732. switch( $type ) {
  9733. case 'binary':
  9734. $v = $child->addChild( $type, $content );
  9735. break;
  9736. case 'boolean':
  9737. break;
  9738. case 'cal-address':
  9739. $v = $child->addChild( $type, $content );
  9740. break;
  9741. case 'date':
  9742. if( array_key_exists( 'year', $content ))
  9743. $content = array( $content );
  9744. foreach( $content as $date ) {
  9745. $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
  9746. $v = $child->addChild( $type, $str );
  9747. }
  9748. break;
  9749. case 'date-time':
  9750. if( array_key_exists( 'year', $content ))
  9751. $content = array( $content );
  9752. foreach( $content as $dt ) {
  9753. if( !isset( $dt['hour'] )) $dt['hour'] = 0;
  9754. if( !isset( $dt['min'] )) $dt['min'] = 0;
  9755. if( !isset( $dt['sec'] )) $dt['sec'] = 0;
  9756. $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
  9757. if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
  9758. $str .= 'Z';
  9759. $v = $child->addChild( $type, $str );
  9760. }
  9761. break;
  9762. case 'duration':
  9763. $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
  9764. $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
  9765. break;
  9766. case 'geo':
  9767. if( !empty( $content )) {
  9768. $v1 = $child->addChild( 'latitude', iCalUtilityFunctions::_geo2str2( $content['latitude'], iCalUtilityFunctions::$geoLatFmt ));
  9769. $v1 = $child->addChild( 'longitude', iCalUtilityFunctions::_geo2str2( $content['longitude'], iCalUtilityFunctions::$geoLongFmt ));
  9770. }
  9771. break;
  9772. case 'integer':
  9773. $v = $child->addChild( $type, (string) $content );
  9774. break;
  9775. case 'period':
  9776. if( !is_array( $content ))
  9777. break;
  9778. foreach( $content as $period ) {
  9779. $v1 = $child->addChild( $type );
  9780. $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
  9781. if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
  9782. $str .= 'Z';
  9783. $v2 = $v1->addChild( 'start', $str );
  9784. if( array_key_exists( 'year', $period[1] )) {
  9785. $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
  9786. if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
  9787. $str .= 'Z';
  9788. $v2 = $v1->addChild( 'end', $str );
  9789. }
  9790. else
  9791. $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
  9792. }
  9793. break;
  9794. case 'recur':
  9795. $content = array_change_key_case( $content );
  9796. foreach( $content as $rulelabel => $rulevalue ) {
  9797. switch( $rulelabel ) {
  9798. case 'until':
  9799. if( isset( $rulevalue['hour'] ))
  9800. $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
  9801. else
  9802. $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
  9803. $v = $child->addChild( $rulelabel, $str );
  9804. break;
  9805. case 'bysecond':
  9806. case 'byminute':
  9807. case 'byhour':
  9808. case 'bymonthday':
  9809. case 'byyearday':
  9810. case 'byweekno':
  9811. case 'bymonth':
  9812. case 'bysetpos': {
  9813. if( is_array( $rulevalue )) {
  9814. foreach( $rulevalue as $vix => $valuePart )
  9815. $v = $child->addChild( $rulelabel, $valuePart );
  9816. }
  9817. else
  9818. $v = $child->addChild( $rulelabel, $rulevalue );
  9819. break;
  9820. }
  9821. case 'byday': {
  9822. if( isset( $rulevalue['DAY'] )) {
  9823. $str = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
  9824. $str .= $rulevalue['DAY'];
  9825. $p = $child->addChild( $rulelabel, $str );
  9826. }
  9827. else {
  9828. foreach( $rulevalue as $valuePart ) {
  9829. if( isset( $valuePart['DAY'] )) {
  9830. $str = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
  9831. $str .= $valuePart['DAY'];
  9832. $p = $child->addChild( $rulelabel, $str );
  9833. }
  9834. else
  9835. $p = $child->addChild( $rulelabel, $valuePart );
  9836. }
  9837. }
  9838. break;
  9839. }
  9840. case 'freq':
  9841. case 'count':
  9842. case 'interval':
  9843. case 'wkst':
  9844. default:
  9845. $p = $child->addChild( $rulelabel, $rulevalue );
  9846. break;
  9847. } // end switch( $rulelabel )
  9848. } // end foreach( $content as $rulelabel => $rulevalue )
  9849. break;
  9850. case 'rstatus':
  9851. $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
  9852. $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
  9853. if( isset( $content['extdata'] ))
  9854. $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
  9855. break;
  9856. case 'text':
  9857. if( !is_array( $content ))
  9858. $content = array( $content );
  9859. foreach( $content as $part )
  9860. $v = $child->addChild( $type, htmlspecialchars( $part ));
  9861. break;
  9862. case 'time':
  9863. break;
  9864. case 'uri':
  9865. $v = $child->addChild( $type, $content );
  9866. break;
  9867. case 'utc-offset':
  9868. if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
  9869. $str = substr( $content, 0, 1 );
  9870. $content = substr( $content, 1 );
  9871. }
  9872. else
  9873. $str = '+';
  9874. $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
  9875. if( 4 < strlen( $content ))
  9876. $str .= ':'.substr( $content, 4 );
  9877. $v = $child->addChild( $type, $str );
  9878. break;
  9879. case 'unknown':
  9880. default:
  9881. if( is_array( $content ))
  9882. $content = implode( '', $content );
  9883. $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
  9884. break;
  9885. }
  9886. }
  9887. /**
  9888. * parse xml file into iCalcreator instance
  9889. *
  9890. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9891. * @since 2.16.22 - 2013-06-18
  9892. * @param string $xmlfile
  9893. * @param array $iCalcfg iCalcreator config array (opt)
  9894. * @return mixediCalcreator instance or FALSE on error
  9895. */
  9896. function XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
  9897. if( FALSE === ( $xmlstr = file_get_contents( $xmlfile )))
  9898. return FALSE;
  9899. return xml2iCal( $xmlstr, $iCalcfg );
  9900. }
  9901. /**
  9902. * parse xml string into iCalcreator instance, alias of XML2iCal
  9903. *
  9904. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9905. * @since 2.16.22 - 2013-06-18
  9906. * @param string $xmlstr
  9907. * @param array $iCalcfg iCalcreator config array (opt)
  9908. * @return mixed iCalcreator instance or FALSE on error
  9909. */
  9910. function XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
  9911. return XML2iCal( $xmlstr, $iCalcfg);
  9912. }
  9913. /**
  9914. * parse xml string into iCalcreator instance
  9915. *
  9916. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9917. * @since 2.16.22 - 2013-06-20
  9918. * @param string $xmlstr
  9919. * @param array $iCalcfg iCalcreator config array (opt)
  9920. * @return mixed iCalcreator instance or FALSE on error
  9921. */
  9922. function XML2iCal( $xmlstr, $iCalcfg=array()) {
  9923. $xmlstr = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), '', $xmlstr );
  9924. $xml = XMLgetTagContent1( $xmlstr, 'vcalendar', $endIx );
  9925. $iCal = new vcalendar( $iCalcfg );
  9926. XMLgetComps( $iCal, $xmlstr );
  9927. unset( $xmlstr );
  9928. return $iCal;
  9929. }
  9930. /**
  9931. * parse XML string into iCalcreator components
  9932. *
  9933. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9934. * @since 2.16.22 - 2013-06-20
  9935. * @param object $iCal, iCalcreator vcalendar or component object instance
  9936. * @param string $xml
  9937. * @return bool
  9938. */
  9939. function XMLgetComps( $iCal, $xml ) {
  9940. static $comps = array( 'vtimezone', 'standard', 'daylight', 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm' );
  9941. $sx = 0;
  9942. while(( FALSE !== substr( $xml, ( $sx + 11 ), 1 )) &&
  9943. ( '<properties>' != substr( $xml, $sx, 12 )) && ( '<components>' != substr( $xml, $sx, 12 )))
  9944. $sx += 1;
  9945. if( FALSE === substr( $xml, ( $sx + 11 ), 1 ))
  9946. return FALSE;
  9947. if( '<properties>' == substr( $xml, $sx, 12 )) {
  9948. $xml2 = XMLgetTagContent1( $xml, 'properties', $endIx );
  9949. XMLgetProps( $iCal, $xml2 );
  9950. $xml = substr( $xml, $endIx );
  9951. }
  9952. if( '<components>' == substr( $xml, 0, 12 ))
  9953. $xml = XMLgetTagContent1( $xml, 'components', $endIx );
  9954. while( ! empty( $xml )) {
  9955. $xml2 = XMLgetTagContent2( $xml, $tagName, $endIx );
  9956. if( in_array( strtolower( $tagName ), $comps ) && ( FALSE !== ( $subComp = $iCal->newComponent( $tagName ))))
  9957. XMLgetComps( $subComp, $xml2 );
  9958. $xml = substr( $xml, $endIx);
  9959. }
  9960. unset( $xml );
  9961. return $iCal;
  9962. }
  9963. /**
  9964. * parse XML into iCalcreator properties
  9965. *
  9966. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  9967. * @since 2.16.21 - 2013-06-23
  9968. * @param array $iCal iCalcreator calendar/component instance
  9969. * @param string $xml
  9970. * @return void
  9971. */
  9972. function XMLgetProps( $iCal, $xml) {
  9973. while( ! empty( $xml )) {
  9974. $xml2 = XMLgetTagContent2( $xml, $propName, $endIx );
  9975. $propName = strtoupper( $propName );
  9976. if( empty( $xml2 ) && ( '0' != $xml2 )) {
  9977. $iCal->setProperty( $propName );
  9978. $xml = substr( $xml, $endIx);
  9979. continue;
  9980. }
  9981. $params = array();
  9982. if( '<parameters/>' == substr( $xml2, 0, 13 ))
  9983. $xml2 = substr( $xml2, 13 );
  9984. elseif( '<parameters>' == substr( $xml2, 0, 12 )) {
  9985. $xml3 = XMLgetTagContent1( $xml2, 'parameters', $endIx2 );
  9986. while( ! empty( $xml3 )) {
  9987. $xml4 = XMLgetTagContent2( $xml3, $paramKey, $endIx3 );
  9988. $pType = FALSE; // skip parameter valueType
  9989. $paramKey = strtoupper( $paramKey );
  9990. if( in_array( $paramKey, array( 'DELEGATED-FROM', 'DELEGATED-TO', 'MEMBER' ))) {
  9991. while( ! empty( $xml4 )) {
  9992. if( ! isset( $params[$paramKey] ))
  9993. $params[$paramKey] = array( XMLgetTagContent1( $xml4, 'cal-address', $endIx4 ));
  9994. else
  9995. $params[$paramKey][] = XMLgetTagContent1( $xml4, 'cal-address', $endIx4 );
  9996. $xml4 = substr( $xml4, $endIx4 );
  9997. }
  9998. }
  9999. else {
  10000. if( ! isset( $params[$paramKey] ))
  10001. $params[$paramKey] = html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
  10002. else
  10003. $params[$paramKey] .= ','.html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
  10004. }
  10005. $xml3 = substr( $xml3, $endIx3 );
  10006. }
  10007. $xml2 = substr( $xml2, $endIx2 );
  10008. } // if( '<parameters>' == substr( $xml2, 0, 12 ))
  10009. $valueType = FALSE;
  10010. $value = ( ! empty( $xml2 ) || ( '0' == $xml2 )) ? XMLgetTagContent2( $xml2, $valueType, $endIx3 ) : '';
  10011. switch( $propName ) {
  10012. case 'CATEGORIES':
  10013. case 'RESOURCES':
  10014. $tValue = array();
  10015. while( ! empty( $xml2 )) {
  10016. $tValue[] = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
  10017. $xml2 = substr( $xml2, $endIx4 );
  10018. }
  10019. $value = $tValue;
  10020. break;
  10021. case 'EXDATE': // multiple single-date(-times) may exist
  10022. case 'RDATE':
  10023. if( 'period' != $valueType ) {
  10024. if( 'date' == $valueType )
  10025. $params['VALUE'] = 'DATE';
  10026. $t = array();
  10027. while( ! empty( $xml2 ) && ( '<date' == substr( $xml2, 0, 5 ))) {
  10028. $t[] = XMLgetTagContent2( $xml2, $pType, $endIx4 );
  10029. $xml2 = substr( $xml2, $endIx4 );
  10030. }
  10031. $value = $t;
  10032. break;
  10033. }
  10034. case 'FREEBUSY':
  10035. if( 'RDATE' == $propName )
  10036. $params['VALUE'] = 'PERIOD';
  10037. $value = array();
  10038. while( ! empty( $xml2 ) && ( '<period>' == substr( $xml2, 0, 8 ))) {
  10039. $xml3 = XMLgetTagContent1( $xml2, 'period', $endIx4 ); // period
  10040. $t = array();
  10041. while( ! empty( $xml3 )) {
  10042. $t[] = XMLgetTagContent2( $xml3, $pType, $endIx5 ); // start - end/duration
  10043. $xml3 = substr( $xml3, $endIx5 );
  10044. }
  10045. $value[] = $t;
  10046. $xml2 = substr( $xml2, $endIx4 );
  10047. }
  10048. break;
  10049. case 'TZOFFSETTO':
  10050. case 'TZOFFSETFROM':
  10051. $value = str_replace( ':', '', $value );
  10052. break;
  10053. case 'GEO':
  10054. $tValue = array( 'latitude' => $value );
  10055. $tValue['longitude'] = XMLgetTagContent1( substr( $xml2, $endIx3 ), 'longitude', $endIx3 );
  10056. $value = $tValue;
  10057. break;
  10058. case 'EXRULE':
  10059. case 'RRULE':
  10060. $tValue = array( $valueType => $value );
  10061. $xml2 = substr( $xml2, $endIx3 );
  10062. $valueType = FALSE;
  10063. while( ! empty( $xml2 )) {
  10064. $t = XMLgetTagContent2( $xml2, $valueType, $endIx4 );
  10065. switch( $valueType ) {
  10066. case 'freq':
  10067. case 'count':
  10068. case 'until':
  10069. case 'interval':
  10070. case 'wkst':
  10071. $tValue[$valueType] = $t;
  10072. break;
  10073. case 'byday':
  10074. if( 2 == strlen( $t ))
  10075. $tValue[$valueType][] = array( 'DAY' => $t );
  10076. else {
  10077. $day = substr( $t, -2 );
  10078. $key = substr( $t, 0, ( strlen( $t ) - 2 ));
  10079. $tValue[$valueType][] = array( $key, 'DAY' => $day );
  10080. }
  10081. break;
  10082. default:
  10083. $tValue[$valueType][] = $t;
  10084. }
  10085. $xml2 = substr( $xml2, $endIx4 );
  10086. }
  10087. $value = $tValue;
  10088. break;
  10089. case 'REQUEST-STATUS':
  10090. $tValue = array();
  10091. while( ! empty( $xml2 )) {
  10092. $t = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
  10093. $tValue[$valueType] = $t;
  10094. $xml2 = substr( $xml2, $endIx4 );
  10095. }
  10096. if( ! empty( $tValue ))
  10097. $value = $tValue;
  10098. else
  10099. $value = array( 'code' => null, 'description' => null );
  10100. break;
  10101. default:
  10102. switch( $valueType ) {
  10103. case 'binary': $params['VALUE'] = 'BINARY'; break;
  10104. case 'date': $params['VALUE'] = 'DATE'; break;
  10105. case 'date-time': $params['VALUE'] = 'DATE-TIME'; break;
  10106. case 'text':
  10107. case 'unknown': $value = html_entity_decode( $value ); break;
  10108. }
  10109. break;
  10110. } // end switch( $propName )
  10111. if( 'FREEBUSY' == $propName ) {
  10112. $fbtype = $params['FBTYPE'];
  10113. unset( $params['FBTYPE'] );
  10114. $iCal->setProperty( $propName, $fbtype, $value, $params );
  10115. }
  10116. elseif( 'GEO' == $propName )
  10117. $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
  10118. elseif( 'REQUEST-STATUS' == $propName ) {
  10119. if( !isset( $value['data'] ))
  10120. $value['data'] = FALSE;
  10121. $iCal->setProperty( $propName, $value['code'], $value['description'], $value['data'], $params );
  10122. }
  10123. else {
  10124. if( empty( $value ) && ( is_array( $value ) || ( '0' > $value )))
  10125. $value = '';
  10126. $iCal->setProperty( $propName, $value, $params );
  10127. }
  10128. $xml = substr( $xml, $endIx);
  10129. } // end while( ! empty( $xml ))
  10130. }
  10131. /**
  10132. * fetch a specific XML tag content
  10133. *
  10134. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  10135. * @since 2.16.22 - 2013-06-20
  10136. * @param string $xml
  10137. * @param string $tagName
  10138. * @param int $endIx
  10139. * @return mixed
  10140. */
  10141. function XMLgetTagContent1( $xml, $tagName, & $endIx=0 ) {
  10142. $strlen = strlen( $tagName );
  10143. $sx1 = 0;
  10144. while( FALSE !== substr( $xml, $sx1, 1 )) {
  10145. if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 1 ), 1 )) &&
  10146. ( strtolower( "<$tagName>" ) == strtolower( substr( $xml, $sx1, ( $strlen + 2 )))))
  10147. break;
  10148. if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 3 ), 1 )) &&
  10149. ( strtolower( "<$tagName />" ) == strtolower( substr( $xml, $sx1, ( $strlen + 4 ))))) { // empty tag
  10150. $endIx = $strlen + 5;
  10151. return '';
  10152. }
  10153. if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 2 ), 1 )) &&
  10154. ( strtolower( "<$tagName/>" ) == strtolower( substr( $xml, $sx1, ( $strlen + 3 ))))) { // empty tag
  10155. $endIx = $strlen + 4;
  10156. return '';
  10157. }
  10158. $sx1 += 1;
  10159. }
  10160. if( FALSE === substr( $xml, $sx1, 1 )) {
  10161. $endIx = ( empty( $sx )) ? 0 : $sx - 1;
  10162. return '';
  10163. }
  10164. if( FALSE === ( $pos = stripos( $xml, "</$tagName>" ))) { // missing end tag??
  10165. $endIx = strlen( $xml ) + 1;
  10166. return '';
  10167. }
  10168. $endIx = $pos + $strlen + 3;
  10169. return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $sx1 - 2 - $strlen ));
  10170. }
  10171. /**
  10172. * fetch next (unknown) XML tagname AND content
  10173. *
  10174. * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  10175. * @since 2.16.22 - 2013-06-20
  10176. * @param string $xml
  10177. * @param string $tagName
  10178. * @param int $endIx
  10179. * @return mixed
  10180. */
  10181. function XMLgetTagContent2( $xml, & $tagName, & $endIx ) {
  10182. $endIx = strlen( $xml ) + 1; // just in case.. .
  10183. $sx1 = 0;
  10184. while( FALSE !== substr( $xml, $sx1, 1 )) {
  10185. if( '<' == substr( $xml, $sx1, 1 )) {
  10186. if(( FALSE !== substr( $xml, ( $sx1 + 3 ), 1 )) && ( '<!--' == substr( $xml, $sx1, 4 ))) // skip comment
  10187. $sx1 += 1;
  10188. else
  10189. break; // tagname start here
  10190. }
  10191. else
  10192. $sx1 += 1;
  10193. }
  10194. $sx2 = $sx1;
  10195. while( FALSE !== substr( $xml, $sx2 )) {
  10196. if(( FALSE !== substr( $xml, ( $sx2 + 1 ), 1 )) && ( '/>' == substr( $xml, $sx2, 2 ))) { // empty tag
  10197. $tagName = trim( substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 )));
  10198. $endIx = $sx2 + 2;
  10199. return '';
  10200. }
  10201. if( '>' == substr( $xml, $sx2, 1 )) // tagname ends here
  10202. break;
  10203. $sx2 += 1;
  10204. }
  10205. $tagName = substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 ));
  10206. $endIx = $sx2 + 1;
  10207. if( FALSE === substr( $xml, $sx2, 1 )) {
  10208. return '';
  10209. }
  10210. $strlen = strlen( $tagName );
  10211. if(( 'duration' == $tagName ) &&
  10212. ( FALSE !== ( $pos1 = stripos( $xml, "<duration>", $sx1+1 ))) &&
  10213. ( FALSE !== ( $pos2 = stripos( $xml, "</duration>", $pos1+1 ))) &&
  10214. ( FALSE !== ( $pos3 = stripos( $xml, "</duration>", $pos2+1 ))) &&
  10215. ( $pos1 < $pos2 ) && ( $pos2 < $pos3 ))
  10216. $pos = $pos3;
  10217. elseif( FALSE === ( $pos = stripos( $xml, "</$tagName>", $sx2 )))
  10218. return '';
  10219. $endIx = $pos + $strlen + 3;
  10220. return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $strlen - 2 ));
  10221. }
  10222. /*********************************************************************************/
  10223. /* Additional functions to use with vtimezone components */
  10224. /*********************************************************************************/
  10225. /**
  10226. * For use with
  10227. * iCalcreator (kigkonsult.se/iCalcreator/index.php)
  10228. * copyright (c) 2011 Yitzchok Lavi
  10229. * icalcreator@onebigsystem.com
  10230. *
  10231. * This library is free software; you can redistribute it and/or
  10232. * modify it under the terms of the GNU Lesser General Public
  10233. * License as published by the Free Software Foundation; either
  10234. * version 2.1 of the License, or (at your option) any later version.
  10235. *
  10236. * This library is distributed in the hope that it will be useful,
  10237. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10238. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10239. * Lesser General Public License for more details.
  10240. *
  10241. * You should have received a copy of the GNU Lesser General Public
  10242. * License along with this library; if not, write to the Free Software
  10243. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  10244. */
  10245. /**
  10246. * Additional functions to use with vtimezone components
  10247. *
  10248. * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
  10249. *
  10250. * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
  10251. * adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
  10252. * @version 1.0.2 - 2011-02-24
  10253. *
  10254. */
  10255. /**
  10256. * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
  10257. * timezone, according to the VTIMEZONE information in the input array.
  10258. *
  10259. * $param array $timezonesarray, output from function getTimezonesAsDateArrays (below)
  10260. * $param string $tzid, time zone identifier
  10261. * $param mixed $timestamp, timestamp or a UTC datetime (in array format)
  10262. * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
  10263. *
  10264. */
  10265. function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
  10266. if( is_array( $timestamp )) {
  10267. //$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ###
  10268. $timestamp = gmmktime(
  10269. $timestamp['hour'],
  10270. $timestamp['min'],
  10271. $timestamp['sec'],
  10272. $timestamp['month'],
  10273. $timestamp['day'],
  10274. $timestamp['year']
  10275. ) ;
  10276. }
  10277. $tzoffset = array();
  10278. // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
  10279. $tzoffset['offsetHis'] = '+0000';
  10280. $tzoffset['offsetSec'] = 0;
  10281. $tzoffset['tzname'] = '?';
  10282. if( !isset( $timezonesarray[$tzid] ))
  10283. return $tzoffset;
  10284. $tzdatearray = $timezonesarray[$tzid];
  10285. if ( is_array($tzdatearray) ) {
  10286. sort($tzdatearray); // just in case
  10287. if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
  10288. // our date is before the first change
  10289. $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
  10290. $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
  10291. $tzoffset['tzname'] = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
  10292. } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
  10293. // our date is after the last change (we do this so our scan can stop at the last record but one)
  10294. $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
  10295. $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
  10296. $tzoffset['tzname'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
  10297. } else {
  10298. // our date somewhere in between
  10299. // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
  10300. // we don't include the last date in our loop as there isn't one after it to check
  10301. for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
  10302. if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
  10303. $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
  10304. $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
  10305. $tzoffset['tzname'] = $tzdatearray[$i]['tzafter']['tzname'] ;
  10306. break;
  10307. }
  10308. }
  10309. }
  10310. }
  10311. return $tzoffset;
  10312. }
  10313. /**
  10314. * Returns an array containing all the timezone data in the vcalendar object
  10315. *
  10316. * @param object $vcalendar, iCalcreator calendar instance
  10317. * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
  10318. * based on the timezone data in the vcalendar object
  10319. *
  10320. */
  10321. function getTimezonesAsDateArrays($vcalendar) {
  10322. $timezonedata = array();
  10323. while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
  10324. $tzid = $vtz->getProperty('tzid');
  10325. $alltzdates = array();
  10326. while ( $vtzc = $vtz->getComponent( 'standard' )) {
  10327. $newtzdates = expandTimezoneDates($vtzc);
  10328. $alltzdates = array_merge($alltzdates, $newtzdates);
  10329. }
  10330. while ( $vtzc = $vtz->getComponent( 'daylight' )) {
  10331. $newtzdates = expandTimezoneDates($vtzc);
  10332. $alltzdates = array_merge($alltzdates, $newtzdates);
  10333. }
  10334. sort($alltzdates);
  10335. $timezonedata[$tzid] = $alltzdates;
  10336. }
  10337. return $timezonedata;
  10338. }
  10339. /**
  10340. * Returns an array containing time zone data from vtimezone standard/daylight instances
  10341. *
  10342. * @param object $vtzc, an iCalcreator calendar standard/daylight instance
  10343. * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
  10344. *
  10345. */
  10346. function expandTimezoneDates($vtzc) {
  10347. $tzdates = array();
  10348. // prepare time zone "description" to attach to each change
  10349. $tzbefore = array();
  10350. $tzbefore['offsetHis'] = $vtzc->getProperty('tzoffsetfrom') ;
  10351. $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
  10352. if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
  10353. $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
  10354. $tzafter = array();
  10355. $tzafter['offsetHis'] = $vtzc->getProperty('tzoffsetto') ;
  10356. $tzafter['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
  10357. if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
  10358. $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
  10359. if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
  10360. $tzafter['tzname'] = $tzafter['offsetHis'];
  10361. // find out where to start from
  10362. $dtstart = $vtzc->getProperty('dtstart');
  10363. $dtstarttimestamp = mktime(
  10364. $dtstart['hour'],
  10365. $dtstart['min'],
  10366. $dtstart['sec'],
  10367. $dtstart['month'],
  10368. $dtstart['day'],
  10369. $dtstart['year']
  10370. ) ;
  10371. if( !isset( $dtstart['unparsedtext'] )) // ??
  10372. $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
  10373. if ( $dtstarttimestamp == 0 ) {
  10374. // it seems that the dtstart string may not have parsed correctly
  10375. // let's set a timestamp starting from 1902, using the time part of the original string
  10376. // so that the time will change at the right time of day
  10377. // at worst we'll get midnight again
  10378. $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
  10379. $dtstarttimestamp = strtotime("19020101",0);
  10380. $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
  10381. }
  10382. // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
  10383. $diff = -1 * $tzbefore['offsetSec'];
  10384. $dtstarttimestamp += $diff;
  10385. // add this (start) change to the array of changes
  10386. $tzdates[] = array(
  10387. 'timestamp' => $dtstarttimestamp,
  10388. 'tzbefore' => $tzbefore,
  10389. 'tzafter' => $tzafter
  10390. );
  10391. $datearray = getdate($dtstarttimestamp);
  10392. // save original array to use time parts, because strtotime (used below) apparently loses the time
  10393. $changetime = $datearray ;
  10394. // generate dates according to an RRULE line
  10395. $rrule = $vtzc->getProperty('rrule') ;
  10396. if ( is_array($rrule) ) {
  10397. if ( $rrule['FREQ'] == 'YEARLY' ) {
  10398. // calculate transition dates starting from DTSTART
  10399. $offsetchangetimestamp = $dtstarttimestamp;
  10400. // calculate transition dates until 10 years in the future
  10401. $stoptimestamp = strtotime("+10 year",time());
  10402. // if UNTIL is set, calculate until then (however far ahead)
  10403. if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
  10404. $stoptimestamp = mktime(
  10405. $rrule['UNTIL']['hour'],
  10406. $rrule['UNTIL']['min'],
  10407. $rrule['UNTIL']['sec'],
  10408. $rrule['UNTIL']['month'],
  10409. $rrule['UNTIL']['day'],
  10410. $rrule['UNTIL']['year']
  10411. ) ;
  10412. }
  10413. $count = 0 ;
  10414. $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
  10415. $daynames = array(
  10416. 'SU' => 'Sunday',
  10417. 'MO' => 'Monday',
  10418. 'TU' => 'Tuesday',
  10419. 'WE' => 'Wednesday',
  10420. 'TH' => 'Thursday',
  10421. 'FR' => 'Friday',
  10422. 'SA' => 'Saturday'
  10423. );
  10424. // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
  10425. while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
  10426. // break up the timestamp into its parts
  10427. $datearray = getdate($offsetchangetimestamp);
  10428. if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
  10429. // set the month
  10430. $datearray['mon'] = $rrule['BYMONTH'] ;
  10431. }
  10432. if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
  10433. // set specific day of month
  10434. $datearray['mday'] = $rrule['BYMONTHDAY'];
  10435. } elseif ( is_array($rrule['BYDAY']) ) {
  10436. // find the Xth WKDAY in the month
  10437. // the starting point for this process is the first of the month set above
  10438. $datearray['mday'] = 1 ;
  10439. // turn $datearray as it is now back into a timestamp
  10440. $offsetchangetimestamp = mktime(
  10441. $datearray['hours'],
  10442. $datearray['minutes'],
  10443. $datearray['seconds'],
  10444. $datearray['mon'],
  10445. $datearray['mday'],
  10446. $datearray['year']
  10447. );
  10448. if ($rrule['BYDAY'][0] > 0) {
  10449. // to find Xth WKDAY in month, we find last WKDAY in month before
  10450. // we do that by finding first WKDAY in this month and going back one week
  10451. // then we add X weeks (below)
  10452. $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
  10453. $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
  10454. } else {
  10455. // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
  10456. // we do that by going forward one month and going to WKDAY there
  10457. // then we subtract X weeks (below)
  10458. $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
  10459. $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
  10460. }
  10461. // now move forward or back the appropriate number of weeks, into the month we want
  10462. $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
  10463. $datearray = getdate($offsetchangetimestamp);
  10464. }
  10465. // convert the date parts back into a timestamp, setting the time parts according to the
  10466. // original time data which we stored
  10467. $offsetchangetimestamp = mktime(
  10468. $changetime['hours'],
  10469. $changetime['minutes'],
  10470. $changetime['seconds'] + $diff,
  10471. $datearray['mon'],
  10472. $datearray['mday'],
  10473. $datearray['year']
  10474. );
  10475. // add this change to the array of changes
  10476. $tzdates[] = array(
  10477. 'timestamp' => $offsetchangetimestamp,
  10478. 'tzbefore' => $tzbefore,
  10479. 'tzafter' => $tzafter
  10480. );
  10481. // update counters (timestamp and count)
  10482. $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
  10483. $count += 1 ;
  10484. }
  10485. }
  10486. }
  10487. // generate dates according to RDATE lines
  10488. while ($rdates = $vtzc->getProperty('rdate')) {
  10489. if ( is_array($rdates) ) {
  10490. foreach ( $rdates as $rdate ) {
  10491. // convert the explicit change date to a timestamp
  10492. $offsetchangetimestamp = mktime(
  10493. $rdate['hour'],
  10494. $rdate['min'],
  10495. $rdate['sec'] + $diff,
  10496. $rdate['month'],
  10497. $rdate['day'],
  10498. $rdate['year']
  10499. ) ;
  10500. // add this change to the array of changes
  10501. $tzdates[] = array(
  10502. 'timestamp' => $offsetchangetimestamp,
  10503. 'tzbefore' => $tzbefore,
  10504. 'tzafter' => $tzafter
  10505. );
  10506. }
  10507. }
  10508. }
  10509. return $tzdates;
  10510. }
  10511. ?>