2 /*********************************************************************************/
5 * copyright (c) 2007-2011 Kjell-Inge Gustafsson kigkonsult
6 * kigkonsult.se/iCalcreator/index.php
10 * This file is a PHP implementation of RFC 2445.
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 /*********************************************************************************/
27 /*********************************************************************************/
29 /*********************************************************************************/
30 /* your local language code */
31 // define( 'ICAL_LANG', 'sv' );
34 $langstr = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
35 $pos = strpos( $langstr, ';' );
37 $langstr = substr( $langstr, 0, $pos );
38 $pos = strpos( $langstr, ',' );
40 $pos = strpos( $langstr, ',' );
41 $langstr = substr( $langstr, 0, $pos );
43 define( 'ICAL_LANG', $langstr );
46 /*********************************************************************************/
47 /* only for phpversion 5.1 and later, */
48 /* date management, default timezone setting */
49 /* since 2.6.36 - 2010-12-31 */
50 if( substr( phpversion(), 0, 3 ) >= '5.1' )
51 // && ( 'UTC' == date_default_timezone_get()))
52 date_default_timezone_set( 'Asia/Hong_Kong' );
53 /*********************************************************************************/
54 /* since 2.6.22 - 2010-09-25, do NOT remove!! */
55 require_once 'UtilityFunctions.php';
56 require_once 'calendarComponent.php';
57 require_once 'valarm.php';
58 require_once 'vevent.php';
59 require_once 'vfreebusy.php';
60 require_once 'vjournal.php';
61 require_once 'vtimezone.php';
62 require_once 'vtodo.php';
66 /*********************************************************************************/
67 /* version, do NOT remove!! */
68 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.10.15' );
69 /*********************************************************************************/
70 /*********************************************************************************/
74 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
75 * @since 2.9.6 - 2011-05-14
77 class iCal_Vcalendar {
78 // calendar property variables
84 // container for calendar components
86 // component config variables
97 // component internal variables
98 var $attributeDelimiter;
100 // component xCal declaration container
103 * constructor for calendar object
105 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
106 * @since 2.9.6 - 2011-05-14
107 * @param array $config
110 function iCal_Vcalendar ( $config = array()) {
111 $this->_makeVersion();
112 $this->calscale = null;
113 $this->method = null;
114 $this->_makeUnique_id();
115 $this->prodid = null;
116 $this->xprop = array();
117 $this->language = null;
118 $this->directory = null;
119 $this->filename = null;
123 * language = <Text identifying a language, as defined in [RFC 1766]>
125 if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
126 $config['language'] = ICAL_LANG;
127 if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE;
128 if( !isset( $config['nl'] )) $config['nl'] = "\r\n";
129 if( !isset( $config['format'] )) $config['format'] = 'iCal';
130 if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR;
131 $this->setConfig( $config );
133 $this->xcaldecl = array();
134 $this->components = array();
136 /*********************************************************************************/
138 * Property Name: CALSCALE
141 * creates formatted output for calendar property calscale
143 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
144 * @since 2.4.8 - 2008-10-21
147 function createCalscale() {
148 if( empty( $this->calscale )) return FALSE;
149 switch( $this->format ) {
151 return ' calscale="'.$this->calscale.'"'.$this->nl;
154 return 'CALSCALE:'.$this->calscale.$this->nl;
159 * set calendar property calscale
161 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
162 * @since 2.4.8 - 2008-10-21
163 * @param string $value
166 function setCalscale( $value ) {
167 if( empty( $value )) return FALSE;
168 $this->calscale = $value;
170 /*********************************************************************************/
172 * Property Name: METHOD
175 * creates formatted output for calendar property method
177 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
178 * @since 0.9.7 - 2006-11-20
181 function createMethod() {
182 if( empty( $this->method )) return FALSE;
183 switch( $this->format ) {
185 return ' method="'.$this->method.'"'.$this->nl;
188 return 'METHOD:'.$this->method.$this->nl;
193 * set calendar property method
195 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
196 * @since 2.4.8 - 2008-20-23
197 * @param string $value
200 function setMethod( $value ) {
201 if( empty( $value )) return FALSE;
202 $this->method = $value;
205 /*********************************************************************************/
207 * Property Name: PRODID
209 * The identifier is RECOMMENDED to be the identical syntax to the
210 * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
211 * domain name or a domain literal IP address of the host on which.. .
214 * creates formatted output for calendar property prodid
216 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
217 * @since 0.9.7 - 2006-11-20
220 function createProdid() {
221 if( !isset( $this->prodid ))
222 $this->_makeProdid();
223 switch( $this->format ) {
225 return ' prodid="'.$this->prodid.'"'.$this->nl;
228 return 'PRODID:'.$this->prodid.$this->nl;
233 * make default value for calendar prodid
235 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
236 * @since 2.6.8 - 2009-12-30
239 function _makeProdid() {
240 $this->prodid = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
243 * Conformance: The property MUST be specified once in an iCalendar object.
244 * Description: The vendor of the implementation SHOULD assure that this
245 * is a globally unique identifier; using some technique such as an FPI
246 * value, as defined in [ISO 9070].
249 * make default unique_id for calendar prodid
251 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
252 * @since 0.3.0 - 2006-08-10
255 function _makeUnique_id() {
256 $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
258 /*********************************************************************************/
260 * Property Name: VERSION
262 * Description: A value of "2.0" corresponds to this memo.
265 * creates formatted output for calendar property version
268 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
269 * @since 0.9.7 - 2006-11-20
272 function createVersion() {
273 if( empty( $this->version ))
274 $this->_makeVersion();
275 switch( $this->format ) {
277 return ' version="'.$this->version.'"'.$this->nl;
280 return 'VERSION:'.$this->version.$this->nl;
285 * set default calendar version
287 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
288 * @since 0.3.0 - 2006-08-10
291 function _makeVersion() {
292 $this->version = '2.0';
295 * set calendar version
297 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
298 * @since 2.4.8 - 2008-10-23
299 * @param string $value
302 function setVersion( $value ) {
303 if( empty( $value )) return FALSE;
304 $this->version = $value;
307 /*********************************************************************************/
309 * Property Name: x-prop
312 * creates formatted output for calendar property x-prop, iCal format only
314 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
315 * @since 2.9.3 - 2011-05-14
318 function createXprop() {
319 if( 'xcal' == $this->format )
321 if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
323 $toolbox = new calendarComponent();
324 $toolbox->setConfig( $this->getConfig());
325 foreach( $this->xprop as $label => $xpropPart ) {
326 if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
327 $output .= $toolbox->_createElement( $label );
330 $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
331 if( is_array( $xpropPart['value'] )) {
332 foreach( $xpropPart['value'] as $pix => $theXpart )
333 $xpropPart['value'][$pix] = $toolbox->_strrep( $theXpart );
334 $xpropPart['value'] = implode( ',', $xpropPart['value'] );
337 $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] );
338 $output .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
343 * set calendar property x-prop
345 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
346 * @since 2.9.3 - 2011-05-14
347 * @param string $label
348 * @param string $value
349 * @param array $params optional
352 function setXprop( $label, $value, $params=FALSE ) {
353 if( empty( $label )) return FALSE;
354 if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
355 $xprop = array( 'value' => $value );
356 $xprop['params'] = iCal_UtilityFunctions::_setParams( $params );
357 if( !is_array( $this->xprop )) $this->xprop = array();
358 $this->xprop[strtoupper( $label )] = $xprop;
361 /*********************************************************************************/
363 * delete calendar property value
365 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
366 * @since 2.8.8 - 2011-03-15
367 * @param mixed $propName, bool FALSE => X-property
368 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
369 * @return bool, if successfull delete
371 function deleteProperty( $propName=FALSE, $propix=FALSE ) {
372 $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
374 $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
375 $this->propdelix[$propName] = --$propix;
377 switch( $propName ) {
379 if( isset( $this->calscale )) {
380 $this->calscale = null;
385 if( isset( $this->method )) {
386 $this->method = null;
392 if( $propName != 'X-PROP' ) {
393 if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
394 foreach( $this->xprop as $k => $a ) {
395 if(( $k != $propName ) && !empty( $a ))
400 if( count( $this->xprop ) <= $propix ) return FALSE;
402 foreach( $this->xprop as $xpropkey => $xpropvalue ) {
403 if( $propix != $xpropno )
404 $reduced[$xpropkey] = $xpropvalue;
408 $this->xprop = $reduced;
409 if( empty( $this->xprop )) {
410 unset( $this->propdelix[$propName] );
418 * get calendar property value/params
420 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
421 * @since 2.8.8 - 2011-04-16
422 * @param string $propName, optional
423 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
424 * @param bool $inclParam=FALSE
427 function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
428 $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
429 if( 'X-PROP' == $propName ) {
431 $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
432 $this->propix[$propName] = --$propix;
434 switch( $propName ) {
444 case 'RECURRENCE-ID-UID':
448 foreach ( $this->components as $cix => $component) {
449 if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
451 if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
452 $component->_getProperties( $propName, $output );
455 elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
456 if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
457 $content = $component->getProperty( 'UID' );
459 elseif( FALSE === ( $content = $component->getProperty( $propName )))
461 if( FALSE === $content )
463 elseif( is_array( $content )) {
464 if( isset( $content['year'] )) {
465 $key = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
466 if( !isset( $output[$key] ))
472 foreach( $content as $partValue => $partCount ) {
473 if( !isset( $output[$partValue] ))
474 $output[$partValue] = $partCount;
476 $output[$partValue] += $partCount;
479 } // end elseif( is_array( $content )) {
480 elseif( !isset( $output[$content] ))
481 $output[$content] = 1;
483 $output[$content] += 1;
484 } // end foreach ( $this->components as $cix => $component)
485 if( !empty( $output ))
491 return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
494 return ( !empty( $this->method )) ? $this->method : FALSE;
497 if( empty( $this->prodid ))
498 $this->_makeProdid();
499 return $this->prodid;
502 return ( !empty( $this->version )) ? $this->version : FALSE;
505 if( $propName != 'X-PROP' ) {
506 if( !isset( $this->xprop[$propName] )) return FALSE;
507 return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
508 : array( $propName, $this->xprop[$propName]['value'] );
511 if( empty( $this->xprop )) return FALSE;
513 foreach( $this->xprop as $xpropkey => $xpropvalue ) {
514 if( $propix == $xpropno )
515 return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
516 : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
520 unset( $this->propix[$propName] );
521 return FALSE; // not found ??
527 * general iCal_Vcalendar property setting
529 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
530 * @since 2.2.13 - 2007-11-04
531 * @param mixed $args variable number of function arguments,
532 * first argument is ALWAYS component name,
533 * second ALWAYS component value!
536 function setProperty () {
537 $numargs = func_num_args();
540 $arglist = func_get_args();
541 $arglist[0] = strtoupper( $arglist[0] );
542 switch( $arglist[0] ) {
544 return $this->setCalscale( $arglist[1] );
546 return $this->setMethod( $arglist[1] );
548 return $this->setVersion( $arglist[1] );
550 if( !isset( $arglist[1] )) $arglist[1] = null;
551 if( !isset( $arglist[2] )) $arglist[2] = null;
552 return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
556 /*********************************************************************************/
558 * get iCal_Vcalendar config values or * calendar components
560 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
561 * @since 2.9.6 - 2011-05-14
562 * @param mixed $config
565 function getConfig( $config = FALSE ) {
568 $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' );
569 $return['DELIMITER'] = $this->getConfig( 'DELIMITER' );
570 $return['DIRECTORY'] = $this->getConfig( 'DIRECTORY' );
571 $return['FILENAME'] = $this->getConfig( 'FILENAME' );
572 $return['DIRFILE'] = $this->getConfig( 'DIRFILE' );
573 $return['FILESIZE'] = $this->getConfig( 'FILESIZE' );
574 $return['FORMAT'] = $this->getConfig( 'FORMAT' );
575 if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' )))
576 $return['LANGUAGE'] = $lang;
577 $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
578 $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' );
579 if( FALSE !== ( $url = $this->getConfig( 'URL' )))
580 $return['URL'] = $url;
581 $return['TZID'] = $this->getConfig( 'TZID' );
584 switch( strtoupper( $config )) {
586 return $this->allowEmpty;
589 unset( $this->compix );
591 foreach( $this->components as $cix => $component ) {
592 if( empty( $component )) continue;
593 $info[$cix]['ordno'] = $cix + 1;
594 $info[$cix]['type'] = $component->objName;
595 $info[$cix]['uid'] = $component->getProperty( 'uid' );
596 $info[$cix]['props'] = $component->getConfig( 'propinfo' );
597 $info[$cix]['sub'] = $component->getConfig( 'compsinfo' );
602 return $this->delimiter;
605 if( empty( $this->directory ))
606 $this->directory = '.';
607 return $this->directory;
610 return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
613 return array( $this->getConfig( 'directory' )
614 , $this->getConfig( 'filename' )
615 , $this->getConfig( 'filesize' ));
618 if( empty( $this->filename )) {
619 if( 'xcal' == $this->format )
620 $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
622 $this->filename = date( 'YmdHis' ).'.ics';
624 return $this->filename;
628 if( empty( $this->url )) {
629 $dirfile = $this->getConfig( 'dirfile' );
630 if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
637 return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
640 /* get language for calendar component as defined in [RFC 1766] */
641 return $this->language;
651 return $this->unique_id;
654 if( !empty( $this->url ))
662 * general iCal_Vcalendar config setting
664 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
665 * @since 2.10.10 - 2011-09-19
666 * @param mixed $config
667 * @param string $value
670 function setConfig( $config, $value = FALSE) {
671 if( is_array( $config )) {
672 $ak = array_keys( $config );
673 foreach( $ak as $k ) {
674 if( 'DIRECTORY' == strtoupper( $k )) {
675 if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
677 unset( $config[$k] );
681 foreach( $config as $cKey => $cValue ) {
682 if( FALSE === $this->setConfig( $cKey, $cValue ))
688 switch( strtoupper( $config )) {
690 $this->allowEmpty = $value;
691 $subcfg = array( 'ALLOWEMPTY' => $value );
695 $this->delimiter = $value;
699 $value = trim( $value );
700 $del = $this->getConfig('delimiter');
701 if( $del == substr( $value, ( 0 - strlen( $del ))))
702 $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
703 if( is_dir( $value )) {
704 /* local directory */
706 $this->directory = $value;
714 $value = trim( $value );
715 if( !empty( $this->url )) {
716 /* remote directory+file -> URL */
717 $this->filename = $value;
720 $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
721 if( file_exists( $dirfile )) {
722 /* local file exists */
723 if( is_readable( $dirfile ) || is_writable( $dirfile )) {
725 $this->filename = $value;
731 elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
732 /* read- or writable directory */
733 $this->filename = $value;
740 $value = trim( strtolower( $value ));
741 if( 'xcal' == $value ) {
742 $this->format = 'xcal';
743 $this->attributeDelimiter = $this->nl;
744 $this->valueInit = null;
747 $this->format = null;
748 $this->attributeDelimiter = ';';
749 $this->valueInit = ':';
751 $subcfg = array( 'FORMAT' => $value );
755 // set language for calendar component as defined in [RFC 1766]
756 $value = trim( $value );
757 $this->language = $value;
758 $subcfg = array( 'LANGUAGE' => $value );
764 $subcfg = array( 'NL' => $value );
768 $this->dtzid = $value;
769 $subcfg = array( 'TZID' => $value );
773 $value = trim( $value );
774 $this->unique_id = $value;
775 $subcfg = array( 'UNIQUE_ID' => $value );
779 /* remote file - URL */
780 $value = trim( $value );
781 $value = str_replace( 'HTTP://', 'http://', $value );
782 $value = str_replace( 'WEBCAL://', 'http://', $value );
783 $value = str_replace( 'webcal://', 'http://', $value );
785 $this->directory = null;
786 $parts = pathinfo( $value );
787 return $this->setConfig( 'filename', $parts['basename'] );
789 default: // any unvalid config key.. .
792 if( !$res ) return FALSE;
793 if( isset( $subcfg ) && !empty( $this->components )) {
794 foreach( $subcfg as $cfgkey => $cfgvalue ) {
795 foreach( $this->components as $cix => $component ) {
796 $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
799 $this->components[$cix] = $component->copy(); // PHP4 compliant
805 /*********************************************************************************/
807 * add calendar component to container
809 * alias to setComponent
811 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
812 * @since 1.x.x - 2007-04-24
813 * @param object $component calendar component
816 function addComponent( $component ) {
817 $this->setComponent( $component );
820 * delete calendar component from container
822 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
823 * @since 2.8.8 - 2011-03-15
824 * @param mixed $arg1 ordno / component type / component uid
825 * @param mixed $arg2 optional, ordno if arg1 = component type
828 function deleteComponent( $arg1, $arg2=FALSE ) {
829 $argType = $index = null;
830 if ( ctype_digit( (string) $arg1 )) {
832 $index = (int) $arg1 - 1;
834 elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
835 $argType = strtolower( $arg1 );
836 $index = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
839 foreach ( $this->components as $cix => $component) {
840 if( empty( $component )) continue;
841 if(( 'INDEX' == $argType ) && ( $index == $cix )) {
842 unset( $this->components[$cix] );
845 elseif( $argType == $component->objName ) {
846 if( $index == $cix1dC ) {
847 unset( $this->components[$cix] );
852 elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
853 unset( $this->components[$cix] );
860 * get calendar component from container
862 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
863 * @since 2.9.1 - 2011-04-16
864 * @param mixed $arg1 optional, ordno/component type/ component uid
865 * @param mixed $arg2 optional, ordno if arg1 = component type
868 function getComponent( $arg1=FALSE, $arg2=FALSE ) {
869 $index = $argType = null;
870 if ( !$arg1 ) { // first or next in component chain
872 $index = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
874 elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
876 $index = (int) $arg1;
877 unset( $this->compix );
879 elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
880 $arg2 = implode( '-', array_keys( $arg1 ));
881 $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
882 $dateProps = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
883 $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' );
885 elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
886 unset( $this->compix['INDEX'] );
887 $argType = strtolower( $arg1 );
889 $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
890 elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
891 $index = (int) $arg2;
893 elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
895 $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
896 elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
897 $index = (int) $arg2;
901 $ckeys = array_keys( $this->components );
902 if( !empty( $index) && ( $index > end( $ckeys )))
905 foreach ( $this->components as $cix => $component) {
906 if( empty( $component )) continue;
907 if(( 'INDEX' == $argType ) && ( $index == $cix ))
908 return $component->copy();
909 elseif( $argType == $component->objName ) {
910 if( $index == $cix1gC )
911 return $component->copy();
914 elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
916 foreach( $arg1 as $pName => $pValue ) {
917 $pName = strtoupper( $pName );
918 if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
920 if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur
921 $propValues = array();
922 $component->_getProperties( $pName, $propValues );
923 $propValues = array_keys( $propValues );
924 $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
926 } // end if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur
927 if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency
928 $hit = FALSE; // missing property
931 if( 'SUMMARY' == $pName ) { // exists within (any case)
932 $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE;
935 if( in_array( strtoupper( $pName ), $dateProps )) {
936 $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
937 if( 8 < strlen( $pValue )) {
938 if( isset( $value['hour'] )) {
939 if( 'T' == substr( $pValue, 8, 1 ))
940 $pValue = str_replace( 'T', '', $pValue );
941 $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
944 $pValue = substr( $pValue, 0, 8 );
946 $hit = ( $pValue == $valuedate ) ? TRUE : FALSE;
949 elseif( !is_array( $value ))
950 $value = array( $value );
951 foreach( $value as $part ) {
952 $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
953 foreach( $part as $subPart ) {
954 if( $pValue == $subPart ) {
960 $hit = FALSE; // no hit in property
961 } // end foreach( $arg1 as $pName => $pValue )
963 if( $index == $cix1gC )
964 return $component->copy();
967 } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
968 elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
969 if( $index == $cix1gC )
970 return $component->copy();
973 } // end foreach ( $this->components.. .
975 unset( $this->compix );
979 * create new calendar component, already included within calendar
981 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
982 * @since 2.6.33 - 2011-01-03
983 * @param string $compType component type
984 * @return object (reference)
986 function & newComponent( $compType ) {
987 $config = $this->getConfig();
988 $keys = array_keys( $this->components );
989 $ix = end( $keys) + 1;
990 switch( strtoupper( $compType )) {
993 $this->components[$ix] = new vevent( $config );
997 $this->components[$ix] = new vtodo( $config );
1001 $this->components[$ix] = new vjournal( $config );
1005 $this->components[$ix] = new vfreebusy( $config );
1009 array_unshift( $this->components, new vtimezone( $config ));
1015 return $this->components[$ix];
1018 * select components from calendar on date or selectOption basis
1020 * Ensure DTSTART is set for every component.
1021 * No date controls occurs.
1023 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1024 * @since 2.10.13 - 2011-09-23
1025 * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions
1026 * @param int $startM optional, start Month, default current Month
1027 * @param int $startD optional, start Day, default current Day
1028 * @param int $endY optional, end Year, default $startY
1029 * @param int $endY optional, end Month, default $startM
1030 * @param int $endY optional, end Day, default $startD
1031 * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s)
1032 * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][]
1033 * TRUE => output : array[] (ignores split)
1034 * @param bool $any optional, TRUE (default) - select component that take place within period
1035 * FALSE - only components that starts within period
1036 * @param bool $split optional, TRUE (default) - one component copy every day it take place during the
1037 * period (implies flat=FALSE)
1038 * FALSE - one occurance of component only in output array
1039 * @return array or FALSE
1041 function selectComponents(
1052 /* check if empty calendar */
1053 if( 0 >= count( $this->components )) {
1057 if( is_array( $startY )) {
1058 return $this->selectComponents2( $startY );
1061 /* check default dates */
1062 if( !$startY ) $startY = date( 'Y' );
1063 if( !$startM ) $startM = date( 'm' );
1064 if( !$startD ) $startD = date( 'd' );
1065 $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
1066 if( !$endY ) $endY = $startY;
1067 if( !$endM ) $endM = $startM;
1068 if( !$endD ) $endD = $startD;
1069 $endDate = mktime( 23, 59, 59, $endM, $endD, $endY );
1073 //echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
1074 /* check component types */
1075 $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1076 if( is_array( $cType )) {
1077 foreach( $cType as $cix => $theType ) {
1078 $cType[$cix] = $theType = strtolower( $theType );
1079 if( !in_array( $theType, $validTypes )) {
1080 $cType[$cix] = 'vevent';
1083 $cType = array_unique( $cType );
1085 } else if( !empty( $cType )) {
1086 $cType = strtolower( $cType );
1088 if( !in_array( $cType, $validTypes )) {
1089 $cType = array( 'vevent' );
1091 $cType = array( $cType );
1095 $cType = $validTypes;
1097 if( 0 >= count( $cType )) {
1098 $cType = $validTypes;
1101 if(( TRUE === $flat ) && ( TRUE === $split )) {// invalid combination
1105 // end validate and reset arguments..
1108 /* iterate components */
1110 foreach ( $this->components as $cix => $component ) {
1111 //print_r($component);
1112 if( empty( $component )) {
1117 /* deselect unvalid type components */
1118 if( !in_array( $component->objName, $cType )) {
1122 $start = $component->getProperty( 'dtstart' );
1123 /* select due when dtstart is missing */
1124 if( empty( $start ) &&
1125 ( $component->objName == 'vtodo' ) &&
1126 ( FALSE === ( $start = $component->getProperty( 'due' )))) {
1130 $dtendExist = $dueExist = $durationExist = $endAllDayEvent = FALSE;
1131 unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up
1133 $startWdate = iCal_UtilityFunctions::_date2timestamp( $start );
1134 $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1136 /* get end date from dtend/due/duration properties */
1137 $end = $component->getProperty( 'dtend' );
1138 if( !empty( $end )) {
1140 $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1142 if( empty( $end ) && ( $component->objName == 'vtodo' )) {
1143 $end = $component->getProperty( 'due' );
1144 if( !empty( $end )) {
1146 $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1149 if( !empty( $end ) && !isset( $end['hour'] )) {
1150 /* a DTEND without time part regards an event that ends the day before,
1151 for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
1152 $endAllDayEvent = TRUE;
1153 $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
1154 $end['year'] = date( 'Y', $endWdate );
1155 $end['month'] = date( 'm', $endWdate );
1156 $end['day'] = date( 'd', $endWdate );
1158 $end['min'] = $end['sec'] = 59;
1160 if( empty( $end )) {
1161 $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
1162 if( !empty( $end )) {
1163 $durationExist = TRUE;
1166 $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1167 // if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1170 if( empty( $end )) { // assume one day duration if missing end date
1172 'year' => $start['year'],
1173 'month' => $start['month'],
1174 'day' => $start['day'],
1180 // if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1181 $endWdate = iCal_UtilityFunctions::_date2timestamp( $end );
1182 if( $endWdate < $startWdate ) { // MUST be after start date!!
1184 'year' => $start['year'],
1185 'month' => $start['month'],
1186 'day' => $start['day'],
1191 $endWdate = iCal_UtilityFunctions::_date2timestamp( $end );
1196 $rdurWsecs = $endWdate - $startWdate; // compute event (component) duration in seconds
1197 /* make a list of optional exclude dates for component occurence from exrule and exdate */
1198 $exdatelist = array();
1199 $workstart = iCal_UtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
1200 $workend = iCal_UtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
1202 while( FALSE !== ( $exrule = $component->getProperty( 'exrule' ))) { // check exrule
1203 iCal_UtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
1206 while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) { // check exdate
1207 foreach( $exdate as $theExdate ) {
1208 $exWdate = iCal_UtilityFunctions::_date2timestamp( $theExdate );
1209 $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate ) ); // on a day-basis !!!
1210 if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate )) {
1211 $exdatelist[$exWdate] = TRUE;
1213 } // end - foreach( $exdate as $theExdate )
1214 } // end - check exdate
1216 //var_dump(array($startWdate ,$startDate , $startWdate , $endDate ));exit;
1217 /* select only components with startdate within period */
1218 if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) {
1219 /* add the selected component (WITHIN valid dates) to output array */
1221 $result[$component->getProperty( 'UID' )] = $component->copy(); // copy original to output;
1223 } elseif( $split ) { // split the original component
1224 if( $endWdate > $endDate ) {
1225 $endWdate = $endDate; // use period end date
1228 $rstart = $startWdate;
1229 if( $rstart < $startDate ){
1230 $rstart = $startDate; // use period start date
1232 $startYMD = date( 'Ymd', $rstart );
1233 $endYMD = date( 'Ymd', $endWdate );
1234 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1236 while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate
1237 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1238 if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
1239 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1242 if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
1243 $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
1245 $datestring = date( $startDateFormat, $rstart );
1246 if( isset( $start['tz'] ))
1247 $datestring .= ' '.$start['tz'];
1248 // echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component->setProperty( 'X-CNT', $tcnt ); // test ###
1249 $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
1250 if( $dtendExist || $dueExist || $durationExist ) {
1251 if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
1252 $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1254 $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1255 $datestring = date( $endDateFormat, $tend );
1256 if( isset( $end['tz'] ))
1257 $datestring .= ' '.$end['tz'];
1258 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1259 $component->setProperty( $propName, $datestring );
1260 } // end if( $dtendExist || $dueExist || $durationExist )
1261 $wd = getdate( $rstart );
1262 if (!isset($result[$wd['year']][$wd['mon']][$wd['mday']][$component->getProperty( 'UID' )] )) {
1263 $result[$wd['year']][$wd['mon']][$wd['mday']][$component->getProperty( 'UID' )] = $component->copy(); // copy to output
1265 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1266 } // end while( $rstart <= $endWdate )
1268 //print_R($result);exit;
1270 } // end if( $split ) - else use component date
1271 } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
1272 /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
1273 if( TRUE === $any ) {
1274 /* make a list of optional repeating dates for component occurence, rrule, rdate */
1275 $recurlist = array();
1276 while( FALSE !== ( $rrule = $component->getProperty( 'rrule' ))) // check rrule
1277 iCal_UtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
1278 foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
1279 $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
1280 while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) { // check rdate
1281 foreach( $rdate as $theRdate ) {
1282 if( is_array( $theRdate ) && ( 2 == count( $theRdate )) && // all days within PERIOD
1283 array_key_exists( '0', $theRdate ) && array_key_exists( '1', $theRdate )) {
1284 $rstart = iCal_UtilityFunctions::_date2timestamp( $theRdate[0] );
1285 if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
1287 if( isset( $theRdate[1]['year'] )) // date-date period
1288 $rend = iCal_UtilityFunctions::_date2timestamp( $theRdate[1] );
1289 else { // date-duration period
1290 $rend = iCal_UtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
1291 $rend = iCal_UtilityFunctions::_date2timestamp( $rend );
1293 while( $rstart < $rend ) {
1294 $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
1295 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1298 else { // single date
1299 $theRdate = iCal_UtilityFunctions::_date2timestamp( $theRdate );
1300 if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
1301 $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
1304 } // end - check rdate
1305 if( 0 < count( $recurlist )) {
1306 ksort( $recurlist );
1308 foreach( $recurlist as $recurkey => $durvalue ) {
1309 // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCal_UtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
1310 if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
1312 $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1313 if( isset( $exdatelist[$checkDate] )) // check excluded dates
1315 if( $startWdate >= $recurkey ) // exclude component start date
1317 $component2 = $component->copy();
1318 $rstart = $recurkey;
1319 $rend = $recurkey + $durvalue;
1320 /* add repeating components within valid dates to output array, only start date set */
1322 $datestring = date( $startDateFormat, $recurkey );
1323 if( isset( $start['tz'] ))
1324 $datestring .= ' '.$start['tz'];
1325 // echo "X-CURRENT-DTSTART 0 =$datestring tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1326 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1327 if( $dtendExist || $dueExist || $durationExist ) {
1328 $datestring = date( $endDateFormat, $recurkey + $durvalue ); // fixa korrekt sluttid
1329 if( isset( $end['tz'] ))
1330 $datestring .= ' '.$end['tz'];
1331 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1332 $component2->setProperty( $propName, $datestring );
1333 } // end if( $dtendExist || $dueExist || $durationExist )
1334 $component2->setProperty( 'X-RECURRENCE', ++$xRecurrence );
1335 $result[$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output
1337 /* add repeating components within valid dates to output array, one each day */
1339 if( $rend > $endDate )
1341 $startYMD = date( 'Ymd', $rstart );
1342 $endYMD = date( 'Ymd', $rend );
1343 // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
1344 while( $rstart <= $rend ) { // iterate.. .
1345 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1346 if( isset( $exdatelist[$checkDate] )) // exclude any recurrence START date, found in exdatelist
1348 // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
1349 if( $rstart >= $startDate ) { // date after dtstart
1350 if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
1351 $datestring = date( $startDateFormat, $checkDate );
1353 $datestring = date( $startDateFormat, $rstart );
1354 if( isset( $start['tz'] ))
1355 $datestring .= ' '.$start['tz'];
1356 //echo "X-CURRENT-DTSTART 1 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1357 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1358 if( $dtendExist || $dueExist || $durationExist ) {
1359 if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
1360 $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1362 $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1363 $datestring = date( $endDateFormat, $tend );
1364 if( isset( $end['tz'] ))
1365 $datestring .= ' '.$end['tz'];
1366 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1367 $component2->setProperty( $propName, $datestring );
1368 } // end if( $dtendExist || $dueExist || $durationExist )
1369 $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1370 $wd = getdate( $rstart );
1371 if (!isset($result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] )) {
1372 $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output
1375 } // end if( $checkDate > $startYMD ) { // date after dtstart
1376 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1377 } // end while( $rstart <= $rend )
1379 } // end elseif( $split )
1380 elseif( $rstart >= $startDate ) { // date within period //* flat=FALSE && split=FALSE *//
1381 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1382 if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
1384 $datestring = date( $startDateFormat, $rstart );
1385 if( isset( $start['tz'] ))
1386 $datestring .= ' '.$start['tz'];
1387 //echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1388 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1389 if( $dtendExist || $dueExist || $durationExist ) {
1390 $rstart += $rdurWsecs;
1391 if( date( 'Ymd', $rstart ) < date( 'Ymd', $endWdate ))
1392 $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1394 $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1395 $datestring = date( $endDateFormat, $tend );
1396 if( isset( $end['tz'] ))
1397 $datestring .= ' '.$end['tz'];
1398 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1399 $component2->setProperty( $propName, $datestring );
1400 } // end if( $dtendExist || $dueExist || $durationExist )
1401 $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1402 $wd = getdate( $rstart );
1403 if (!isset($result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] )) {
1404 $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output
1406 } // end if( !isset( $exdatelist[$checkDate] ))
1407 } // end elseif( $rstart >= $startDate )
1408 } // end foreach( $recurlist as $recurkey => $durvalue )
1409 } // end if( 0 < count( $recurlist ))
1410 /* deselect components with startdate/enddate not within period */
1411 if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
1413 } // end if( TRUE === $any )
1414 } // end foreach ( $this->components as $cix => $component )
1415 if( 0 >= count( $result )) {
1420 foreach( $result as $y => $yeararr ) {
1421 foreach( $yeararr as $m => $montharr ) {
1422 foreach( $montharr as $d => $dayarr )
1423 $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
1424 ksort( $result[$y][$m] );
1426 ksort( $result[$y] );
1429 } // end elseif( !$flat )
1434 * select components from calendar on based on Categories, Location, Resources and/or Summary
1436 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1437 * @since 2.8.8 - 2011-05-03
1438 * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
1441 function selectComponents2( $selectOptions ) {
1443 $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' );
1444 foreach( $this->components as $cix => $component3 ) {
1445 if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
1447 $uid = $component3->getProperty( 'UID' );
1448 foreach( $selectOptions as $propName => $pvalue ) {
1449 $propName = strtoupper( $propName );
1450 if( !in_array( $propName, $allowedProperties ))
1452 if( !is_array( $pvalue ))
1453 $pvalue = array( $pvalue );
1454 if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
1455 $output[] = $component3->copy();
1458 elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
1459 $propValues = array();
1460 $component3->_getProperties( $propName, $propValues );
1461 $propValues = array_keys( $propValues );
1462 foreach( $pvalue as $theValue ) {
1463 if( in_array( $theValue, $propValues ) && !isset( $output[$uid] )) {
1464 $output[$uid] = $component3->copy();
1469 } // end elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName ))
1470 elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence
1472 if( is_array( $d )) {
1473 foreach( $d as $part ) {
1474 if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
1475 $output[$uid] = $component3->copy();
1478 elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
1479 foreach( $pvalue as $pval ) {
1480 if( FALSE !== stripos( $d, $pval )) {
1481 $output[$uid] = $component3->copy();
1486 elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
1487 $output[$uid] = $component3->copy();
1488 } // end foreach( $selectOptions as $propName => $pvalue ) {
1489 } // end foreach( $this->components as $cix => $component3 ) {
1490 if( !empty( $output )) {
1492 $output = array_values( $output );
1497 * add calendar component to container
1499 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1500 * @since 2.8.8 - 2011-03-15
1501 * @param object $component calendar component
1502 * @param mixed $arg1 optional, ordno/component type/ component uid
1503 * @param mixed $arg2 optional, ordno if arg1 = component type
1506 function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) {
1507 $component->setConfig( $this->getConfig(), FALSE, TRUE );
1508 if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
1509 /* make sure dtstamp and uid is set */
1510 $dummy1 = $component->getProperty( 'dtstamp' );
1511 $dummy2 = $component->getProperty( 'uid' );
1513 if( !$arg1 ) { // plain insert, last in chain
1514 $this->components[] = $component->copy();
1517 $argType = $index = null;
1518 if ( ctype_digit( (string) $arg1 )) { // index insert/replace
1520 $index = (int) $arg1 - 1;
1522 elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
1523 $argType = strtolower( $arg1 );
1524 $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
1526 // else if arg1 is set, arg1 must be an UID
1528 foreach ( $this->components as $cix => $component2) {
1529 if( empty( $component2 )) continue;
1530 if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
1531 $this->components[$cix] = $component->copy();
1534 elseif( $argType == $component2->objName ) { // component Type index insert/replace
1535 if( $index == $cix1sC ) {
1536 $this->components[$cix] = $component->copy();
1541 elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
1542 $this->components[$cix] = $component->copy();
1546 /* arg1=index and not found.. . insert at index .. .*/
1547 if( 'INDEX' == $argType ) {
1548 $this->components[$index] = $component->copy();
1549 ksort( $this->components, SORT_NUMERIC );
1551 else /* not found.. . insert last in chain anyway .. .*/
1552 $this->components[] = $component->copy();
1556 * sort iCal compoments
1558 * ascending sort on properties (if exist) x-current-dtstart, dtstart,
1559 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid
1560 * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES
1562 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1563 * @since 2.8.4 - 2011-06-02
1564 * @param string $sortArg, optional
1568 function sort( $sortArg=FALSE ) {
1569 if( is_array( $this->components )) {
1571 $sortArg = strtoupper( $sortArg );
1572 if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' )))
1575 /* set sort parameters for each component */
1576 foreach( $this->components as $cix => & $c ) {
1577 $c->srtk = array( '0', '0', '0', '0' );
1578 if( 'vtimezone' == $c->objName ) {
1579 if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
1583 elseif( $sortArg ) {
1584 if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
1585 $propValues = array();
1586 $c->_getProperties( $sortArg, $propValues );
1587 $c->srtk[0] = reset( array_keys( $propValues ));
1589 elseif( FALSE !== ( $d = $c->getProperty( $sortArg )))
1593 if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' )))
1594 $c->srtk[0] = iCal_UtilityFunctions::_date_time_string( $d[1] );
1595 elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
1596 $c->srtk[1] = 0; // sortkey 0 : dtstart
1597 if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' )))
1598 $c->srtk[1] = iCal_UtilityFunctions::_date_time_string( $d[1] ); // sortkey 1 : dtend/due(/dtstart+duration)
1599 elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
1600 if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' )))
1601 $c->srtk[1] = iCal_UtilityFunctions::_date_time_string( $d[1] );
1602 elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
1603 if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
1606 if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' ))) // sortkey 2 : created/dtstamp
1607 if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
1609 if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' ))) // sortkey 3 : uid
1611 } // end foreach( $this->components as & $c
1613 usort( $this->components, array( $this, '_cmpfcn' ));
1616 function _cmpfcn( $a, $b ) {
1617 if( empty( $a )) return -1;
1618 if( empty( $b )) return 1;
1619 if( 'vtimezone' == $a->objName ) {
1620 if( 'vtimezone' != $b->objName ) return -1;
1621 elseif( $a->srtk[0] <= $b->srtk[0] ) return -1;
1624 elseif( 'vtimezone' == $b->objName ) return 1;
1625 $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
1626 for( $k = 0; $k < 4 ; $k++ ) {
1627 if( empty( $a->srtk[$k] )) return -1;
1628 elseif( empty( $b->srtk[$k] )) return 1;
1629 if( is_array( $a->srtk[$k] )) {
1630 if( is_array( $b->srtk[$k] )) {
1631 foreach( $sortkeys as $key ) {
1632 if ( empty( $a->srtk[$k][$key] )) return -1;
1633 elseif( empty( $b->srtk[$k][$key] )) return 1;
1634 if ( $a->srtk[$k][$key] == $b->srtk[$k][$key])
1636 if (( (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
1638 elseif(( (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
1644 elseif( is_array( $b->srtk[$k] )) return 1;
1645 elseif( $a->srtk[$k] < $b->srtk[$k] ) return -1;
1646 elseif( $a->srtk[$k] > $b->srtk[$k] ) return 1;
1651 * parse iCal text/file into iCal_Vcalendar, components, properties and parameters
1653 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1654 * @since 2.8.2 - 2011-05-21
1655 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
1656 * @return bool FALSE if error occurs during parsing
1659 function parse( $unparsedtext=FALSE ) {
1660 $nl = $this->getConfig( 'nl' );
1661 if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
1662 /* directory+filename is set previously via setConfig directory+filename or url */
1663 if( FALSE === ( $filename = $this->getConfig( 'url' )))
1664 $filename = $this->getConfig( 'dirfile' );
1666 if( FALSE === ( $rows = file_get_contents( $filename )))
1667 return FALSE; /* err 1 */
1669 elseif( is_array( $unparsedtext ))
1670 $rows = implode( '\n'.$nl, $unparsedtext );
1672 $rows = & $unparsedtext;
1673 /* identify BEGIN:VCALENDAR, MUST be first row */
1674 if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 )))
1675 return FALSE; /* err 8 */
1676 /* fix line folding */
1677 $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
1679 foreach( $eolchars as $eolchar ) {
1680 if( !$EOLmark && ( FALSE !== strpos( $rows, $eolchar ))) {
1681 $rows = str_replace( $eolchar." ", '', $rows );
1682 $rows = str_replace( $eolchar."\t", '', $rows );
1683 if( $eolchar != $nl )
1684 $rows = str_replace( $eolchar, $nl, $rows );
1688 $tmp = explode( $nl, $rows );
1690 foreach( $tmp as $tmpr )
1691 if( !empty( $tmpr ))
1693 /* skip trailing empty lines */
1694 $lix = count( $rows ) - 1;
1695 while( empty( $rows[$lix] ) && ( 0 < $lix ))
1697 /* identify ending END:VCALENDAR row, MUST be last row */
1698 if( 'END:VCALENDAR' != strtoupper( substr( $rows[$lix], 0, 13 )))
1699 return FALSE; /* err 9 */
1700 if( 3 > count( $rows ))
1701 return FALSE; /* err 10 */
1704 /* identify components and update unparsed data within component */
1705 $config = $this->getConfig();
1706 foreach( $rows as $line ) {
1707 if( '' == trim( $line ))
1709 if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
1713 elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) {
1717 elseif( 1 != $calsync )
1718 return FALSE; /* err 20 */
1719 elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) {
1720 $this->components[] = $comp->copy();
1724 if( 'BEGIN:VEVENT' == strtoupper( substr( $line, 0, 12 )))
1725 $comp = new vevent( $config );
1726 elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 )))
1727 $comp = new vfreebusy( $config );
1728 elseif( 'BEGIN:VJOURNAL' == strtoupper( substr( $line, 0, 14 )))
1729 $comp = new vjournal( $config );
1730 elseif( 'BEGIN:VTODO' == strtoupper( substr( $line, 0, 11 )))
1731 $comp = new vtodo( $config );
1732 elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 )))
1733 $comp = new vtimezone( $config );
1734 else /* update component with unparsed data */
1735 $comp->unparsed[] = $line;
1736 } // end - foreach( rows.. .
1738 /* parse data for calendar (this) object */
1739 if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
1740 /* concatenate property values spread over several lines */
1742 $propnames = array( 'calscale','method','prodid','version','x-' );
1743 $proprows = array();
1744 foreach( $this->unparsed as $line ) {
1745 if( '' == trim( $line ))
1748 foreach ( $propnames as $propname ) {
1749 if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
1757 $proprows[$lastix] = $line;
1760 $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
1762 foreach( $proprows as $line ) {
1763 $line = str_replace( '!"#¤%&/()=? ', '', $line );
1764 $line = str_replace( '!"#¤%&/()=?', '', $line );
1765 if( '\n' == substr( $line, -2 ))
1766 $line = substr( $line, 0, strlen( $line ) - 2 );
1767 /* get property name */
1768 $cix = $propname = null;
1769 for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
1770 if( in_array( $line[$cix], array( ':', ';' )))
1773 $propname .= $line[$cix];
1775 /* ignore version/prodid properties */
1776 if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' )))
1778 $line = substr( $line, $cix);
1779 /* separate attributes from value */
1782 $strlen = strlen( $line );
1783 for( $cix=0; $cix < $strlen; $cix++ ) {
1784 if(( ':' == $line[$cix] ) &&
1785 ( '://' != substr( $line, $cix, 3 )) &&
1786 ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) &&
1787 ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) &&
1788 ( 'mailto:' != strtolower( substr( $line, $cix - 6, 7 )))) {
1790 if(( $cix < ( $strlen - 4 )) &&
1791 ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
1792 for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
1793 if( '://' == substr( $line, $c2ix - 2, 3 )) {
1795 break; // an URI with a portnr!!
1800 $line = substr( $line, $cix + 1 );
1804 if( ';' == $line[$cix] )
1805 $attr[++$attrix] = null;
1807 $attr[$attrix] .= $line[$cix];
1810 /* make attributes in array format */
1811 $propattr = array();
1812 foreach( $attr as $attribute ) {
1813 $attrsplit = explode( '=', $attribute, 2 );
1814 if( 1 < count( $attrsplit ))
1815 $propattr[$attrsplit[0]] = $attrsplit[1];
1817 $propattr[] = $attribute;
1819 /* update Property */
1820 if( FALSE !== strpos( $line, ',' )) {
1821 $content = explode( ',', $line );
1822 $clen = count( $content );
1823 for( $cix = 0; $cix < $clen; $cix++ ) {
1824 if( "\\" == substr( $content[$cix], -1 )) {
1825 $content[$cix] .= ','.$content[$cix + 1];
1826 unset( $content[$cix + 1] );
1830 if( 1 < count( $content )) {
1831 foreach( $content as $cix => $contentPart )
1832 $content[$cix] = calendarComponent::_strunrep( $contentPart );
1833 $this->setProperty( $propname, $content, $propattr );
1837 $line = reset( $content );
1838 $line = calendarComponent::_strunrep( $line );
1840 $this->setProperty( $propname, trim( $line ), $propattr );
1841 } // end - foreach( $this->unparsed.. .
1842 } // end - if( is_array( $this->unparsed.. .
1843 unset( $unparsedtext, $rows, $this->unparsed, $proprows );
1844 /* parse Components */
1845 if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
1846 $ckeys = array_keys( $this->components );
1847 foreach( $ckeys as $ckey ) {
1848 if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
1849 $this->components[$ckey]->parse();
1854 return FALSE; /* err 91 or something.. . */
1857 /*********************************************************************************/
1859 * creates formatted output for calendar object instance
1861 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1862 * @since 2.8.1 - 2011-03-12
1865 function createCalendar() {
1866 $calendarInit1 = $calendarInit2 = $calendarxCaldecl = $calendarStart = $calendar = null;
1867 switch( $this->format ) {
1869 $calendarInit1 = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
1870 '<!DOCTYPE iCalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
1871 '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
1872 $calendarInit2 = '>'.$this->nl;
1873 $calendarStart = '<vcalendar';
1876 $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
1879 $calendarStart .= $this->createVersion();
1880 $calendarStart .= $this->createProdid();
1881 $calendarStart .= $this->createCalscale();
1882 $calendarStart .= $this->createMethod();
1883 switch( $this->format ) {
1885 $nlstrlen = strlen( $this->nl );
1886 if( $this->nl == substr( $calendarStart, ( 0 - $nlstrlen )))
1887 $calendarStart = substr( $calendarStart, 0, ( strlen( $calendarStart ) - $nlstrlen ));
1888 $calendarStart .= '>'.$this->nl;
1893 $calendar .= $this->createXprop();
1894 foreach( $this->components as $component ) {
1895 if( empty( $component )) continue;
1896 $component->setConfig( $this->getConfig(), FALSE, TRUE );
1897 $calendar .= $component->createComponent( $this->xcaldecl );
1899 if(( 0 < count( $this->xcaldecl )) && ( 'xcal' == $this->format )) { // xCal only
1900 $calendarInit1 .= $this->nl.'['.$this->nl;
1901 $old_xcaldecl = array();
1902 foreach( $this->xcaldecl as $declix => $declPart ) {
1903 if(( 0 < count( $old_xcaldecl)) &&
1904 ( in_array( $declPart['uri'], $old_xcaldecl['uri'] )) &&
1905 ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
1906 continue; // no duplicate uri and ext. references
1907 $calendarxCaldecl .= '<!';
1908 foreach( $declPart as $declKey => $declValue ) {
1909 switch( $declKey ) { // index
1910 case 'xmldecl': // no 1
1911 $calendarxCaldecl .= $declValue.' ';
1914 $calendarxCaldecl .= $declValue.' ';
1915 $old_xcaldecl['uri'][] = $declValue;
1918 $calendarxCaldecl .= $declValue.' ';
1920 case 'external': // no 4
1921 $calendarxCaldecl .= '"'.$declValue.'" ';
1922 $old_xcaldecl['external'][] = $declValue;
1924 case 'type': // no 5
1925 $calendarxCaldecl .= $declValue.' ';
1927 case 'type2': // no 6
1928 $calendarxCaldecl .= $declValue;
1932 $calendarxCaldecl .= '>'.$this->nl;
1934 $calendarInit2 = ']'.$calendarInit2;
1936 switch( $this->format ) {
1938 $calendar .= '</vcalendar>'.$this->nl;
1941 $calendar .= 'END:VCALENDAR'.$this->nl;
1944 return $calendarInit1.$calendarxCaldecl.$calendarInit2.$calendarStart.$calendar;
1947 * a HTTP redirect header is sent with created, updated and/or parsed calendar
1949 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1950 * @since 2.9.12 - 2011-07-13
1951 * @param bool $utf8Encode
1955 function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
1956 $filename = $this->getConfig( 'filename' );
1957 $output = $this->createCalendar();
1959 $output = utf8_encode( $output );
1961 $output = gzencode( $output, 9 );
1962 header( 'Content-Encoding: gzip');
1965 $filesize = strlen( $output );
1966 if( 'xcal' == $this->format )
1967 header( 'Content-Type: application/calendar+xml; charset=utf-8' );
1969 header( 'Content-Type: text/calendar; charset=utf-8' );
1970 header( 'Content-Length: '.$filesize );
1971 header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
1972 header( 'Cache-Control: max-age=10' );
1977 * save content in a file
1979 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1980 * @since 2.2.12 - 2007-12-30
1981 * @param string $directory optional
1982 * @param string $filename optional
1983 * @param string $delimiter optional
1986 function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
1988 $this->setConfig( 'directory', $directory );
1990 $this->setConfig( 'filename', $filename );
1991 if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
1992 $this->setConfig( 'delimiter', $delimiter );
1993 if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
1994 $dirfile = $this->getConfig( 'dirfile' );
1995 $iCalFile = @fopen( $dirfile, 'w' );
1997 if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
1999 fclose( $iCalFile );
2006 * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
2007 * else FALSE is returned
2009 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2010 * @since 2.2.12 - 2007-10-28
2011 * @param string $directory optional alt. int timeout
2012 * @param string $filename optional
2013 * @param string $delimiter optional
2014 * @param int timeout optional, default 3600 sec
2015 * @return redirect/FALSE
2017 function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
2018 if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
2019 $timeout = (int) $directory;
2023 $this->setConfig( 'directory', $directory );
2025 $this->setConfig( 'filename', $filename );
2026 if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
2027 $this->setConfig( 'delimiter', $delimiter );
2028 $filesize = $this->getConfig( 'filesize' );
2029 if( 0 >= $filesize )
2031 $dirfile = $this->getConfig( 'dirfile' );
2032 if( time() - filemtime( $dirfile ) < $timeout) {
2034 $dirfile = $this->getConfig( 'dirfile' );
2035 $filename = $this->getConfig( 'filename' );
2036 // if( headers_sent( $filename, $linenum ))
2037 // die( "Headers already sent in $filename on line $linenum\n" );
2038 if( 'xcal' == $this->format )
2039 header( 'Content-Type: application/calendar+xml; charset=utf-8' );
2041 header( 'Content-Type: text/calendar; charset=utf-8' );
2042 header( 'Content-Length: '.$filesize );
2043 header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
2044 header( 'Cache-Control: max-age=10' );
2045 $fp = @fopen( $dirfile, 'r' );