Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117887633
Horde_iCalendar.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
111 KB
Referenced Files
None
Subscribers
None
Horde_iCalendar.php
View Options
<?php
/**
* This is a concatenated copy of the following files:
* Horde/String.php, Horde/iCalendar.php, Horde/iCalendar/*.php
*/
require_once
(
dirname
(
__FILE__
)
.
'/Horde_Date.php'
);
$GLOBALS
[
'_HORDE_STRING_CHARSET'
]
=
'iso-8859-1'
;
/**
* The String:: class provides static methods for charset and locale safe
* string manipulation.
*
* $Horde: framework/Util/String.php,v 1.43.6.38 2009-09-15 16:36:14 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Jan Schneider <jan@horde.org>
* @since Horde 3.0
* @package Horde_Util
*/
class
String
{
/**
* Caches the result of extension_loaded() calls.
*
* @param string $ext The extension name.
*
* @return boolean Is the extension loaded?
*
* @see Util::extensionExists()
*/
function
extensionExists
(
$ext
)
{
static
$cache
=
array
();
if
(!
isset
(
$cache
[
$ext
]))
{
$cache
[
$ext
]
=
extension_loaded
(
$ext
);
}
return
$cache
[
$ext
];
}
/**
* Sets a default charset that the String:: methods will use if none is
* explicitly specified.
*
* @param string $charset The charset to use as the default one.
*/
function
setDefaultCharset
(
$charset
)
{
$GLOBALS
[
'_HORDE_STRING_CHARSET'
]
=
$charset
;
if
(
String
::
extensionExists
(
'mbstring'
)
&&
function_exists
(
'mb_regex_encoding'
))
{
$old_error
=
error_reporting
(
0
);
mb_regex_encoding
(
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
}
}
/**
* Converts a string from one charset to another.
*
* Works only if either the iconv or the mbstring extension
* are present and best if both are available.
* The original string is returned if conversion failed or none
* of the extensions were available.
*
* @param mixed $input The data to be converted. If $input is an an array,
* the array's values get converted recursively.
* @param string $from The string's current charset.
* @param string $to The charset to convert the string to. If not
* specified, the global variable
* $_HORDE_STRING_CHARSET will be used.
*
* @return mixed The converted input data.
*/
function
convertCharset
(
$input
,
$from
,
$to
=
null
)
{
/* Don't bother converting numbers. */
if
(
is_numeric
(
$input
))
{
return
$input
;
}
/* Get the user's default character set if none passed in. */
if
(
is_null
(
$to
))
{
$to
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
/* If the from and to character sets are identical, return now. */
if
(
$from
==
$to
)
{
return
$input
;
}
$from
=
String
::
lower
(
$from
);
$to
=
String
::
lower
(
$to
);
if
(
$from
==
$to
)
{
return
$input
;
}
if
(
is_array
(
$input
))
{
$tmp
=
array
();
reset
(
$input
);
while
(
list
(
$key
,
$val
)
=
each
(
$input
))
{
$tmp
[
String
::
_convertCharset
(
$key
,
$from
,
$to
)]
=
String
::
convertCharset
(
$val
,
$from
,
$to
);
}
return
$tmp
;
}
if
(
is_object
(
$input
))
{
// PEAR_Error objects are almost guaranteed to contain recursion,
// which will cause a segfault in PHP. We should never reach
// this line, but add a check and a log message to help the devs
// track down and fix this issue.
if
(
is_a
(
$input
,
'PEAR_Error'
))
{
Horde
::
logMessage
(
'Called convertCharset() on a PEAR_Error object. '
.
print_r
(
$input
,
true
),
__FILE__
,
__LINE__
,
PEAR_LOG_DEBUG
);
return
''
;
}
$vars
=
get_object_vars
(
$input
);
while
(
list
(
$key
,
$val
)
=
each
(
$vars
))
{
$input
->
$key
=
String
::
convertCharset
(
$val
,
$from
,
$to
);
}
return
$input
;
}
if
(!
is_string
(
$input
))
{
return
$input
;
}
return
String
::
_convertCharset
(
$input
,
$from
,
$to
);
}
/**
* Internal function used to do charset conversion.
*
* @access private
*
* @param string $input See String::convertCharset().
* @param string $from See String::convertCharset().
* @param string $to See String::convertCharset().
*
* @return string The converted string.
*/
function
_convertCharset
(
$input
,
$from
,
$to
)
{
$output
=
''
;
$from_check
=
((
$from
==
'iso-8859-1'
)
||
(
$from
==
'us-ascii'
));
$to_check
=
((
$to
==
'iso-8859-1'
)
||
(
$to
==
'us-ascii'
));
/* Use utf8_[en|de]code() if possible and if the string isn't too
* large (less than 16 MB = 16 * 1024 * 1024 = 16777216 bytes) - these
* functions use more memory. */
if
(
strlen
(
$input
)
<
16777216
||
!(
String
::
extensionExists
(
'iconv'
)
||
String
::
extensionExists
(
'mbstring'
)))
{
if
(
$from_check
&&
(
$to
==
'utf-8'
))
{
return
utf8_encode
(
$input
);
}
if
((
$from
==
'utf-8'
)
&&
$to_check
)
{
return
utf8_decode
(
$input
);
}
}
/* First try iconv with transliteration. */
if
((
$from
!=
'utf7-imap'
)
&&
(
$to
!=
'utf7-imap'
)
&&
String
::
extensionExists
(
'iconv'
))
{
/* We need to tack an extra character temporarily because of a bug
* in iconv() if the last character is not a 7 bit ASCII
* character. */
$oldTrackErrors
=
ini_set
(
'track_errors'
,
1
);
unset
(
$php_errormsg
);
$output
=
@
iconv
(
$from
,
$to
.
'//TRANSLIT'
,
$input
.
'x'
);
$output
=
(
isset
(
$php_errormsg
))
?
false
:
String
::
substr
(
$output
,
0
,
-
1
,
$to
);
ini_set
(
'track_errors'
,
$oldTrackErrors
);
}
/* Next try mbstring. */
if
(!
$output
&&
String
::
extensionExists
(
'mbstring'
))
{
$old_error
=
error_reporting
(
0
);
$output
=
mb_convert_encoding
(
$input
,
$to
,
String
::
_mbstringCharset
(
$from
));
error_reporting
(
$old_error
);
}
/* At last try imap_utf7_[en|de]code if appropriate. */
if
(!
$output
&&
String
::
extensionExists
(
'imap'
))
{
if
(
$from_check
&&
(
$to
==
'utf7-imap'
))
{
return
@
imap_utf7_encode
(
$input
);
}
if
((
$from
==
'utf7-imap'
)
&&
$to_check
)
{
return
@
imap_utf7_decode
(
$input
);
}
}
return
(!
$output
)
?
$input
:
$output
;
}
/**
* Makes a string lowercase.
*
* @param string $string The string to be converted.
* @param boolean $locale If true the string will be converted based on a
* given charset, locale independent else.
* @param string $charset If $locale is true, the charset to use when
* converting. If not provided the current charset.
*
* @return string The string with lowercase characters
*/
function
lower
(
$string
,
$locale
=
false
,
$charset
=
null
)
{
static
$lowers
;
if
(
$locale
)
{
/* The existence of mb_strtolower() depends on the platform. */
if
(
String
::
extensionExists
(
'mbstring'
)
&&
function_exists
(
'mb_strtolower'
))
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$old_error
=
error_reporting
(
0
);
$ret
=
mb_strtolower
(
$string
,
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
if
(!
empty
(
$ret
))
{
return
$ret
;
}
}
return
strtolower
(
$string
);
}
if
(!
isset
(
$lowers
))
{
$lowers
=
array
();
}
if
(!
isset
(
$lowers
[
$string
]))
{
$language
=
setlocale
(
LC_CTYPE
,
0
);
setlocale
(
LC_CTYPE
,
'C'
);
$lowers
[
$string
]
=
strtolower
(
$string
);
setlocale
(
LC_CTYPE
,
$language
);
}
return
$lowers
[
$string
];
}
/**
* Makes a string uppercase.
*
* @param string $string The string to be converted.
* @param boolean $locale If true the string will be converted based on a
* given charset, locale independent else.
* @param string $charset If $locale is true, the charset to use when
* converting. If not provided the current charset.
*
* @return string The string with uppercase characters
*/
function
upper
(
$string
,
$locale
=
false
,
$charset
=
null
)
{
static
$uppers
;
if
(
$locale
)
{
/* The existence of mb_strtoupper() depends on the
* platform. */
if
(
function_exists
(
'mb_strtoupper'
))
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$old_error
=
error_reporting
(
0
);
$ret
=
mb_strtoupper
(
$string
,
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
if
(!
empty
(
$ret
))
{
return
$ret
;
}
}
return
strtoupper
(
$string
);
}
if
(!
isset
(
$uppers
))
{
$uppers
=
array
();
}
if
(!
isset
(
$uppers
[
$string
]))
{
$language
=
setlocale
(
LC_CTYPE
,
0
);
setlocale
(
LC_CTYPE
,
'C'
);
$uppers
[
$string
]
=
strtoupper
(
$string
);
setlocale
(
LC_CTYPE
,
$language
);
}
return
$uppers
[
$string
];
}
/**
* Returns a string with the first letter capitalized if it is
* alphabetic.
*
* @param string $string The string to be capitalized.
* @param boolean $locale If true the string will be converted based on a
* given charset, locale independent else.
* @param string $charset The charset to use, defaults to current charset.
*
* @return string The capitalized string.
*/
function
ucfirst
(
$string
,
$locale
=
false
,
$charset
=
null
)
{
if
(
$locale
)
{
$first
=
String
::
substr
(
$string
,
0
,
1
,
$charset
);
if
(
String
::
isAlpha
(
$first
,
$charset
))
{
$string
=
String
::
upper
(
$first
,
true
,
$charset
)
.
String
::
substr
(
$string
,
1
,
null
,
$charset
);
}
}
else
{
$string
=
String
::
upper
(
substr
(
$string
,
0
,
1
),
false
)
.
substr
(
$string
,
1
);
}
return
$string
;
}
/**
* Returns part of a string.
*
* @param string $string The string to be converted.
* @param integer $start The part's start position, zero based.
* @param integer $length The part's length.
* @param string $charset The charset to use when calculating the part's
* position and length, defaults to current
* charset.
*
* @return string The string's part.
*/
function
substr
(
$string
,
$start
,
$length
=
null
,
$charset
=
null
)
{
if
(
is_null
(
$length
))
{
$length
=
String
::
length
(
$string
,
$charset
)
-
$start
;
}
if
(
$length
==
0
)
{
return
''
;
}
/* Try iconv. */
if
(
function_exists
(
'iconv_substr'
))
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$old_error
=
error_reporting
(
0
);
$ret
=
iconv_substr
(
$string
,
$start
,
$length
,
$charset
);
error_reporting
(
$old_error
);
/* iconv_substr() returns false on failure. */
if
(
$ret
!==
false
)
{
return
$ret
;
}
}
/* Try mbstring. */
if
(
String
::
extensionExists
(
'mbstring'
))
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$old_error
=
error_reporting
(
0
);
$ret
=
mb_substr
(
$string
,
$start
,
$length
,
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
/* mb_substr() returns empty string on failure. */
if
(
strlen
(
$ret
))
{
return
$ret
;
}
}
return
substr
(
$string
,
$start
,
$length
);
}
/**
* Returns the character (not byte) length of a string.
*
* @param string $string The string to return the length of.
* @param string $charset The charset to use when calculating the string's
* length.
*
* @return string The string's part.
*/
function
length
(
$string
,
$charset
=
null
)
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$charset
=
String
::
lower
(
$charset
);
if
(
$charset
==
'utf-8'
||
$charset
==
'utf8'
)
{
return
strlen
(
utf8_decode
(
$string
));
}
if
(
String
::
extensionExists
(
'mbstring'
))
{
$old_error
=
error_reporting
(
0
);
$ret
=
mb_strlen
(
$string
,
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
if
(!
empty
(
$ret
))
{
return
$ret
;
}
}
return
strlen
(
$string
);
}
/**
* Returns the numeric position of the first occurrence of $needle
* in the $haystack string.
*
* @param string $haystack The string to search through.
* @param string $needle The string to search for.
* @param integer $offset Allows to specify which character in haystack
* to start searching.
* @param string $charset The charset to use when searching for the
* $needle string.
*
* @return integer The position of first occurrence.
*/
function
pos
(
$haystack
,
$needle
,
$offset
=
0
,
$charset
=
null
)
{
if
(
String
::
extensionExists
(
'mbstring'
))
{
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$track_errors
=
ini_set
(
'track_errors'
,
1
);
$old_error
=
error_reporting
(
0
);
$ret
=
mb_strpos
(
$haystack
,
$needle
,
$offset
,
String
::
_mbstringCharset
(
$charset
));
error_reporting
(
$old_error
);
ini_set
(
'track_errors'
,
$track_errors
);
if
(!
isset
(
$php_errormsg
))
{
return
$ret
;
}
}
return
strpos
(
$haystack
,
$needle
,
$offset
);
}
/**
* Returns a string padded to a certain length with another string.
*
* This method behaves exactly like str_pad but is multibyte safe.
*
* @param string $input The string to be padded.
* @param integer $length The length of the resulting string.
* @param string $pad The string to pad the input string with. Must
* be in the same charset like the input string.
* @param const $type The padding type. One of STR_PAD_LEFT,
* STR_PAD_RIGHT, or STR_PAD_BOTH.
* @param string $charset The charset of the input and the padding
* strings.
*
* @return string The padded string.
*/
function
pad
(
$input
,
$length
,
$pad
=
' '
,
$type
=
STR_PAD_RIGHT
,
$charset
=
null
)
{
$mb_length
=
String
::
length
(
$input
,
$charset
);
$sb_length
=
strlen
(
$input
);
$pad_length
=
String
::
length
(
$pad
,
$charset
);
/* Return if we already have the length. */
if
(
$mb_length
>=
$length
)
{
return
$input
;
}
/* Shortcut for single byte strings. */
if
(
$mb_length
==
$sb_length
&&
$pad_length
==
strlen
(
$pad
))
{
return
str_pad
(
$input
,
$length
,
$pad
,
$type
);
}
switch
(
$type
)
{
case
STR_PAD_LEFT
:
$left
=
$length
-
$mb_length
;
$output
=
String
::
substr
(
str_repeat
(
$pad
,
ceil
(
$left
/
$pad_length
)),
0
,
$left
,
$charset
)
.
$input
;
break
;
case
STR_PAD_BOTH
:
$left
=
floor
((
$length
-
$mb_length
)
/
2
);
$right
=
ceil
((
$length
-
$mb_length
)
/
2
);
$output
=
String
::
substr
(
str_repeat
(
$pad
,
ceil
(
$left
/
$pad_length
)),
0
,
$left
,
$charset
)
.
$input
.
String
::
substr
(
str_repeat
(
$pad
,
ceil
(
$right
/
$pad_length
)),
0
,
$right
,
$charset
);
break
;
case
STR_PAD_RIGHT
:
$right
=
$length
-
$mb_length
;
$output
=
$input
.
String
::
substr
(
str_repeat
(
$pad
,
ceil
(
$right
/
$pad_length
)),
0
,
$right
,
$charset
);
break
;
}
return
$output
;
}
/**
* Wraps the text of a message.
*
* @since Horde 3.2
*
* @param string $string String containing the text to wrap.
* @param integer $width Wrap the string at this number of
* characters.
* @param string $break Character(s) to use when breaking lines.
* @param boolean $cut Whether to cut inside words if a line
* can't be wrapped.
* @param string $charset Character set to use when breaking lines.
* @param boolean $line_folding Whether to apply line folding rules per
* RFC 822 or similar. The correct break
* characters including leading whitespace
* have to be specified too.
*
* @return string String containing the wrapped text.
*/
function
wordwrap
(
$string
,
$width
=
75
,
$break
=
"
\n
"
,
$cut
=
false
,
$charset
=
null
,
$line_folding
=
false
)
{
/* Get the user's default character set if none passed in. */
if
(
is_null
(
$charset
))
{
$charset
=
$GLOBALS
[
'_HORDE_STRING_CHARSET'
];
}
$charset
=
String
::
_mbstringCharset
(
$charset
);
$string
=
String
::
convertCharset
(
$string
,
$charset
,
'utf-8'
);
$wrapped
=
''
;
while
(
String
::
length
(
$string
,
'utf-8'
)
>
$width
)
{
$line
=
String
::
substr
(
$string
,
0
,
$width
,
'utf-8'
);
$string
=
String
::
substr
(
$string
,
String
::
length
(
$line
,
'utf-8'
),
null
,
'utf-8'
);
// Make sure didn't cut a word, unless we want hard breaks anyway.
if
(!
$cut
&&
preg_match
(
'/^(.+?)((
\s
|
\r
?
\n
).*)/us'
,
$string
,
$match
))
{
$line
.=
$match
[
1
];
$string
=
$match
[
2
];
}
// Wrap at existing line breaks.
if
(
preg_match
(
'/^(.*?)(
\r
?
\n
)(.*)$/u'
,
$line
,
$match
))
{
$wrapped
.=
$match
[
1
]
.
$match
[
2
];
$string
=
$match
[
3
]
.
$string
;
continue
;
}
// Wrap at the last colon or semicolon followed by a whitespace if
// doing line folding.
if
(
$line_folding
&&
preg_match
(
'/^(.*?)(;|:)(
\s
+.*)$/u'
,
$line
,
$match
))
{
$wrapped
.=
$match
[
1
]
.
$match
[
2
]
.
$break
;
$string
=
$match
[
3
]
.
$string
;
continue
;
}
// Wrap at the last whitespace of $line.
if
(
$line_folding
)
{
$sub
=
'(.+[^
\s
])'
;
}
else
{
$sub
=
'(.*)'
;
}
if
(
preg_match
(
'/^'
.
$sub
.
'(
\s
+)(.*)$/u'
,
$line
,
$match
))
{
$wrapped
.=
$match
[
1
]
.
$break
;
$string
=
(
$line_folding
?
$match
[
2
]
:
''
)
.
$match
[
3
]
.
$string
;
continue
;
}
// Hard wrap if necessary.
if
(
$cut
)
{
$wrapped
.=
$line
.
$break
;
continue
;
}
$wrapped
.=
$line
;
}
return
String
::
convertCharset
(
$wrapped
.
$string
,
'utf-8'
,
$charset
);
}
/**
* Wraps the text of a message.
*
* @param string $text String containing the text to wrap.
* @param integer $length Wrap $text at this number of characters.
* @param string $break_char Character(s) to use when breaking lines.
* @param string $charset Character set to use when breaking lines.
* @param boolean $quote Ignore lines that are wrapped with the '>'
* character (RFC 2646)? If true, we don't
* remove any padding whitespace at the end of
* the string.
*
* @return string String containing the wrapped text.
*/
function
wrap
(
$text
,
$length
=
80
,
$break_char
=
"
\n
"
,
$charset
=
null
,
$quote
=
false
)
{
$paragraphs
=
array
();
foreach
(
preg_split
(
'/
\r
?
\n
/'
,
$text
)
as
$input
)
{
if
(
$quote
&&
(
strpos
(
$input
,
'>'
)
===
0
))
{
$line
=
$input
;
}
else
{
/* We need to handle the Usenet-style signature line
* separately; since the space after the two dashes is
* REQUIRED, we don't want to trim the line. */
if
(
$input
!=
'-- '
)
{
$input
=
rtrim
(
$input
);
}
$line
=
String
::
wordwrap
(
$input
,
$length
,
$break_char
,
false
,
$charset
);
}
$paragraphs
[]
=
$line
;
}
return
implode
(
$break_char
,
$paragraphs
);
}
/**
* Returns true if the every character in the parameter is an alphabetic
* character.
*
* @param $string The string to test.
* @param $charset The charset to use when testing the string.
*
* @return boolean True if the parameter was alphabetic only.
*/
function
isAlpha
(
$string
,
$charset
=
null
)
{
if
(!
String
::
extensionExists
(
'mbstring'
))
{
return
ctype_alpha
(
$string
);
}
$charset
=
String
::
_mbstringCharset
(
$charset
);
$old_charset
=
mb_regex_encoding
();
$old_error
=
error_reporting
(
0
);
if
(
$charset
!=
$old_charset
)
{
mb_regex_encoding
(
$charset
);
}
$alpha
=
!
mb_ereg_match
(
'[^[:alpha:]]'
,
$string
);
if
(
$charset
!=
$old_charset
)
{
mb_regex_encoding
(
$old_charset
);
}
error_reporting
(
$old_error
);
return
$alpha
;
}
/**
* Returns true if ever character in the parameter is a lowercase letter in
* the current locale.
*
* @param $string The string to test.
* @param $charset The charset to use when testing the string.
*
* @return boolean True if the parameter was lowercase.
*/
function
isLower
(
$string
,
$charset
=
null
)
{
return
((
String
::
lower
(
$string
,
true
,
$charset
)
===
$string
)
&&
String
::
isAlpha
(
$string
,
$charset
));
}
/**
* Returns true if every character in the parameter is an uppercase letter
* in the current locale.
*
* @param string $string The string to test.
* @param string $charset The charset to use when testing the string.
*
* @return boolean True if the parameter was uppercase.
*/
function
isUpper
(
$string
,
$charset
=
null
)
{
return
((
String
::
upper
(
$string
,
true
,
$charset
)
===
$string
)
&&
String
::
isAlpha
(
$string
,
$charset
));
}
/**
* Performs a multibyte safe regex match search on the text provided.
*
* @since Horde 3.1
*
* @param string $text The text to search.
* @param array $regex The regular expressions to use, without perl
* regex delimiters (e.g. '/' or '|').
* @param string $charset The character set of the text.
*
* @return array The matches array from the first regex that matches.
*/
function
regexMatch
(
$text
,
$regex
,
$charset
=
null
)
{
if
(!
empty
(
$charset
))
{
$regex
=
String
::
convertCharset
(
$regex
,
$charset
,
'utf-8'
);
$text
=
String
::
convertCharset
(
$text
,
$charset
,
'utf-8'
);
}
$matches
=
array
();
foreach
(
$regex
as
$val
)
{
if
(
preg_match
(
'/'
.
$val
.
'/u'
,
$text
,
$matches
))
{
break
;
}
}
if
(!
empty
(
$charset
))
{
$matches
=
String
::
convertCharset
(
$matches
,
'utf-8'
,
$charset
);
}
return
$matches
;
}
/**
* Workaround charsets that don't work with mbstring functions.
*
* @access private
*
* @param string $charset The original charset.
*
* @return string The charset to use with mbstring functions.
*/
function
_mbstringCharset
(
$charset
)
{
/* mbstring functions do not handle the 'ks_c_5601-1987' &
* 'ks_c_5601-1989' charsets. However, these charsets are used, for
* example, by various versions of Outlook to send Korean characters.
* Use UHC (CP949) encoding instead. See, e.g.,
* http://lists.w3.org/Archives/Public/ietf-charsets/2001AprJun/0030.html */
if
(
in_array
(
String
::
lower
(
$charset
),
array
(
'ks_c_5601-1987'
,
'ks_c_5601-1989'
)))
{
$charset
=
'UHC'
;
}
return
$charset
;
}
}
/**
* @package Horde_iCalendar
*/
/**
* String package
*/
/**
* Class representing iCalendar files.
*
* $Horde: framework/iCalendar/iCalendar.php,v 1.57.4.81 2010-11-10 14:34:25 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar
{
/**
* The parent (containing) iCalendar object.
*
* @var Horde_iCalendar
*/
var
$_container
=
false
;
/**
* The name/value pairs of attributes for this object (UID,
* DTSTART, etc.). Which are present depends on the object and on
* what kind of component it is.
*
* @var array
*/
var
$_attributes
=
array
();
/**
* Any children (contained) iCalendar components of this object.
*
* @var array
*/
var
$_components
=
array
();
/**
* According to RFC 2425, we should always use CRLF-terminated lines.
*
* @var string
*/
var
$_newline
=
"
\r\n
"
;
/**
* iCalendar format version (different behavior for 1.0 and 2.0
* especially with recurring events).
*
* @var string
*/
var
$_version
;
function
Horde_iCalendar
(
$version
=
'2.0'
)
{
$this
->
_version
=
$version
;
$this
->
setAttribute
(
'VERSION'
,
$version
);
}
/**
* Return a reference to a new component.
*
* @param string $type The type of component to return
* @param Horde_iCalendar $container A container that this component
* will be associated with.
*
* @return object Reference to a Horde_iCalendar_* object as specified.
*
* @static
*/
function
&
newComponent
(
$type
,
&
$container
)
{
$type
=
String
::
lower
(
$type
);
$class
=
'Horde_iCalendar_'
.
$type
;
if
(!
class_exists
(
$class
))
{
include
'Horde/iCalendar/'
.
$type
.
'.php'
;
}
if
(
class_exists
(
$class
))
{
$component
=
new
$class
();
if
(
$container
!==
false
)
{
$component
->
_container
=
&
$container
;
// Use version of container, not default set by component
// constructor.
$component
->
_version
=
$container
->
_version
;
}
}
else
{
// Should return an dummy x-unknown type class here.
$component
=
false
;
}
return
$component
;
}
/**
* Sets the value of an attribute.
*
* @param string $name The name of the attribute.
* @param string $value The value of the attribute.
* @param array $params Array containing any addition parameters for
* this attribute.
* @param boolean $append True to append the attribute, False to replace
* the first matching attribute found.
* @param array $values Array representation of $value. For
* comma/semicolon seperated lists of values. If
* not set use $value as single array element.
*/
function
setAttribute
(
$name
,
$value
,
$params
=
array
(),
$append
=
true
,
$values
=
false
)
{
// Make sure we update the internal format version if
// setAttribute('VERSION', ...) is called.
if
(
$name
==
'VERSION'
)
{
$this
->
_version
=
$value
;
if
(
$this
->
_container
!==
false
)
{
$this
->
_container
->
_version
=
$value
;
}
}
if
(!
$values
)
{
$values
=
array
(
$value
);
}
$found
=
false
;
if
(!
$append
)
{
foreach
(
array_keys
(
$this
->
_attributes
)
as
$key
)
{
if
(
$this
->
_attributes
[
$key
][
'name'
]
==
String
::
upper
(
$name
))
{
$this
->
_attributes
[
$key
][
'params'
]
=
$params
;
$this
->
_attributes
[
$key
][
'value'
]
=
$value
;
$this
->
_attributes
[
$key
][
'values'
]
=
$values
;
$found
=
true
;
break
;
}
}
}
if
(
$append
||
!
$found
)
{
$this
->
_attributes
[]
=
array
(
'name'
=>
String
::
upper
(
$name
),
'params'
=>
$params
,
'value'
=>
$value
,
'values'
=>
$values
);
}
}
/**
* Sets parameter(s) for an (already existing) attribute. The
* parameter set is merged into the existing set.
*
* @param string $name The name of the attribute.
* @param array $params Array containing any additional parameters for
* this attribute.
* @return boolean True on success, false if no attribute $name exists.
*/
function
setParameter
(
$name
,
$params
=
array
())
{
$keys
=
array_keys
(
$this
->
_attributes
);
foreach
(
$keys
as
$key
)
{
if
(
$this
->
_attributes
[
$key
][
'name'
]
==
$name
)
{
$this
->
_attributes
[
$key
][
'params'
]
=
array_merge
(
$this
->
_attributes
[
$key
][
'params'
],
$params
);
return
true
;
}
}
return
false
;
}
/**
* Get the value of an attribute.
*
* @param string $name The name of the attribute.
* @param boolean $params Return the parameters for this attribute instead
* of its value.
*
* @return mixed (object) PEAR_Error if the attribute does not exist.
* (string) The value of the attribute.
* (array) The parameters for the attribute or
* multiple values for an attribute.
*/
function
getAttribute
(
$name
,
$params
=
false
)
{
$result
=
array
();
foreach
(
$this
->
_attributes
as
$attribute
)
{
if
(
$attribute
[
'name'
]
==
$name
)
{
if
(
$params
)
{
$result
[]
=
$attribute
[
'params'
];
}
else
{
$result
[]
=
$attribute
[
'value'
];
}
}
}
if
(!
count
(
$result
))
{
require_once
'PEAR.php'
;
return
PEAR
::
raiseError
(
'Attribute "'
.
$name
.
'" Not Found'
);
}
if
(
count
(
$result
)
==
1
&&
!
$params
)
{
return
$result
[
0
];
}
else
{
return
$result
;
}
}
/**
* Gets the values of an attribute as an array. Multiple values
* are possible due to:
*
* a) multiplce occurences of 'name'
* b) (unsecapd) comma seperated lists.
*
* So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY')
* will return array('a', 'b', 'c').
*
* @param string $name The name of the attribute.
* @return mixed (object) PEAR_Error if the attribute does not exist.
* (array) Multiple values for an attribute.
*/
function
getAttributeValues
(
$name
)
{
$result
=
array
();
foreach
(
$this
->
_attributes
as
$attribute
)
{
if
(
$attribute
[
'name'
]
==
$name
)
{
$result
=
array_merge
(
$attribute
[
'values'
],
$result
);
}
}
if
(!
count
(
$result
))
{
return
PEAR
::
raiseError
(
'Attribute "'
.
$name
.
'" Not Found'
);
}
return
$result
;
}
/**
* Returns the value of an attribute, or a specified default value
* if the attribute does not exist.
*
* @param string $name The name of the attribute.
* @param mixed $default What to return if the attribute specified by
* $name does not exist.
*
* @return mixed (string) The value of $name.
* (mixed) $default if $name does not exist.
*/
function
getAttributeDefault
(
$name
,
$default
=
''
)
{
$value
=
$this
->
getAttribute
(
$name
);
return
is_a
(
$value
,
'PEAR_Error'
)
?
$default
:
$value
;
}
/**
* Remove all occurences of an attribute.
*
* @param string $name The name of the attribute.
*/
function
removeAttribute
(
$name
)
{
$keys
=
array_keys
(
$this
->
_attributes
);
foreach
(
$keys
as
$key
)
{
if
(
$this
->
_attributes
[
$key
][
'name'
]
==
$name
)
{
unset
(
$this
->
_attributes
[
$key
]);
}
}
}
/**
* Get attributes for all tags or for a given tag.
*
* @param string $tag Return attributes for this tag, or all attributes if
* not given.
*
* @return array An array containing all the attributes and their types.
*/
function
getAllAttributes
(
$tag
=
false
)
{
if
(
$tag
===
false
)
{
return
$this
->
_attributes
;
}
$result
=
array
();
foreach
(
$this
->
_attributes
as
$attribute
)
{
if
(
$attribute
[
'name'
]
==
$tag
)
{
$result
[]
=
$attribute
;
}
}
return
$result
;
}
/**
* Add a vCalendar component (eg vEvent, vTimezone, etc.).
*
* @param Horde_iCalendar $component Component (subclass) to add.
*/
function
addComponent
(
$component
)
{
if
(
is_a
(
$component
,
'Horde_iCalendar'
))
{
$component
->
_container
=
&
$this
;
$this
->
_components
[]
=
&
$component
;
}
}
/**
* Retrieve all the components.
*
* @return array Array of Horde_iCalendar objects.
*/
function
getComponents
()
{
return
$this
->
_components
;
}
function
getType
()
{
return
'vcalendar'
;
}
/**
* Return the classes (entry types) we have.
*
* @return array Hash with class names Horde_iCalendar_xxx as keys
* and number of components of this class as value.
*/
function
getComponentClasses
()
{
$r
=
array
();
foreach
(
$this
->
_components
as
$c
)
{
$cn
=
strtolower
(
get_class
(
$c
));
if
(
empty
(
$r
[
$cn
]))
{
$r
[
$cn
]
=
1
;
}
else
{
$r
[
$cn
]++;
}
}
return
$r
;
}
/**
* Number of components in this container.
*
* @return integer Number of components in this container.
*/
function
getComponentCount
()
{
return
count
(
$this
->
_components
);
}
/**
* Retrieve a specific component.
*
* @param integer $idx The index of the object to retrieve.
*
* @return mixed (boolean) False if the index does not exist.
* (Horde_iCalendar_*) The requested component.
*/
function
getComponent
(
$idx
)
{
if
(
isset
(
$this
->
_components
[
$idx
]))
{
return
$this
->
_components
[
$idx
];
}
else
{
return
false
;
}
}
/**
* Locates the first child component of the specified class, and returns a
* reference to it.
*
* @param string $type The type of component to find.
*
* @return boolean|Horde_iCalendar_* False if no subcomponent of the
* specified class exists or a reference
* to the requested component.
*/
function
&
findComponent
(
$childclass
)
{
$childclass
=
'Horde_iCalendar_'
.
String
::
lower
(
$childclass
);
$keys
=
array_keys
(
$this
->
_components
);
foreach
(
$keys
as
$key
)
{
if
(
is_a
(
$this
->
_components
[
$key
],
$childclass
))
{
return
$this
->
_components
[
$key
];
}
}
$component
=
false
;
return
$component
;
}
/**
* Locates the first matching child component of the specified class, and
* returns a reference to it.
*
* @param string $childclass The type of component to find.
* @param string $attribute This attribute must be set in the component
* for it to match.
* @param string $value Optional value that $attribute must match.
*
* @return boolean|Horde_iCalendar_* False if no matching subcomponent of
* the specified class exists, or a
* reference to the requested component.
*/
function
&
findComponentByAttribute
(
$childclass
,
$attribute
,
$value
=
null
)
{
$childclass
=
'Horde_iCalendar_'
.
String
::
lower
(
$childclass
);
$keys
=
array_keys
(
$this
->
_components
);
foreach
(
$keys
as
$key
)
{
if
(
is_a
(
$this
->
_components
[
$key
],
$childclass
))
{
$attr
=
$this
->
_components
[
$key
]->
getAttribute
(
$attribute
);
if
(
is_a
(
$attr
,
'PEAR_Error'
))
{
continue
;
}
if
(
$value
!==
null
&&
$value
!=
$attr
)
{
continue
;
}
return
$this
->
_components
[
$key
];
}
}
$component
=
false
;
return
$component
;
}
/**
* Clears the iCalendar object (resets the components and attributes
* arrays).
*/
function
clear
()
{
$this
->
_components
=
array
();
$this
->
_attributes
=
array
();
}
/**
* Checks if entry is vcalendar 1.0, vcard 2.1 or vnote 1.1.
*
* These 'old' formats are defined by www.imc.org. The 'new' (non-old)
* formats icalendar 2.0 and vcard 3.0 are defined in rfc2426 and rfc2445
* respectively.
*
* @since Horde 3.1.2
*/
function
isOldFormat
()
{
if
(
$this
->
getType
()
==
'vcard'
)
{
return
(
$this
->
_version
<
3
);
}
if
(
$this
->
getType
()
==
'vNote'
)
{
return
(
$this
->
_version
<
2
);
}
if
(
$this
->
_version
>=
2
)
{
return
false
;
}
return
true
;
}
/**
* Export as vCalendar format.
*/
function
exportvCalendar
()
{
// Default values.
$requiredAttributes
[
'PRODID'
]
=
'-//The Horde Project//Horde_iCalendar Library'
.
(
defined
(
'HORDE_VERSION'
)
?
', Horde '
.
constant
(
'HORDE_VERSION'
)
:
''
)
.
'//EN'
;
$requiredAttributes
[
'METHOD'
]
=
'PUBLISH'
;
foreach
(
$requiredAttributes
as
$name
=>
$default_value
)
{
if
(
is_a
(
$this
->
getattribute
(
$name
),
'PEAR_Error'
))
{
$this
->
setAttribute
(
$name
,
$default_value
);
}
}
return
$this
->
_exportvData
(
'VCALENDAR'
);
}
/**
* Export this entry as a hash array with tag names as keys.
*
* @param boolean $paramsInKeys
* If false, the operation can be quite lossy as the
* parameters are ignored when building the array keys.
* So if you export a vcard with
* LABEL;TYPE=WORK:foo
* LABEL;TYPE=HOME:bar
* the resulting hash contains only one label field!
* If set to true, array keys look like 'LABEL;TYPE=WORK'
* @return array A hash array with tag names as keys.
*/
function
toHash
(
$paramsInKeys
=
false
)
{
$hash
=
array
();
foreach
(
$this
->
_attributes
as
$a
)
{
$k
=
$a
[
'name'
];
if
(
$paramsInKeys
&&
is_array
(
$a
[
'params'
]))
{
foreach
(
$a
[
'params'
]
as
$p
=>
$v
)
{
$k
.=
";$p=$v"
;
}
}
$hash
[
$k
]
=
$a
[
'value'
];
}
return
$hash
;
}
/**
* Parses a string containing vCalendar data.
*
* @todo This method doesn't work well at all, if $base is VCARD.
*
* @param string $text The data to parse.
* @param string $base The type of the base object.
* @param string $charset The encoding charset for $text. Defaults to
* utf-8 for new format, iso-8859-1 for old format.
* @param boolean $clear If true clears the iCal object before parsing.
*
* @return boolean True on successful import, false otherwise.
*/
function
parsevCalendar
(
$text
,
$base
=
'VCALENDAR'
,
$charset
=
null
,
$clear
=
true
)
{
if
(
$clear
)
{
$this
->
clear
();
}
if
(
preg_match
(
'/^BEGIN:'
.
$base
.
'(.*)^END:'
.
$base
.
'/ism'
,
$text
,
$matches
))
{
$container
=
true
;
$vCal
=
$matches
[
1
];
}
else
{
// Text isn't enclosed in BEGIN:VCALENDAR
// .. END:VCALENDAR. We'll try to parse it anyway.
$container
=
false
;
$vCal
=
$text
;
}
$vCal
=
trim
(
$vCal
);
// Extract all subcomponents.
$matches
=
$components
=
null
;
if
(
preg_match_all
(
'/^BEGIN:(.*)(
\r\n
|
\r
|
\n
)(.*)^END:
\1
/Uims'
,
$vCal
,
$components
))
{
foreach
(
$components
[
0
]
as
$key
=>
$data
)
{
// Remove from the vCalendar data.
$vCal
=
str_replace
(
$data
,
''
,
$vCal
);
}
}
elseif
(!
$container
)
{
return
false
;
}
// Unfold "quoted printable" folded lines like:
// BODY;ENCODING=QUOTED-PRINTABLE:=
// another=20line=
// last=20line
while
(
preg_match_all
(
'/^([^:]+;
\s
*(ENCODING=)?QUOTED-PRINTABLE(.*=
\r
?
\n
)+(.*[^=])?
\r
?
\n
)/mU'
,
$vCal
,
$matches
))
{
foreach
(
$matches
[
1
]
as
$s
)
{
$r
=
preg_replace
(
'/=
\r
?
\n
/'
,
''
,
$s
);
$vCal
=
str_replace
(
$s
,
$r
,
$vCal
);
}
}
// Unfold any folded lines.
if
(
$this
->
isOldFormat
())
{
$vCal
=
preg_replace
(
'/[
\r\n
]+([
\t
])/'
,
'$1'
,
$vCal
);
}
else
{
$vCal
=
preg_replace
(
'/[
\r\n
]+[
\t
]/'
,
''
,
$vCal
);
}
// Parse the remaining attributes.
if
(
preg_match_all
(
'/^((?:[^":]+|(?:"[^"]*")+)*):([^
\r\n
]*)
\r
?$/m'
,
$vCal
,
$matches
))
{
foreach
(
$matches
[
0
]
as
$attribute
)
{
preg_match
(
'/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^
\r\n
]*)[
\r\n
]*/'
,
$attribute
,
$parts
);
$tag
=
trim
(
String
::
upper
(
$parts
[
1
]));
$value
=
$parts
[
4
];
$params
=
array
();
// Parse parameters.
if
(!
empty
(
$parts
[
2
]))
{
preg_match_all
(
'/;(([^;=]*)(=("[^"]*"|[^;]*))?)/'
,
$parts
[
2
],
$param_parts
);
foreach
(
$param_parts
[
2
]
as
$key
=>
$paramName
)
{
$paramName
=
String
::
upper
(
$paramName
);
$paramValue
=
$param_parts
[
4
][
$key
];
if
(
$paramName
==
'TYPE'
)
{
$paramValue
=
preg_split
(
'/(?<!
\\\\
),/'
,
$paramValue
);
if
(
count
(
$paramValue
)
==
1
)
{
$paramValue
=
$paramValue
[
0
];
}
}
if
(
is_string
(
$paramValue
))
{
if
(
preg_match
(
'/"([^"]*)"/'
,
$paramValue
,
$parts
))
{
$paramValue
=
$parts
[
1
];
}
}
else
{
foreach
(
$paramValue
as
$k
=>
$tmp
)
{
if
(
preg_match
(
'/"([^"]*)"/'
,
$tmp
,
$parts
))
{
$paramValue
[
$k
]
=
$parts
[
1
];
}
}
}
$params
[
$paramName
]
=
$paramValue
;
}
}
// Charset and encoding handling.
if
((
isset
(
$params
[
'ENCODING'
])
&&
String
::
upper
(
$params
[
'ENCODING'
])
==
'QUOTED-PRINTABLE'
)
||
isset
(
$params
[
'QUOTED-PRINTABLE'
]))
{
$value
=
quoted_printable_decode
(
$value
);
if
(
isset
(
$params
[
'CHARSET'
]))
{
$value
=
String
::
convertCharset
(
$value
,
$params
[
'CHARSET'
]);
}
else
{
$value
=
String
::
convertCharset
(
$value
,
empty
(
$charset
)
?
(
$this
->
isOldFormat
()
?
'iso-8859-1'
:
'utf-8'
)
:
$charset
);
}
}
elseif
(
isset
(
$params
[
'CHARSET'
]))
{
$value
=
String
::
convertCharset
(
$value
,
$params
[
'CHARSET'
]);
}
else
{
// As per RFC 2279, assume UTF8 if we don't have an
// explicit charset parameter.
$value
=
String
::
convertCharset
(
$value
,
empty
(
$charset
)
?
(
$this
->
isOldFormat
()
?
'iso-8859-1'
:
'utf-8'
)
:
$charset
);
}
// Get timezone info for date fields from $params.
$tzid
=
isset
(
$params
[
'TZID'
])
?
trim
(
$params
[
'TZID'
],
'
\"
'
)
:
false
;
switch
(
$tag
)
{
// Date fields.
case
'COMPLETED'
:
case
'CREATED'
:
case
'LAST-MODIFIED'
:
case
'X-MOZ-LASTACK'
:
case
'X-MOZ-SNOOZE-TIME'
:
$this
->
setAttribute
(
$tag
,
$this
->
_parseDateTime
(
$value
,
$tzid
),
$params
);
break
;
case
'BDAY'
:
case
'X-SYNCJE-ANNIVERSARY'
:
case
'X-ANNIVERSARY'
:
$this
->
setAttribute
(
$tag
,
$this
->
_parseDate
(
$value
),
$params
);
break
;
case
'DTEND'
:
case
'DTSTART'
:
case
'DTSTAMP'
:
case
'DUE'
:
case
'AALARM'
:
case
'RECURRENCE-ID'
:
// types like AALARM may contain additional data after a ;
// ignore these.
$ts
=
explode
(
';'
,
$value
);
if
(
isset
(
$params
[
'VALUE'
])
&&
$params
[
'VALUE'
]
==
'DATE'
)
{
$this
->
setAttribute
(
$tag
,
$this
->
_parseDate
(
$ts
[
0
]),
$params
);
}
else
{
$this
->
setAttribute
(
$tag
,
$this
->
_parseDateTime
(
$ts
[
0
],
$tzid
),
$params
);
}
break
;
case
'TRIGGER'
:
if
(
isset
(
$params
[
'VALUE'
])
&&
$params
[
'VALUE'
]
==
'DATE-TIME'
)
{
$this
->
setAttribute
(
$tag
,
$this
->
_parseDateTime
(
$value
,
$tzid
),
$params
);
}
else
{
$this
->
setAttribute
(
$tag
,
$this
->
_parseDuration
(
$value
),
$params
);
}
break
;
// Comma seperated dates.
case
'EXDATE'
:
case
'RDATE'
:
if
(!
strlen
(
$value
))
{
break
;
}
$dates
=
array
();
$separator
=
$this
->
isOldFormat
()
?
';'
:
','
;
preg_match_all
(
'/'
.
$separator
.
'([^'
.
$separator
.
']*)/'
,
$separator
.
$value
,
$values
);
foreach
(
$values
[
1
]
as
$value
)
{
$dates
[]
=
$this
->
_parseDate
(
$value
);
}
$this
->
setAttribute
(
$tag
,
isset
(
$dates
[
0
])
?
$dates
[
0
]
:
null
,
$params
,
true
,
$dates
);
break
;
// Duration fields.
case
'DURATION'
:
$this
->
setAttribute
(
$tag
,
$this
->
_parseDuration
(
$value
),
$params
);
break
;
// Period of time fields.
case
'FREEBUSY'
:
$periods
=
array
();
preg_match_all
(
'/,([^,]*)/'
,
','
.
$value
,
$values
);
foreach
(
$values
[
1
]
as
$value
)
{
$periods
[]
=
$this
->
_parsePeriod
(
$value
);
}
$this
->
setAttribute
(
$tag
,
isset
(
$periods
[
0
])
?
$periods
[
0
]
:
null
,
$params
,
true
,
$periods
);
break
;
// UTC offset fields.
case
'TZOFFSETFROM'
:
case
'TZOFFSETTO'
:
$this
->
setAttribute
(
$tag
,
$this
->
_parseUtcOffset
(
$value
),
$params
);
break
;
// Integer fields.
case
'PERCENT-COMPLETE'
:
case
'PRIORITY'
:
case
'REPEAT'
:
case
'SEQUENCE'
:
$this
->
setAttribute
(
$tag
,
intval
(
$value
),
$params
);
break
;
// Geo fields.
case
'GEO'
:
if
(
$this
->
isOldFormat
())
{
$floats
=
explode
(
','
,
$value
);
$value
=
array
(
'latitude'
=>
floatval
(
$floats
[
1
]),
'longitude'
=>
floatval
(
$floats
[
0
]));
}
else
{
$floats
=
explode
(
';'
,
$value
);
$value
=
array
(
'latitude'
=>
floatval
(
$floats
[
0
]),
'longitude'
=>
floatval
(
$floats
[
1
]));
}
$this
->
setAttribute
(
$tag
,
$value
,
$params
);
break
;
// Recursion fields.
case
'EXRULE'
:
case
'RRULE'
:
$this
->
setAttribute
(
$tag
,
trim
(
$value
),
$params
);
break
;
// ADR, ORG and N are lists seperated by unescaped semicolons
// with a specific number of slots.
case
'ADR'
:
case
'N'
:
case
'ORG'
:
$value
=
trim
(
$value
);
// As of rfc 2426 2.4.2 semicolon, comma, and colon must
// be escaped (comma is unescaped after splitting below).
$value
=
str_replace
(
array
(
'
\\
n'
,
'
\\
N'
,
'
\\
;'
,
'
\\
:'
),
array
(
$this
->
_newline
,
$this
->
_newline
,
';'
,
':'
),
$value
);
// Split by unescaped semicolons:
$values
=
preg_split
(
'/(?<!
\\\\
);/'
,
$value
);
$value
=
str_replace
(
'
\\
;'
,
';'
,
$value
);
$values
=
str_replace
(
'
\\
;'
,
';'
,
$values
);
$this
->
setAttribute
(
$tag
,
trim
(
$value
),
$params
,
true
,
$values
);
break
;
// String fields.
default
:
if
(
$this
->
isOldFormat
())
{
// vCalendar 1.0 and vCard 2.1 only escape semicolons
// and use unescaped semicolons to create lists.
$value
=
trim
(
$value
);
// Split by unescaped semicolons:
$values
=
preg_split
(
'/(?<!
\\\\
);/'
,
$value
);
$value
=
str_replace
(
'
\\
;'
,
';'
,
$value
);
$values
=
str_replace
(
'
\\
;'
,
';'
,
$values
);
$this
->
setAttribute
(
$tag
,
trim
(
$value
),
$params
,
true
,
$values
);
}
else
{
$value
=
trim
(
$value
);
// As of rfc 2426 2.4.2 semicolon, comma, and colon
// must be escaped (comma is unescaped after splitting
// below).
$value
=
str_replace
(
array
(
'
\\
n'
,
'
\\
N'
,
'
\\
;'
,
'
\\
:'
,
'
\\\\
'
),
array
(
$this
->
_newline
,
$this
->
_newline
,
';'
,
':'
,
'
\\
'
),
$value
);
// Split by unescaped commas.
$values
=
preg_split
(
'/(?<!
\\\\
),/'
,
$value
);
$value
=
str_replace
(
'
\\
,'
,
','
,
$value
);
$values
=
str_replace
(
'
\\
,'
,
','
,
$values
);
$this
->
setAttribute
(
$tag
,
trim
(
$value
),
$params
,
true
,
$values
);
}
break
;
}
}
}
// Process all components.
if
(
$components
)
{
// vTimezone components are processed first. They are
// needed to process vEvents that may use a TZID.
foreach
(
$components
[
0
]
as
$key
=>
$data
)
{
$type
=
trim
(
$components
[
1
][
$key
]);
if
(
$type
!=
'VTIMEZONE'
)
{
continue
;
}
$component
=
&
Horde_iCalendar
::
newComponent
(
$type
,
$this
);
if
(
$component
===
false
)
{
return
PEAR
::
raiseError
(
"Unable to create object for type $type"
);
}
$component
->
parsevCalendar
(
$data
,
$type
,
$charset
);
$this
->
addComponent
(
$component
);
}
// Now process the non-vTimezone components.
foreach
(
$components
[
0
]
as
$key
=>
$data
)
{
$type
=
trim
(
$components
[
1
][
$key
]);
if
(
$type
==
'VTIMEZONE'
)
{
continue
;
}
$component
=
&
Horde_iCalendar
::
newComponent
(
$type
,
$this
);
if
(
$component
===
false
)
{
return
PEAR
::
raiseError
(
"Unable to create object for type $type"
);
}
$component
->
parsevCalendar
(
$data
,
$type
,
$charset
);
$this
->
addComponent
(
$component
);
}
}
return
true
;
}
/**
* Export this component in vCal format.
*
* @param string $base The type of the base object.
*
* @return string vCal format data.
*/
function
_exportvData
(
$base
=
'VCALENDAR'
)
{
$result
=
'BEGIN:'
.
String
::
upper
(
$base
)
.
$this
->
_newline
;
// VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR,
// as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445
if
(
$base
!==
'VEVENT'
&&
$base
!==
'VTODO'
&&
$base
!==
'VALARM'
&&
$base
!==
'VJOURNAL'
&&
$base
!==
'VFREEBUSY'
)
{
// Ensure that version is the first attribute.
$result
.=
'VERSION:'
.
$this
->
_version
.
$this
->
_newline
;
}
foreach
(
$this
->
_attributes
as
$attribute
)
{
$name
=
$attribute
[
'name'
];
if
(
$name
==
'VERSION'
)
{
// Already done.
continue
;
}
$params_str
=
''
;
$params
=
$attribute
[
'params'
];
if
(
$params
)
{
foreach
(
$params
as
$param_name
=>
$param_value
)
{
/* Skip CHARSET for iCalendar 2.0 data, not allowed. */
if
(
$param_name
==
'CHARSET'
&&
!
$this
->
isOldFormat
())
{
continue
;
}
/* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */
if
(
$this
->
isOldFormat
()
&&
$param_name
==
'VALUE'
&&
$param_value
==
'DATE'
)
{
continue
;
}
if
(
$param_value
===
null
)
{
$params_str
.=
";$param_name"
;
}
else
{
$len
=
strlen
(
$param_value
);
$safe_value
=
''
;
$quote
=
false
;
for
(
$i
=
0
;
$i
<
$len
;
++
$i
)
{
$ord
=
ord
(
$param_value
[
$i
]);
// Accept only valid characters.
if
(
$ord
==
9
||
$ord
==
32
||
$ord
==
33
||
(
$ord
>=
35
&&
$ord
<=
126
)
||
$ord
>=
128
)
{
$safe_value
.=
$param_value
[
$i
];
// Characters above 128 do not need to be
// quoted as per RFC2445 but Outlook requires
// this.
if
(
$ord
==
44
||
$ord
==
58
||
$ord
==
59
||
$ord
>=
128
)
{
$quote
=
true
;
}
}
}
if
(
$quote
)
{
$safe_value
=
'"'
.
$safe_value
.
'"'
;
}
$params_str
.=
";$param_name=$safe_value"
;
}
}
}
$value
=
$attribute
[
'value'
];
switch
(
$name
)
{
// Date fields.
case
'COMPLETED'
:
case
'CREATED'
:
case
'DCREATED'
:
case
'LAST-MODIFIED'
:
case
'X-MOZ-LASTACK'
:
case
'X-MOZ-SNOOZE-TIME'
:
$value
=
$this
->
_exportDateTime
(
$value
);
break
;
case
'DTEND'
:
case
'DTSTART'
:
case
'DTSTAMP'
:
case
'DUE'
:
case
'AALARM'
:
case
'RECURRENCE-ID'
:
if
(
isset
(
$params
[
'VALUE'
]))
{
if
(
$params
[
'VALUE'
]
==
'DATE'
)
{
// VCALENDAR 1.0 uses T000000 - T235959 for all day events:
if
(
$this
->
isOldFormat
()
&&
$name
==
'DTEND'
)
{
$d
=
new
Horde_Date
(
$value
);
$value
=
new
Horde_Date
(
array
(
'year'
=>
$d
->
year
,
'month'
=>
$d
->
month
,
'mday'
=>
$d
->
mday
-
1
));
$value
->
correct
();
$value
=
$this
->
_exportDate
(
$value
,
'235959'
);
}
else
{
$value
=
$this
->
_exportDate
(
$value
,
'000000'
);
}
}
else
{
$value
=
$this
->
_exportDateTime
(
$value
);
}
}
else
{
$value
=
$this
->
_exportDateTime
(
$value
);
}
break
;
// Comma seperated dates.
case
'EXDATE'
:
case
'RDATE'
:
$dates
=
array
();
foreach
(
$value
as
$date
)
{
if
(
isset
(
$params
[
'VALUE'
]))
{
if
(
$params
[
'VALUE'
]
==
'DATE'
)
{
$dates
[]
=
$this
->
_exportDate
(
$date
,
'000000'
);
}
elseif
(
$params
[
'VALUE'
]
==
'PERIOD'
)
{
$dates
[]
=
$this
->
_exportPeriod
(
$date
);
}
else
{
$dates
[]
=
$this
->
_exportDateTime
(
$date
);
}
}
else
{
$dates
[]
=
$this
->
_exportDateTime
(
$date
);
}
}
$value
=
implode
(
$this
->
isOldFormat
()
?
';'
:
','
,
$dates
);
break
;
case
'TRIGGER'
:
if
(
isset
(
$params
[
'VALUE'
]))
{
if
(
$params
[
'VALUE'
]
==
'DATE-TIME'
)
{
$value
=
$this
->
_exportDateTime
(
$value
);
}
elseif
(
$params
[
'VALUE'
]
==
'DURATION'
)
{
$value
=
$this
->
_exportDuration
(
$value
);
}
}
else
{
$value
=
$this
->
_exportDuration
(
$value
);
}
break
;
// Duration fields.
case
'DURATION'
:
$value
=
$this
->
_exportDuration
(
$value
);
break
;
// Period of time fields.
case
'FREEBUSY'
:
$value_str
=
''
;
foreach
(
$value
as
$period
)
{
$value_str
.=
empty
(
$value_str
)
?
''
:
','
;
$value_str
.=
$this
->
_exportPeriod
(
$period
);
}
$value
=
$value_str
;
break
;
// UTC offset fields.
case
'TZOFFSETFROM'
:
case
'TZOFFSETTO'
:
$value
=
$this
->
_exportUtcOffset
(
$value
);
break
;
// Integer fields.
case
'PERCENT-COMPLETE'
:
case
'PRIORITY'
:
case
'REPEAT'
:
case
'SEQUENCE'
:
$value
=
"$value"
;
break
;
// Geo fields.
case
'GEO'
:
if
(
$this
->
isOldFormat
())
{
$value
=
$value
[
'longitude'
]
.
','
.
$value
[
'latitude'
];
}
else
{
$value
=
$value
[
'latitude'
]
.
';'
.
$value
[
'longitude'
];
}
break
;
// Recurrence fields.
case
'EXRULE'
:
case
'RRULE'
:
break
;
default
:
if
(
$this
->
isOldFormat
())
{
if
(
is_array
(
$attribute
[
'values'
])
&&
count
(
$attribute
[
'values'
])
>
1
)
{
$values
=
$attribute
[
'values'
];
if
(
$name
==
'N'
||
$name
==
'ADR'
||
$name
==
'ORG'
)
{
$glue
=
';'
;
}
else
{
$glue
=
','
;
}
$values
=
str_replace
(
';'
,
'
\\
;'
,
$values
);
$value
=
implode
(
$glue
,
$values
);
}
else
{
/* vcard 2.1 and vcalendar 1.0 escape only
* semicolons */
$value
=
str_replace
(
';'
,
'
\\
;'
,
$value
);
}
// Text containing newlines or ASCII >= 127 must be BASE64
// or QUOTED-PRINTABLE encoded. Currently we use
// QUOTED-PRINTABLE as default.
if
(
preg_match
(
"/[^
\x
20-
\x
7F]/"
,
$value
)
&&
empty
(
$params
[
'ENCODING'
]))
{
$params
[
'ENCODING'
]
=
'QUOTED-PRINTABLE'
;
$params_str
.=
';ENCODING=QUOTED-PRINTABLE'
;
// Add CHARSET as well. At least the synthesis client
// gets confused otherwise
if
(
empty
(
$params
[
'CHARSET'
]))
{
$params
[
'CHARSET'
]
=
'UTF-8'
;
$params_str
.=
';CHARSET='
.
$params
[
'CHARSET'
];
}
}
}
else
{
if
(
is_array
(
$attribute
[
'values'
])
&&
count
(
$attribute
[
'values'
]))
{
$values
=
$attribute
[
'values'
];
if
(
$name
==
'N'
||
$name
==
'ADR'
||
$name
==
'ORG'
)
{
$glue
=
';'
;
}
else
{
$glue
=
','
;
}
// As of rfc 2426 2.5 semicolon and comma must be
// escaped.
$values
=
str_replace
(
array
(
'
\\
'
,
';'
,
','
),
array
(
'
\\\\
'
,
'
\\
;'
,
'
\\
,'
),
$values
);
$value
=
implode
(
$glue
,
$values
);
}
else
{
// As of rfc 2426 2.5 semicolon and comma must be
// escaped.
$value
=
str_replace
(
array
(
'
\\
'
,
';'
,
','
),
array
(
'
\\\\
'
,
'
\\
;'
,
'
\\
,'
),
$value
);
}
$value
=
preg_replace
(
'/
\r
?
\n
/'
,
'
\n
'
,
$value
);
}
break
;
}
$value
=
str_replace
(
"
\r
"
,
''
,
$value
);
if
(!
empty
(
$params
[
'ENCODING'
])
&&
$params
[
'ENCODING'
]
==
'QUOTED-PRINTABLE'
&&
strlen
(
trim
(
$value
)))
{
$result
.=
$name
.
$params_str
.
':'
.
str_replace
(
'=0A'
,
'=0D=0A'
,
$this
->
_quotedPrintableEncode
(
$value
))
.
$this
->
_newline
;
}
else
{
$attr_string
=
$name
.
$params_str
.
':'
.
$value
;
if
(!
$this
->
isOldFormat
())
{
$attr_string
=
String
::
wordwrap
(
$attr_string
,
75
,
$this
->
_newline
.
' '
,
true
,
'utf-8'
,
true
);
}
$result
.=
$attr_string
.
$this
->
_newline
;
}
}
foreach
(
$this
->
_components
as
$component
)
{
$result
.=
$component
->
exportvCalendar
();
}
return
$result
.
'END:'
.
$base
.
$this
->
_newline
;
}
/**
* Parse a UTC Offset field.
*/
function
_parseUtcOffset
(
$text
)
{
$offset
=
array
();
if
(
preg_match
(
'/(
\+
|-)([0-9]{2})([0-9]{2})([0-9]{2})?/'
,
$text
,
$timeParts
))
{
$offset
[
'ahead'
]
=
(
bool
)(
$timeParts
[
1
]
==
'+'
);
$offset
[
'hour'
]
=
intval
(
$timeParts
[
2
]);
$offset
[
'minute'
]
=
intval
(
$timeParts
[
3
]);
if
(
isset
(
$timeParts
[
4
]))
{
$offset
[
'second'
]
=
intval
(
$timeParts
[
4
]);
}
return
$offset
;
}
else
{
return
false
;
}
}
/**
* Export a UTC Offset field.
*/
function
_exportUtcOffset
(
$value
)
{
$offset
=
$value
[
'ahead'
]
?
'+'
:
'-'
;
$offset
.=
sprintf
(
'%02d%02d'
,
$value
[
'hour'
],
$value
[
'minute'
]);
if
(
isset
(
$value
[
'second'
]))
{
$offset
.=
sprintf
(
'%02d'
,
$value
[
'second'
]);
}
return
$offset
;
}
/**
* Parse a Time Period field.
*/
function
_parsePeriod
(
$text
)
{
$periodParts
=
explode
(
'/'
,
$text
);
$start
=
$this
->
_parseDateTime
(
$periodParts
[
0
]);
if
(
$duration
=
$this
->
_parseDuration
(
$periodParts
[
1
]))
{
return
array
(
'start'
=>
$start
,
'duration'
=>
$duration
);
}
elseif
(
$end
=
$this
->
_parseDateTime
(
$periodParts
[
1
]))
{
return
array
(
'start'
=>
$start
,
'end'
=>
$end
);
}
}
/**
* Export a Time Period field.
*/
function
_exportPeriod
(
$value
)
{
$period
=
$this
->
_exportDateTime
(
$value
[
'start'
]);
$period
.=
'/'
;
if
(
isset
(
$value
[
'duration'
]))
{
$period
.=
$this
->
_exportDuration
(
$value
[
'duration'
]);
}
else
{
$period
.=
$this
->
_exportDateTime
(
$value
[
'end'
]);
}
return
$period
;
}
/**
* Grok the TZID and return an offset in seconds from UTC for this
* date and time.
*/
function
_parseTZID
(
$date
,
$time
,
$tzid
)
{
$vtimezone
=
$this
->
_container
->
findComponentByAttribute
(
'vtimezone'
,
'TZID'
,
$tzid
);
if
(!
$vtimezone
)
{
return
false
;
}
$change_times
=
array
();
foreach
(
$vtimezone
->
getComponents
()
as
$o
)
{
$t
=
$vtimezone
->
parseChild
(
$o
,
$date
[
'year'
]);
if
(
$t
!==
false
)
{
$change_times
[]
=
$t
;
}
}
if
(!
$change_times
)
{
return
false
;
}
sort
(
$change_times
);
// Time is arbitrarily based on UTC for comparison.
$t
=
@
gmmktime
(
$time
[
'hour'
],
$time
[
'minute'
],
$time
[
'second'
],
$date
[
'month'
],
$date
[
'mday'
],
$date
[
'year'
]);
if
(
$t
<
$change_times
[
0
][
'time'
])
{
return
$change_times
[
0
][
'from'
];
}
for
(
$i
=
0
,
$n
=
count
(
$change_times
);
$i
<
$n
-
1
;
$i
++)
{
if
((
$t
>=
$change_times
[
$i
][
'time'
])
&&
(
$t
<
$change_times
[
$i
+
1
][
'time'
]))
{
return
$change_times
[
$i
][
'to'
];
}
}
if
(
$t
>=
$change_times
[
$n
-
1
][
'time'
])
{
return
$change_times
[
$n
-
1
][
'to'
];
}
return
false
;
}
/**
* Parses a DateTime field and returns a unix timestamp. If the
* field cannot be parsed then the original text is returned
* unmodified.
*
* @todo This function should be moved to Horde_Date and made public.
*/
function
_parseDateTime
(
$text
,
$tzid
=
false
)
{
$dateParts
=
explode
(
'T'
,
$text
);
if
(
count
(
$dateParts
)
!=
2
&&
!
empty
(
$text
))
{
// Not a datetime field but may be just a date field.
if
(!
preg_match
(
'/^(
\d
{4})-?(
\d
{2})-?(
\d
{2})$/'
,
$text
,
$match
))
{
// Or not
return
$text
;
}
$newtext
=
$text
.
'T000000'
;
$dateParts
=
explode
(
'T'
,
$newtext
);
}
if
(!
$date
=
Horde_iCalendar
::
_parseDate
(
$dateParts
[
0
]))
{
return
$text
;
}
if
(!
$time
=
Horde_iCalendar
::
_parseTime
(
$dateParts
[
1
]))
{
return
$text
;
}
// Get timezone info for date fields from $tzid and container.
$tzoffset
=
(
$time
[
'zone'
]
==
'Local'
&&
$tzid
&&
is_a
(
$this
->
_container
,
'Horde_iCalendar'
))
?
$this
->
_parseTZID
(
$date
,
$time
,
$tzid
)
:
false
;
if
(
$time
[
'zone'
]
==
'UTC'
||
$tzoffset
!==
false
)
{
$result
=
@
gmmktime
(
$time
[
'hour'
],
$time
[
'minute'
],
$time
[
'second'
],
$date
[
'month'
],
$date
[
'mday'
],
$date
[
'year'
]);
if
(
$tzoffset
)
{
$result
-=
$tzoffset
;
}
}
else
{
// We don't know the timezone so assume local timezone.
// FIXME: shouldn't this be based on the user's timezone
// preference rather than the server's timezone?
$result
=
@
mktime
(
$time
[
'hour'
],
$time
[
'minute'
],
$time
[
'second'
],
$date
[
'month'
],
$date
[
'mday'
],
$date
[
'year'
]);
}
return
(
$result
!==
false
)
?
$result
:
$text
;
}
/**
* Export a DateTime field.
*/
function
_exportDateTime
(
$value
)
{
$temp
=
array
();
if
(!
is_object
(
$value
)
&&
!
is_array
(
$value
))
{
$tz
=
date
(
'O'
,
$value
);
$TZOffset
=
(
3600
*
substr
(
$tz
,
0
,
3
))
+
(
60
*
substr
(
$tz
,
3
,
2
));
$value
-=
$TZOffset
;
$temp
[
'zone'
]
=
'UTC'
;
list
(
$temp
[
'year'
],
$temp
[
'month'
],
$temp
[
'mday'
],
$temp
[
'hour'
],
$temp
[
'minute'
],
$temp
[
'second'
])
=
explode
(
'-'
,
date
(
'Y-n-j-G-i-s'
,
$value
));
}
else
{
$dateOb
=
new
Horde_Date
(
$value
);
return
Horde_iCalendar
::
_exportDateTime
(
$dateOb
->
timestamp
());
}
return
Horde_iCalendar
::
_exportDate
(
$temp
)
.
'T'
.
Horde_iCalendar
::
_exportTime
(
$temp
);
}
/**
* Parses a Time field.
*
* @static
*/
function
_parseTime
(
$text
)
{
if
(
preg_match
(
'/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/'
,
$text
,
$timeParts
))
{
$time
[
'hour'
]
=
intval
(
$timeParts
[
1
]);
$time
[
'minute'
]
=
intval
(
$timeParts
[
2
]);
$time
[
'second'
]
=
intval
(
$timeParts
[
3
]);
if
(
isset
(
$timeParts
[
4
]))
{
$time
[
'zone'
]
=
'UTC'
;
}
else
{
$time
[
'zone'
]
=
'Local'
;
}
return
$time
;
}
else
{
return
false
;
}
}
/**
* Exports a Time field.
*/
function
_exportTime
(
$value
)
{
$time
=
sprintf
(
'%02d%02d%02d'
,
$value
[
'hour'
],
$value
[
'minute'
],
$value
[
'second'
]);
if
(
$value
[
'zone'
]
==
'UTC'
)
{
$time
.=
'Z'
;
}
return
$time
;
}
/**
* Parses a Date field.
*
* @static
*/
function
_parseDate
(
$text
)
{
$parts
=
explode
(
'T'
,
$text
);
if
(
count
(
$parts
)
==
2
)
{
$text
=
$parts
[
0
];
}
if
(!
preg_match
(
'/^(
\d
{4})-?(
\d
{2})-?(
\d
{2})$/'
,
$text
,
$match
))
{
return
false
;
}
return
array
(
'year'
=>
$match
[
1
],
'month'
=>
$match
[
2
],
'mday'
=>
$match
[
3
]);
}
/**
* Exports a date field.
*
* @param object|array $value Date object or hash.
* @param string $autoconvert If set, use this as time part to export the
* date as datetime when exporting to Vcalendar
* 1.0. Examples: '000000' or '235959'
*/
function
_exportDate
(
$value
,
$autoconvert
=
false
)
{
if
(
is_object
(
$value
))
{
$value
=
array
(
'year'
=>
$value
->
year
,
'month'
=>
$value
->
month
,
'mday'
=>
$value
->
mday
);
}
if
(
$autoconvert
!==
false
&&
$this
->
isOldFormat
())
{
return
sprintf
(
'%04d%02d%02dT%s'
,
$value
[
'year'
],
$value
[
'month'
],
$value
[
'mday'
],
$autoconvert
);
}
else
{
return
sprintf
(
'%04d%02d%02d'
,
$value
[
'year'
],
$value
[
'month'
],
$value
[
'mday'
]);
}
}
/**
* Parse a Duration Value field.
*/
function
_parseDuration
(
$text
)
{
if
(
preg_match
(
'/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/'
,
trim
(
$text
),
$durvalue
))
{
// Weeks.
$duration
=
7
*
86400
*
intval
(
$durvalue
[
3
]);
if
(
count
(
$durvalue
)
>
4
)
{
// Days.
$duration
+=
86400
*
intval
(
$durvalue
[
4
]);
}
if
(
count
(
$durvalue
)
>
5
)
{
// Hours.
$duration
+=
3600
*
intval
(
$durvalue
[
7
]);
// Mins.
if
(
isset
(
$durvalue
[
8
]))
{
$duration
+=
60
*
intval
(
$durvalue
[
8
]);
}
// Secs.
if
(
isset
(
$durvalue
[
9
]))
{
$duration
+=
intval
(
$durvalue
[
9
]);
}
}
// Sign.
if
(
$durvalue
[
1
]
==
"-"
)
{
$duration
*=
-
1
;
}
return
$duration
;
}
else
{
return
false
;
}
}
/**
* Export a duration value.
*/
function
_exportDuration
(
$value
)
{
$duration
=
''
;
if
(
$value
<
0
)
{
$value
*=
-
1
;
$duration
.=
'-'
;
}
$duration
.=
'P'
;
$weeks
=
floor
(
$value
/
(
7
*
86400
));
$value
=
$value
%
(
7
*
86400
);
if
(
$weeks
)
{
$duration
.=
$weeks
.
'W'
;
}
$days
=
floor
(
$value
/
(
86400
));
$value
=
$value
%
(
86400
);
if
(
$days
)
{
$duration
.=
$days
.
'D'
;
}
if
(
$value
)
{
$duration
.=
'T'
;
$hours
=
floor
(
$value
/
3600
);
$value
=
$value
%
3600
;
if
(
$hours
)
{
$duration
.=
$hours
.
'H'
;
}
$mins
=
floor
(
$value
/
60
);
$value
=
$value
%
60
;
if
(
$mins
)
{
$duration
.=
$mins
.
'M'
;
}
if
(
$value
)
{
$duration
.=
$value
.
'S'
;
}
}
return
$duration
;
}
/**
* Converts an 8bit string to a quoted-printable string according to RFC
* 2045, section 6.7.
*
* imap_8bit() does not apply all necessary rules.
*
* @param string $input The string to be encoded.
*
* @return string The quoted-printable encoded string.
*/
function
_quotedPrintableEncode
(
$input
=
''
)
{
$output
=
$line
=
''
;
$len
=
strlen
(
$input
);
for
(
$i
=
0
;
$i
<
$len
;
++
$i
)
{
$ord
=
ord
(
$input
[
$i
]);
// Encode non-printable characters (rule 2).
if
(
$ord
==
9
||
(
$ord
>=
32
&&
$ord
<=
60
)
||
(
$ord
>=
62
&&
$ord
<=
126
))
{
$chunk
=
$input
[
$i
];
}
else
{
// Quoted printable encoding (rule 1).
$chunk
=
'='
.
String
::
upper
(
sprintf
(
'%02X'
,
$ord
));
}
$line
.=
$chunk
;
// Wrap long lines (rule 5)
if
(
strlen
(
$line
)
+
1
>
76
)
{
$line
=
String
::
wordwrap
(
$line
,
75
,
"=
\r\n
"
,
true
,
'us-ascii'
,
true
);
$newline
=
strrchr
(
$line
,
"
\r\n
"
);
if
(
$newline
!==
false
)
{
$output
.=
substr
(
$line
,
0
,
-
strlen
(
$newline
)
+
2
);
$line
=
substr
(
$newline
,
2
);
}
else
{
$output
.=
$line
;
}
continue
;
}
// Wrap at line breaks for better readability (rule 4).
if
(
substr
(
$line
,
-
3
)
==
'=0A'
)
{
$output
.=
$line
.
"=
\r\n
"
;
$line
=
''
;
}
}
$output
.=
$line
;
// Trailing whitespace must be encoded (rule 3).
$lastpos
=
strlen
(
$output
)
-
1
;
if
(
$output
[
$lastpos
]
==
chr
(
9
)
||
$output
[
$lastpos
]
==
chr
(
32
))
{
$output
[
$lastpos
]
=
'='
;
$output
.=
String
::
upper
(
sprintf
(
'%02X'
,
ord
(
$output
[
$lastpos
])));
}
return
$output
;
}
}
/**
* Class representing vAlarms.
*
* $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8.10.9 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_valarm
extends
Horde_iCalendar
{
function
getType
()
{
return
'vAlarm'
;
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'VALARM'
);
}
}
/**
* Class representing vEvents.
*
* $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31.10.16 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vevent
extends
Horde_iCalendar
{
function
getType
()
{
return
'vEvent'
;
}
function
exportvCalendar
()
{
// Default values.
$requiredAttributes
=
array
();
$requiredAttributes
[
'DTSTAMP'
]
=
time
();
$requiredAttributes
[
'UID'
]
=
$this
->
_exportDateTime
(
time
())
.
substr
(
str_pad
(
base_convert
(
microtime
(),
10
,
36
),
16
,
uniqid
(
mt_rand
()),
STR_PAD_LEFT
),
-
16
)
.
'@'
.
(
isset
(
$_SERVER
[
'SERVER_NAME'
])
?
$_SERVER
[
'SERVER_NAME'
]
:
'localhost'
);
$method
=
!
empty
(
$this
->
_container
)
?
$this
->
_container
->
getAttribute
(
'METHOD'
)
:
'PUBLISH'
;
switch
(
$method
)
{
case
'PUBLISH'
:
$requiredAttributes
[
'DTSTART'
]
=
time
();
$requiredAttributes
[
'SUMMARY'
]
=
''
;
break
;
case
'REQUEST'
:
$requiredAttributes
[
'ATTENDEE'
]
=
''
;
$requiredAttributes
[
'DTSTART'
]
=
time
();
$requiredAttributes
[
'SUMMARY'
]
=
''
;
break
;
case
'REPLY'
:
$requiredAttributes
[
'ATTENDEE'
]
=
''
;
break
;
case
'ADD'
:
$requiredAttributes
[
'DTSTART'
]
=
time
();
$requiredAttributes
[
'SEQUENCE'
]
=
1
;
$requiredAttributes
[
'SUMMARY'
]
=
''
;
break
;
case
'CANCEL'
:
$requiredAttributes
[
'ATTENDEE'
]
=
''
;
$requiredAttributes
[
'SEQUENCE'
]
=
1
;
break
;
case
'REFRESH'
:
$requiredAttributes
[
'ATTENDEE'
]
=
''
;
break
;
}
foreach
(
$requiredAttributes
as
$name
=>
$default_value
)
{
if
(
is_a
(
$this
->
getAttribute
(
$name
),
'PEAR_Error'
))
{
$this
->
setAttribute
(
$name
,
$default_value
);
}
}
return
parent
::
_exportvData
(
'VEVENT'
);
}
/**
* Update the status of an attendee of an event.
*
* @param $email The email address of the attendee.
* @param $status The participant status to set.
* @param $fullname The full name of the participant to set.
*/
function
updateAttendee
(
$email
,
$status
,
$fullname
=
''
)
{
foreach
(
$this
->
_attributes
as
$key
=>
$attribute
)
{
if
(
$attribute
[
'name'
]
==
'ATTENDEE'
&&
$attribute
[
'value'
]
==
'mailto:'
.
$email
)
{
$this
->
_attributes
[
$key
][
'params'
][
'PARTSTAT'
]
=
$status
;
if
(!
empty
(
$fullname
))
{
$this
->
_attributes
[
$key
][
'params'
][
'CN'
]
=
$fullname
;
}
unset
(
$this
->
_attributes
[
$key
][
'params'
][
'RSVP'
]);
return
;
}
}
$params
=
array
(
'PARTSTAT'
=>
$status
);
if
(!
empty
(
$fullname
))
{
$params
[
'CN'
]
=
$fullname
;
}
$this
->
setAttribute
(
'ATTENDEE'
,
'mailto:'
.
$email
,
$params
);
}
/**
* Return the organizer display name or email.
*
* @return string The organizer name to display for this event.
*/
function
organizerName
()
{
$organizer
=
$this
->
getAttribute
(
'ORGANIZER'
,
true
);
if
(
is_a
(
$organizer
,
'PEAR_Error'
))
{
return
_
(
"An unknown person"
);
}
if
(
isset
(
$organizer
[
0
][
'CN'
]))
{
return
$organizer
[
0
][
'CN'
];
}
$organizer
=
parse_url
(
$this
->
getAttribute
(
'ORGANIZER'
));
return
$organizer
[
'path'
];
}
/**
* Update this event with details from another event.
*
* @param Horde_iCalendar_vEvent $vevent The vEvent with latest details.
*/
function
updateFromvEvent
(
$vevent
)
{
$newAttributes
=
$vevent
->
getAllAttributes
();
foreach
(
$newAttributes
as
$newAttribute
)
{
$currentValue
=
$this
->
getAttribute
(
$newAttribute
[
'name'
]);
if
(
is_a
(
$currentValue
,
'PEAR_error'
))
{
// Already exists so just add it.
$this
->
setAttribute
(
$newAttribute
[
'name'
],
$newAttribute
[
'value'
],
$newAttribute
[
'params'
]);
}
else
{
// Already exists so locate and modify.
$found
=
false
;
// Try matching the attribte name and value incase
// only the params changed (eg attendee updating
// status).
foreach
(
$this
->
_attributes
as
$id
=>
$attr
)
{
if
(
$attr
[
'name'
]
==
$newAttribute
[
'name'
]
&&
$attr
[
'value'
]
==
$newAttribute
[
'value'
])
{
// merge the params
foreach
(
$newAttribute
[
'params'
]
as
$param_id
=>
$param_name
)
{
$this
->
_attributes
[
$id
][
'params'
][
$param_id
]
=
$param_name
;
}
$found
=
true
;
break
;
}
}
if
(!
$found
)
{
// Else match the first attribute with the same
// name (eg changing start time).
foreach
(
$this
->
_attributes
as
$id
=>
$attr
)
{
if
(
$attr
[
'name'
]
==
$newAttribute
[
'name'
])
{
$this
->
_attributes
[
$id
][
'value'
]
=
$newAttribute
[
'value'
];
// Merge the params.
foreach
(
$newAttribute
[
'params'
]
as
$param_id
=>
$param_name
)
{
$this
->
_attributes
[
$id
][
'params'
][
$param_id
]
=
$param_name
;
}
break
;
}
}
}
}
}
}
/**
* Update just the attendess of event with details from another
* event.
*
* @param Horde_iCalendar_vEvent $vevent The vEvent with latest details
*/
function
updateAttendeesFromvEvent
(
$vevent
)
{
$newAttributes
=
$vevent
->
getAllAttributes
();
foreach
(
$newAttributes
as
$newAttribute
)
{
if
(
$newAttribute
[
'name'
]
!=
'ATTENDEE'
)
{
continue
;
}
$currentValue
=
$this
->
getAttribute
(
$newAttribute
[
'name'
]);
if
(
is_a
(
$currentValue
,
'PEAR_error'
))
{
// Already exists so just add it.
$this
->
setAttribute
(
$newAttribute
[
'name'
],
$newAttribute
[
'value'
],
$newAttribute
[
'params'
]);
}
else
{
// Already exists so locate and modify.
$found
=
false
;
// Try matching the attribte name and value incase
// only the params changed (eg attendee updating
// status).
foreach
(
$this
->
_attributes
as
$id
=>
$attr
)
{
if
(
$attr
[
'name'
]
==
$newAttribute
[
'name'
]
&&
$attr
[
'value'
]
==
$newAttribute
[
'value'
])
{
// Merge the params.
foreach
(
$newAttribute
[
'params'
]
as
$param_id
=>
$param_name
)
{
$this
->
_attributes
[
$id
][
'params'
][
$param_id
]
=
$param_name
;
}
$found
=
true
;
break
;
}
}
if
(!
$found
)
{
// Else match the first attribute with the same
// name (eg changing start time).
foreach
(
$this
->
_attributes
as
$id
=>
$attr
)
{
if
(
$attr
[
'name'
]
==
$newAttribute
[
'name'
])
{
$this
->
_attributes
[
$id
][
'value'
]
=
$newAttribute
[
'value'
];
// Merge the params.
foreach
(
$newAttribute
[
'params'
]
as
$param_id
=>
$param_name
)
{
$this
->
_attributes
[
$id
][
'params'
][
$param_id
]
=
$param_name
;
}
break
;
}
}
}
}
}
}
}
/**
* Class representing vFreebusy components.
*
* $Horde: framework/iCalendar/iCalendar/vfreebusy.php,v 1.16.10.18 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @todo Don't use timestamps
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vfreebusy
extends
Horde_iCalendar
{
var
$_busyPeriods
=
array
();
var
$_extraParams
=
array
();
/**
* Returns the type of this calendar component.
*
* @return string The type of this component.
*/
function
getType
()
{
return
'vFreebusy'
;
}
/**
* Parses a string containing vFreebusy data.
*
* @param string $data The data to parse.
*/
function
parsevCalendar
(
$data
,
$type
=
null
,
$charset
=
null
)
{
parent
::
parsevCalendar
(
$data
,
'VFREEBUSY'
,
$charset
);
// Do something with all the busy periods.
foreach
(
$this
->
_attributes
as
$key
=>
$attribute
)
{
if
(
$attribute
[
'name'
]
!=
'FREEBUSY'
)
{
continue
;
}
foreach
(
$attribute
[
'values'
]
as
$value
)
{
$params
=
isset
(
$attribute
[
'params'
])
?
$attribute
[
'params'
]
:
array
();
if
(
isset
(
$value
[
'duration'
]))
{
$this
->
addBusyPeriod
(
'BUSY'
,
$value
[
'start'
],
null
,
$value
[
'duration'
],
$params
);
}
else
{
$this
->
addBusyPeriod
(
'BUSY'
,
$value
[
'start'
],
$value
[
'end'
],
null
,
$params
);
}
}
unset
(
$this
->
_attributes
[
$key
]);
}
}
/**
* Returns the component exported as string.
*
* @return string The exported vFreeBusy information according to the
* iCalender format specification.
*/
function
exportvCalendar
()
{
foreach
(
$this
->
_busyPeriods
as
$start
=>
$end
)
{
$periods
=
array
(
array
(
'start'
=>
$start
,
'end'
=>
$end
));
$this
->
setAttribute
(
'FREEBUSY'
,
$periods
,
isset
(
$this
->
_extraParams
[
$start
])
?
$this
->
_extraParams
[
$start
]
:
array
());
}
$res
=
parent
::
_exportvData
(
'VFREEBUSY'
);
foreach
(
$this
->
_attributes
as
$key
=>
$attribute
)
{
if
(
$attribute
[
'name'
]
==
'FREEBUSY'
)
{
unset
(
$this
->
_attributes
[
$key
]);
}
}
return
$res
;
}
/**
* Returns a display name for this object.
*
* @return string A clear text name for displaying this object.
*/
function
getName
()
{
$name
=
''
;
$method
=
!
empty
(
$this
->
_container
)
?
$this
->
_container
->
getAttribute
(
'METHOD'
)
:
'PUBLISH'
;
if
(
is_a
(
$method
,
'PEAR_Error'
)
||
$method
==
'PUBLISH'
)
{
$attr
=
'ORGANIZER'
;
}
elseif
(
$method
==
'REPLY'
)
{
$attr
=
'ATTENDEE'
;
}
$name
=
$this
->
getAttribute
(
$attr
,
true
);
if
(!
is_a
(
$name
,
'PEAR_Error'
)
&&
isset
(
$name
[
0
][
'CN'
]))
{
return
$name
[
0
][
'CN'
];
}
$name
=
$this
->
getAttribute
(
$attr
);
if
(
is_a
(
$name
,
'PEAR_Error'
))
{
return
''
;
}
else
{
$name
=
parse_url
(
$name
);
return
$name
[
'path'
];
}
}
/**
* Returns the email address for this object.
*
* @return string The email address of this object's owner.
*/
function
getEmail
()
{
$name
=
''
;
$method
=
!
empty
(
$this
->
_container
)
?
$this
->
_container
->
getAttribute
(
'METHOD'
)
:
'PUBLISH'
;
if
(
is_a
(
$method
,
'PEAR_Error'
)
||
$method
==
'PUBLISH'
)
{
$attr
=
'ORGANIZER'
;
}
elseif
(
$method
==
'REPLY'
)
{
$attr
=
'ATTENDEE'
;
}
$name
=
$this
->
getAttribute
(
$attr
);
if
(
is_a
(
$name
,
'PEAR_Error'
))
{
return
''
;
}
else
{
$name
=
parse_url
(
$name
);
return
$name
[
'path'
];
}
}
/**
* Returns the busy periods.
*
* @return array All busy periods.
*/
function
getBusyPeriods
()
{
return
$this
->
_busyPeriods
;
}
/**
* Returns any additional freebusy parameters.
*
* @return array Additional parameters of the freebusy periods.
*/
function
getExtraParams
()
{
return
$this
->
_extraParams
;
}
/**
* Returns all the free periods of time in a given period.
*
* @param integer $startStamp The start timestamp.
* @param integer $endStamp The end timestamp.
*
* @return array A hash with free time periods, the start times as the
* keys and the end times as the values.
*/
function
getFreePeriods
(
$startStamp
,
$endStamp
)
{
$this
->
simplify
();
$periods
=
array
();
// Check that we have data for some part of this period.
if
(
$this
->
getEnd
()
<
$startStamp
||
$this
->
getStart
()
>
$endStamp
)
{
return
$periods
;
}
// Locate the first time in the requested period we have data for.
$nextstart
=
max
(
$startStamp
,
$this
->
getStart
());
// Check each busy period and add free periods in between.
foreach
(
$this
->
_busyPeriods
as
$start
=>
$end
)
{
if
(
$start
<=
$endStamp
&&
$end
>=
$nextstart
)
{
if
(
$nextstart
<=
$start
)
{
$periods
[
$nextstart
]
=
min
(
$start
,
$endStamp
);
}
$nextstart
=
min
(
$end
,
$endStamp
);
}
}
// If we didn't read the end of the requested period but still have
// data then mark as free to the end of the period or available data.
if
(
$nextstart
<
$endStamp
&&
$nextstart
<
$this
->
getEnd
())
{
$periods
[
$nextstart
]
=
min
(
$this
->
getEnd
(),
$endStamp
);
}
return
$periods
;
}
/**
* Adds a busy period to the info.
*
* This function may throw away data in case you add a period with a start
* date that already exists. The longer of the two periods will be chosen
* (and all information associated with the shorter one will be removed).
*
* @param string $type The type of the period. Either 'FREE' or
* 'BUSY'; only 'BUSY' supported at the moment.
* @param integer $start The start timestamp of the period.
* @param integer $end The end timestamp of the period.
* @param integer $duration The duration of the period. If specified, the
* $end parameter will be ignored.
* @param array $extra Additional parameters for this busy period.
*/
function
addBusyPeriod
(
$type
,
$start
,
$end
=
null
,
$duration
=
null
,
$extra
=
array
())
{
if
(
$type
==
'FREE'
)
{
// Make sure this period is not marked as busy.
return
false
;
}
// Calculate the end time if duration was specified.
$tempEnd
=
is_null
(
$duration
)
?
$end
:
$start
+
$duration
;
// Make sure the period length is always positive.
$end
=
max
(
$start
,
$tempEnd
);
$start
=
min
(
$start
,
$tempEnd
);
if
(
isset
(
$this
->
_busyPeriods
[
$start
]))
{
// Already a period starting at this time. Change the current
// period only if the new one is longer. This might be a problem
// if the callee assumes that there is no simplification going
// on. But since the periods are stored using the start time of
// the busy periods we have to throw away data here.
if
(
$end
>
$this
->
_busyPeriods
[
$start
])
{
$this
->
_busyPeriods
[
$start
]
=
$end
;
$this
->
_extraParams
[
$start
]
=
$extra
;
}
}
else
{
// Add a new busy period.
$this
->
_busyPeriods
[
$start
]
=
$end
;
$this
->
_extraParams
[
$start
]
=
$extra
;
}
return
true
;
}
/**
* Returns the timestamp of the start of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function
getStart
()
{
if
(!
is_a
(
$this
->
getAttribute
(
'DTSTART'
),
'PEAR_Error'
))
{
return
$this
->
getAttribute
(
'DTSTART'
);
}
elseif
(
count
(
$this
->
_busyPeriods
))
{
return
min
(
array_keys
(
$this
->
_busyPeriods
));
}
else
{
return
false
;
}
}
/**
* Returns the timestamp of the end of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function
getEnd
()
{
if
(!
is_a
(
$this
->
getAttribute
(
'DTEND'
),
'PEAR_Error'
))
{
return
$this
->
getAttribute
(
'DTEND'
);
}
elseif
(
count
(
$this
->
_busyPeriods
))
{
return
max
(
array_values
(
$this
->
_busyPeriods
));
}
else
{
return
false
;
}
}
/**
* Merges the busy periods of another Horde_iCalendar_vfreebusy object
* into this one.
*
* This might lead to simplification no matter what you specify for the
* "simplify" flag since periods with the same start date will lead to the
* shorter period being removed (see addBusyPeriod).
*
* @param Horde_iCalendar_vfreebusy $freebusy A freebusy object.
* @param boolean $simplify If true, simplify() will
* called after the merge.
*/
function
merge
(
$freebusy
,
$simplify
=
true
)
{
if
(!
is_a
(
$freebusy
,
'Horde_iCalendar_vfreebusy'
))
{
return
false
;
}
$extra
=
$freebusy
->
getExtraParams
();
foreach
(
$freebusy
->
getBusyPeriods
()
as
$start
=>
$end
)
{
// This might simplify the busy periods without taking the
// "simplify" flag into account.
$this
->
addBusyPeriod
(
'BUSY'
,
$start
,
$end
,
null
,
isset
(
$extra
[
$start
])
?
$extra
[
$start
]
:
array
());
}
$thisattr
=
$this
->
getAttribute
(
'DTSTART'
);
$thatattr
=
$freebusy
->
getAttribute
(
'DTSTART'
);
if
(
is_a
(
$thisattr
,
'PEAR_Error'
)
&&
!
is_a
(
$thatattr
,
'PEAR_Error'
))
{
$this
->
setAttribute
(
'DTSTART'
,
$thatattr
,
array
(),
false
);
}
elseif
(!
is_a
(
$thatattr
,
'PEAR_Error'
))
{
if
(
$thatattr
<
$thisattr
)
{
$this
->
setAttribute
(
'DTSTART'
,
$thatattr
,
array
(),
false
);
}
}
$thisattr
=
$this
->
getAttribute
(
'DTEND'
);
$thatattr
=
$freebusy
->
getAttribute
(
'DTEND'
);
if
(
is_a
(
$thisattr
,
'PEAR_Error'
)
&&
!
is_a
(
$thatattr
,
'PEAR_Error'
))
{
$this
->
setAttribute
(
'DTEND'
,
$thatattr
,
array
(),
false
);
}
elseif
(!
is_a
(
$thatattr
,
'PEAR_Error'
))
{
if
(
$thatattr
>
$thisattr
)
{
$this
->
setAttribute
(
'DTEND'
,
$thatattr
,
array
(),
false
);
}
}
if
(
$simplify
)
{
$this
->
simplify
();
}
return
true
;
}
/**
* Removes all overlaps and simplifies the busy periods array as much as
* possible.
*/
function
simplify
()
{
$clean
=
false
;
$busy
=
array
(
$this
->
_busyPeriods
,
$this
->
_extraParams
);
while
(!
$clean
)
{
$result
=
$this
->
_simplify
(
$busy
[
0
],
$busy
[
1
]);
$clean
=
$result
===
$busy
;
$busy
=
$result
;
}
ksort
(
$result
[
1
],
SORT_NUMERIC
);
$this
->
_extraParams
=
$result
[
1
];
ksort
(
$result
[
0
],
SORT_NUMERIC
);
$this
->
_busyPeriods
=
$result
[
0
];
}
function
_simplify
(
$busyPeriods
,
$extraParams
=
array
())
{
$checked
=
array
();
$checkedExtra
=
array
();
$checkedEmpty
=
true
;
foreach
(
$busyPeriods
as
$start
=>
$end
)
{
if
(
$checkedEmpty
)
{
$checked
[
$start
]
=
$end
;
$checkedExtra
[
$start
]
=
isset
(
$extraParams
[
$start
])
?
$extraParams
[
$start
]
:
array
();
$checkedEmpty
=
false
;
}
else
{
$added
=
false
;
foreach
(
$checked
as
$testStart
=>
$testEnd
)
{
// Replace old period if the new period lies around the
// old period.
if
(
$start
<=
$testStart
&&
$end
>=
$testEnd
)
{
// Remove old period entry.
unset
(
$checked
[
$testStart
]);
unset
(
$checkedExtra
[
$testStart
]);
// Add replacing entry.
$checked
[
$start
]
=
$end
;
$checkedExtra
[
$start
]
=
isset
(
$extraParams
[
$start
])
?
$extraParams
[
$start
]
:
array
();
$added
=
true
;
}
elseif
(
$start
>=
$testStart
&&
$end
<=
$testEnd
)
{
// The new period lies fully within the old
// period. Just forget about it.
$added
=
true
;
}
elseif
((
$end
<=
$testEnd
&&
$end
>=
$testStart
)
||
(
$start
>=
$testStart
&&
$start
<=
$testEnd
))
{
// Now we are in trouble: Overlapping time periods. If
// we allow for additional parameters we cannot simply
// choose one of the two parameter sets. It's better
// to leave two separated time periods.
$extra
=
isset
(
$extraParams
[
$start
])
?
$extraParams
[
$start
]
:
array
();
$testExtra
=
isset
(
$checkedExtra
[
$testStart
])
?
$checkedExtra
[
$testStart
]
:
array
();
// Remove old period entry.
unset
(
$checked
[
$testStart
]);
unset
(
$checkedExtra
[
$testStart
]);
// We have two periods overlapping. Are their
// additional parameters the same or different?
$newStart
=
min
(
$start
,
$testStart
);
$newEnd
=
max
(
$end
,
$testEnd
);
if
(
$extra
===
$testExtra
)
{
// Both periods have the same information. So we
// can just merge.
$checked
[
$newStart
]
=
$newEnd
;
$checkedExtra
[
$newStart
]
=
$extra
;
}
else
{
// Extra parameters are different. Create one
// period at the beginning with the params of the
// first period and create a trailing period with
// the params of the second period. The break
// point will be the end of the first period.
$break
=
min
(
$end
,
$testEnd
);
$checked
[
$newStart
]
=
$break
;
$checkedExtra
[
$newStart
]
=
isset
(
$extraParams
[
$newStart
])
?
$extraParams
[
$newStart
]
:
array
();
$checked
[
$break
]
=
$newEnd
;
$highStart
=
max
(
$start
,
$testStart
);
$checkedExtra
[
$break
]
=
isset
(
$extraParams
[
$highStart
])
?
$extraParams
[
$highStart
]
:
array
();
// Ensure we also have the extra data in the
// extraParams.
$extraParams
[
$break
]
=
isset
(
$extraParams
[
$highStart
])
?
$extraParams
[
$highStart
]
:
array
();
}
$added
=
true
;
}
if
(
$added
)
{
break
;
}
}
if
(!
$added
)
{
$checked
[
$start
]
=
$end
;
$checkedExtra
[
$start
]
=
isset
(
$extraParams
[
$start
])
?
$extraParams
[
$start
]
:
array
();
}
}
}
return
array
(
$checked
,
$checkedExtra
);
}
}
/**
* Class representing vJournals.
*
* $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8.10.9 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vjournal
extends
Horde_iCalendar
{
function
getType
()
{
return
'vJournal'
;
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'VJOURNAL'
);
}
}
/**
* Class representing vNotes.
*
* $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.3.10.10 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @author Karsten Fourmont <fourmont@gmx.de>
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vnote
extends
Horde_iCalendar
{
function
Horde_iCalendar_vnote
(
$version
=
'1.1'
)
{
return
parent
::
Horde_iCalendar
(
$version
);
}
function
getType
()
{
return
'vNote'
;
}
/**
* Unlike vevent and vtodo, a vnote is normally not enclosed in an
* iCalendar container. (BEGIN..END)
*/
function
exportvCalendar
()
{
$requiredAttributes
[
'BODY'
]
=
''
;
$requiredAttributes
[
'VERSION'
]
=
'1.1'
;
foreach
(
$requiredAttributes
as
$name
=>
$default_value
)
{
if
(
is_a
(
$this
->
getattribute
(
$name
),
'PEAR_Error'
))
{
$this
->
setAttribute
(
$name
,
$default_value
);
}
}
return
$this
->
_exportvData
(
'VNOTE'
);
}
}
/**
* Class representing vTimezones.
*
* $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8.10.10 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vtimezone
extends
Horde_iCalendar
{
function
getType
()
{
return
'vTimeZone'
;
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'VTIMEZONE'
);
}
/**
* Parse child components of the vTimezone component. Returns an
* array with the exact time of the time change as well as the
* 'from' and 'to' offsets around the change. Time is arbitrarily
* based on UTC for comparison.
*/
function
parseChild
(&
$child
,
$year
)
{
// Make sure 'time' key is first for sort().
$result
[
'time'
]
=
0
;
$t
=
$child
->
getAttribute
(
'TZOFFSETFROM'
);
if
(
is_a
(
$t
,
'PEAR_Error'
))
{
return
false
;
}
$result
[
'from'
]
=
(
$t
[
'hour'
]
*
60
*
60
+
$t
[
'minute'
]
*
60
)
*
(
$t
[
'ahead'
]
?
1
:
-
1
);
$t
=
$child
->
getAttribute
(
'TZOFFSETTO'
);
if
(
is_a
(
$t
,
'PEAR_Error'
))
{
return
false
;
}
$result
[
'to'
]
=
(
$t
[
'hour'
]
*
60
*
60
+
$t
[
'minute'
]
*
60
)
*
(
$t
[
'ahead'
]
?
1
:
-
1
);
$switch_time
=
$child
->
getAttribute
(
'DTSTART'
);
if
(
is_a
(
$switch_time
,
'PEAR_Error'
))
{
return
false
;
}
$rrules
=
$child
->
getAttribute
(
'RRULE'
);
if
(
is_a
(
$rrules
,
'PEAR_Error'
))
{
if
(!
is_int
(
$switch_time
))
{
return
false
;
}
// Convert this timestamp from local time to UTC for
// comparison (All dates are compared as if they are UTC).
$t
=
getdate
(
$switch_time
);
$result
[
'time'
]
=
@
gmmktime
(
$t
[
'hours'
],
$t
[
'minutes'
],
$t
[
'seconds'
],
$t
[
'mon'
],
$t
[
'mday'
],
$t
[
'year'
]);
return
$result
;
}
$rrules
=
explode
(
';'
,
$rrules
);
foreach
(
$rrules
as
$rrule
)
{
$t
=
explode
(
'='
,
$rrule
);
switch
(
$t
[
0
])
{
case
'FREQ'
:
if
(
$t
[
1
]
!=
'YEARLY'
)
{
return
false
;
}
break
;
case
'INTERVAL'
:
if
(
$t
[
1
]
!=
'1'
)
{
return
false
;
}
break
;
case
'BYMONTH'
:
$month
=
intval
(
$t
[
1
]);
break
;
case
'BYDAY'
:
$len
=
strspn
(
$t
[
1
],
'1234567890-+'
);
if
(
$len
==
0
)
{
return
false
;
}
$weekday
=
substr
(
$t
[
1
],
$len
);
$weekdays
=
array
(
'SU'
=>
0
,
'MO'
=>
1
,
'TU'
=>
2
,
'WE'
=>
3
,
'TH'
=>
4
,
'FR'
=>
5
,
'SA'
=>
6
);
$weekday
=
$weekdays
[
$weekday
];
$which
=
intval
(
substr
(
$t
[
1
],
0
,
$len
));
break
;
case
'UNTIL'
:
if
(
intval
(
$year
)
>
intval
(
substr
(
$t
[
1
],
0
,
4
)))
{
return
false
;
}
break
;
}
}
if
(
empty
(
$month
)
||
!
isset
(
$weekday
))
{
return
false
;
}
if
(
is_int
(
$switch_time
))
{
// Was stored as localtime.
$switch_time
=
strftime
(
'%H:%M:%S'
,
$switch_time
);
$switch_time
=
explode
(
':'
,
$switch_time
);
}
else
{
$switch_time
=
explode
(
'T'
,
$switch_time
);
if
(
count
(
$switch_time
)
!=
2
)
{
return
false
;
}
$switch_time
[
0
]
=
substr
(
$switch_time
[
1
],
0
,
2
);
$switch_time
[
2
]
=
substr
(
$switch_time
[
1
],
4
,
2
);
$switch_time
[
1
]
=
substr
(
$switch_time
[
1
],
2
,
2
);
}
// Get the timestamp for the first day of $month.
$when
=
gmmktime
(
$switch_time
[
0
],
$switch_time
[
1
],
$switch_time
[
2
],
$month
,
1
,
$year
);
// Get the day of the week for the first day of $month.
$first_of_month_weekday
=
intval
(
gmstrftime
(
'%w'
,
$when
));
// Go to the first $weekday before first day of $month.
if
(
$weekday
>=
$first_of_month_weekday
)
{
$weekday
-=
7
;
}
$when
-=
(
$first_of_month_weekday
-
$weekday
)
*
60
*
60
*
24
;
// If going backwards go to the first $weekday after last day
// of $month.
if
(
$which
<
0
)
{
do
{
$when
+=
60
*
60
*
24
*
7
;
}
while
(
intval
(
gmstrftime
(
'%m'
,
$when
))
==
$month
);
}
// Calculate $weekday number $which.
$when
+=
$which
*
60
*
60
*
24
*
7
;
$result
[
'time'
]
=
$when
;
return
$result
;
}
}
/**
* @package Horde_iCalendar
*/
class
Horde_iCalendar_standard
extends
Horde_iCalendar
{
function
getType
()
{
return
'standard'
;
}
function
parsevCalendar
(
$data
)
{
parent
::
parsevCalendar
(
$data
,
'STANDARD'
);
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'STANDARD'
);
}
}
/**
* @package Horde_iCalendar
*/
class
Horde_iCalendar_daylight
extends
Horde_iCalendar
{
function
getType
()
{
return
'daylight'
;
}
function
parsevCalendar
(
$data
)
{
parent
::
parsevCalendar
(
$data
,
'DAYLIGHT'
);
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'DAYLIGHT'
);
}
}
/**
* Class representing vTodos.
*
* $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13.10.9 2009-01-06 15:23:53 jan Exp $
*
* Copyright 2003-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class
Horde_iCalendar_vtodo
extends
Horde_iCalendar
{
function
getType
()
{
return
'vTodo'
;
}
function
exportvCalendar
()
{
return
parent
::
_exportvData
(
'VTODO'
);
}
/**
* Convert this todo to an array of attributes.
*
* @return array Array containing the details of the todo in a hash
* as used by Horde applications.
*/
function
toArray
()
{
$todo
=
array
();
$name
=
$this
->
getAttribute
(
'SUMMARY'
);
if
(!
is_array
(
$name
)
&&
!
is_a
(
$name
,
'PEAR_Error'
))
{
$todo
[
'name'
]
=
$name
;
}
$desc
=
$this
->
getAttribute
(
'DESCRIPTION'
);
if
(!
is_array
(
$desc
)
&&
!
is_a
(
$desc
,
'PEAR_Error'
))
{
$todo
[
'desc'
]
=
$desc
;
}
$priority
=
$this
->
getAttribute
(
'PRIORITY'
);
if
(!
is_array
(
$priority
)
&&
!
is_a
(
$priority
,
'PEAR_Error'
))
{
$todo
[
'priority'
]
=
$priority
;
}
$due
=
$this
->
getAttribute
(
'DTSTAMP'
);
if
(!
is_array
(
$due
)
&&
!
is_a
(
$due
,
'PEAR_Error'
))
{
$todo
[
'due'
]
=
$due
;
}
return
$todo
;
}
/**
* Set the attributes for this todo item from an array.
*
* @param array $todo Array containing the details of the todo in
* the same format that toArray() exports.
*/
function
fromArray
(
$todo
)
{
if
(
isset
(
$todo
[
'name'
]))
{
$this
->
setAttribute
(
'SUMMARY'
,
$todo
[
'name'
]);
}
if
(
isset
(
$todo
[
'desc'
]))
{
$this
->
setAttribute
(
'DESCRIPTION'
,
$todo
[
'desc'
]);
}
if
(
isset
(
$todo
[
'priority'
]))
{
$this
->
setAttribute
(
'PRIORITY'
,
$todo
[
'priority'
]);
}
if
(
isset
(
$todo
[
'due'
]))
{
$this
->
setAttribute
(
'DTSTAMP'
,
$todo
[
'due'
]);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, Apr 6, 3:04 AM (2 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
55/dc/d4b6aa7e8e93371e800d4b3768e7
Default Alt Text
Horde_iCalendar.php (111 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline