Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<?php
namespace Sabre\VObject;
class AttachIssueTest extends \PHPUnit_Framework_TestCase {
function testRead() {
$event = <<<ICS
BEGIN:VCALENDAR\r
BEGIN:VEVENT\r
ATTACH;FMTTYPE=;ENCODING=:Zm9v\r
END:VEVENT\r
END:VCALENDAR\r
ICS;
$obj = Reader::read($event);
$this->assertEquals($event, $obj->serialize());
}
}

View File

@@ -0,0 +1,650 @@
<?php
namespace Sabre\VObject;
/**
* Tests the cli.
*
* Warning: these tests are very rudimentary.
*/
class CliTest extends \PHPUnit_Framework_TestCase {
public function setUp() {
$this->cli = new CliMock();
$this->cli->stderr = fopen('php://memory','r+');
$this->cli->stdout = fopen('php://memory','r+');
}
public function testInvalidArg() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', '--hi'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
}
public function testQuiet() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', '-q'))
);
$this->assertTrue($this->cli->quiet);
rewind($this->cli->stderr);
$this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr)));
}
public function testHelp() {
$this->assertEquals(
0,
$this->cli->main(array('vobject', '-h'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
}
public function testFormat() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', '--format=jcard'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
$this->assertEquals('jcard', $this->cli->format);
}
public function testFormatInvalid() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', '--format=foo'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
$this->assertNull($this->cli->format);
}
public function testInputFormatInvalid() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', '--inputformat=foo'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
$this->assertNull($this->cli->format);
}
public function testNoInputFile() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', 'color'))
);
rewind($this->cli->stderr);
$this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
}
public function testTooManyArgs() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', 'color', 'a', 'b', 'c'))
);
}
public function testUnknownCommand() {
$this->assertEquals(
1,
$this->cli->main(array('vobject', 'foo', '-'))
);
}
public function testConvertJson() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<ICS
BEGIN:VCARD
VERSION:3.0
FN:Cowboy Henk
END:VCARD
ICS
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
0,
$this->cli->main(array('vobject', 'convert','--format=json', '-'))
);
rewind($this->cli->stdout);
$version = Version::VERSION;
$this->assertEquals(
'["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject '. $version .'\/\/EN"],["fn",{},"text","Cowboy Henk"]]]',
stream_get_contents($this->cli->stdout)
);
}
public function testConvertJCardPretty() {
if (version_compare(PHP_VERSION, '5.4.0') < 0) {
$this->markTestSkipped('This test required PHP 5.4.0');
}
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<ICS
BEGIN:VCARD
VERSION:3.0
FN:Cowboy Henk
END:VCARD
ICS
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
0,
$this->cli->main(array('vobject', 'convert','--format=jcard', '--pretty', '-'))
);
rewind($this->cli->stdout);
$version = Version::VERSION;
// PHP 5.5.12 changed the output
$expected = <<<JCARD
[
"vcard",
[
[
"versi
JCARD;
$this->assertStringStartsWith(
$expected,
stream_get_contents($this->cli->stdout)
);
}
public function testConvertJCalFail() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<ICS
BEGIN:VCARD
VERSION:3.0
FN:Cowboy Henk
END:VCARD
ICS
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'convert','--format=jcal', '--inputformat=mimedir', '-'))
);
}
public function testConvertMimeDir() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<JCARD
[
"vcard",
[
[
"version",
{
},
"text",
"4.0"
],
[
"prodid",
{
},
"text",
"-\/\/Sabre\/\/Sabre VObject 3.1.0\/\/EN"
],
[
"fn",
{
},
"text",
"Cowboy Henk"
]
]
]
JCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
0,
$this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-'))
);
rewind($this->cli->stdout);
$expected = <<<VCF
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
FN:Cowboy Henk
END:VCARD
VCF;
$this->assertEquals(
strtr($expected, array("\n"=>"\r\n")),
stream_get_contents($this->cli->stdout)
);
}
public function testConvertDefaultFormats() {
$inputStream = fopen('php://memory','r+');
$outputFile = SABRE_TEMPDIR . 'bar.json';
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'convert','foo.json',$outputFile))
);
$this->assertEquals('json', $this->cli->inputFormat);
$this->assertEquals('json', $this->cli->format);
}
public function testConvertDefaultFormats2() {
$outputFile = SABRE_TEMPDIR . 'bar.ics';
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'convert','foo.ics',$outputFile))
);
$this->assertEquals('mimedir', $this->cli->inputFormat);
$this->assertEquals('mimedir', $this->cli->format);
}
public function testVCard3040() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
FN:Cowboy Henk
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
0,
$this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-'))
);
rewind($this->cli->stdout);
$version = Version::VERSION;
$expected = <<<VCF
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject $version//EN
FN:Cowboy Henk
END:VCARD
VCF;
$this->assertEquals(
strtr($expected, array("\n"=>"\r\n")),
stream_get_contents($this->cli->stdout)
);
}
public function testVCard4030() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
FN:Cowboy Henk
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$this->assertEquals(
0,
$this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-'))
);
$version = Version::VERSION;
rewind($this->cli->stdout);
$expected = <<<VCF
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject $version//EN
FN:Cowboy Henk
END:VCARD
VCF;
$this->assertEquals(
strtr($expected, array("\n"=>"\r\n")),
stream_get_contents($this->cli->stdout)
);
}
public function testVCard4021() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
FN:Cowboy Henk
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'convert','--format=vcard21', '--pretty', '-'))
);
}
function testValidate() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
UID:foo
FN:Cowboy Henk
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
$result = $this->cli->main(array('vobject', 'validate', '-'));
$this->assertEquals(
0,
$result
);
}
function testValidateFail() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCALENDAR
VERSION:2.0
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'validate', '-'))
);
}
function testValidateFail2() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCALENDAR
VERSION:5.0
END:VCALENDAR
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'validate', '-'))
);
}
function testRepair() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:5.0
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$this->assertEquals(
2,
$this->cli->main(array('vobject', 'repair', '-'))
);
rewind($this->cli->stdout);
$this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout));
}
function testRepairNothing() {
$inputStream = fopen('php://memory','r+');
fwrite($inputStream, <<<VCARD
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 3.1.0//EN
BEGIN:VEVENT
UID:foo
DTSTAMP:20140122T233226Z
DTSTART:20140101T120000Z
END:VEVENT
END:VCALENDAR
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$result = $this->cli->main(array('vobject', 'repair', '-'));
rewind($this->cli->stderr);
$error = stream_get_contents($this->cli->stderr);
$this->assertEquals(
0,
$result,
"This should have been error free. stderr output:\n" . $error
);
}
/**
* Note: this is a very shallow test, doesn't dig into the actual output,
* but just makes sure there's no errors thrown.
*
* The colorizer is not a critical component, it's mostly a debugging tool.
*/
function testColorCalendar() {
$inputStream = fopen('php://memory','r+');
$version = Version::VERSION;
/**
* This object is not valid, but it's designed to hit every part of the
* colorizer source.
*/
fwrite($inputStream, <<<VCARD
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
BEGIN:VTIMEZONE
END:VTIMEZONE
BEGIN:VEVENT
ATTENDEE;RSVP=TRUE:mailto:foo@example.org
REQUEST-STATUS:5;foo
ATTACH:blabla
END:VEVENT
END:VCALENDAR
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$result = $this->cli->main(array('vobject', 'color', '-'));
rewind($this->cli->stderr);
$error = stream_get_contents($this->cli->stderr);
$this->assertEquals(
0,
$result,
"This should have been error free. stderr output:\n" . $error
);
}
/**
* Note: this is a very shallow test, doesn't dig into the actual output,
* but just makes sure there's no errors thrown.
*
* The colorizer is not a critical component, it's mostly a debugging tool.
*/
function testColorVCard() {
$inputStream = fopen('php://memory','r+');
$version = Version::VERSION;
/**
* This object is not valid, but it's designed to hit every part of the
* colorizer source.
*/
fwrite($inputStream, <<<VCARD
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
ADR:1;2;3;4a,4b;5;6
group.TEL:123454768
END:VCARD
VCARD
);
rewind($inputStream);
$this->cli->stdin = $inputStream;
// vCard 2.1 is not supported yet, so this returns a failure.
$result = $this->cli->main(array('vobject', 'color', '-'));
rewind($this->cli->stderr);
$error = stream_get_contents($this->cli->stderr);
$this->assertEquals(
0,
$result,
"This should have been error free. stderr output:\n" . $error
);
}
}
class CliMock extends Cli {
public $log = array();
public $quiet = false;
public $format;
public $pretty;
public $stdin;
public $stdout;
public $stderr;
public $inputFormat;
public $outputFormat;
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject\Component;
use DateTime;
use Sabre\VObject\Reader;
class VAlarmTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VAlarm $valarm,$start,$end,$outcome) {
$this->assertEquals($outcome, $valarm->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$calendar = new VCalendar();
// Hard date and time
$valarm1 = $calendar->createComponent('VALARM');
$valarm1->add(
$calendar->createProperty('TRIGGER', '20120312T130000Z', array('VALUE' => 'DATE-TIME'))
);
$tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to start time of event
$valarm2 = $calendar->createComponent('VALARM');
$valarm2->add(
$calendar->createProperty('TRIGGER', '-P1D', array('VALUE' => 'DURATION'))
);
$vevent2 = $calendar->createComponent('VEVENT');
$vevent2->DTSTART = '20120313T130000Z';
$vevent2->add($valarm2);
$tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to end time of event
$valarm3 = $calendar->createComponent('VALARM');
$valarm3->add( $calendar->createProperty('TRIGGER', '-P1D', array('VALUE'=>'DURATION', 'RELATED' => 'END')) );
$vevent3 = $calendar->createComponent('VEVENT');
$vevent3->DTSTART = '20120301T130000Z';
$vevent3->DTEND = '20120401T130000Z';
$vevent3->add($valarm3);
$tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to end time of todo
$valarm4 = $calendar->createComponent('VALARM');
$valarm4->TRIGGER = '-P1D';
$valarm4->TRIGGER['VALUE'] = 'DURATION';
$valarm4->TRIGGER['RELATED']= 'END';
$vtodo4 = $calendar->createComponent('VTODO');
$vtodo4->DTSTART = '20120301T130000Z';
$vtodo4->DUE = '20120401T130000Z';
$vtodo4->add($valarm4);
$tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to start time of event + repeat
$valarm5 = $calendar->createComponent('VALARM');
$valarm5->TRIGGER = '-P1D';
$valarm5->TRIGGER['VALUE'] = 'DURATION';
$valarm5->REPEAT = 10;
$valarm5->DURATION = 'P1D';
$vevent5 = $calendar->createComponent('VEVENT');
$vevent5->DTSTART = '20120301T130000Z';
$vevent5->add($valarm5);
$tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true);
// Relation to start time of event + duration, but no repeat
$valarm6 = $calendar->createComponent('VALARM');
$valarm6->TRIGGER = '-P1D';
$valarm6->TRIGGER['VALUE'] = 'DURATION';
$valarm6->DURATION = 'P1D';
$vevent6 = $calendar->createComponent('VEVENT');
$vevent6->DTSTART = '20120313T130000Z';
$vevent6->add($valarm6);
$tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to end time of event (DURATION instead of DTEND)
$valarm7 = $calendar->createComponent('VALARM');
$valarm7->TRIGGER = '-P1D';
$valarm7->TRIGGER['VALUE'] = 'DURATION';
$valarm7->TRIGGER['RELATED']= 'END';
$vevent7 = $calendar->createComponent('VEVENT');
$vevent7->DTSTART = '20120301T130000Z';
$vevent7->DURATION = 'P30D';
$vevent7->add($valarm7);
$tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to end time of event (No DTEND or DURATION)
$valarm7 = $calendar->createComponent('VALARM');
$valarm7->TRIGGER = '-P1D';
$valarm7->TRIGGER['VALUE'] = 'DURATION';
$valarm7->TRIGGER['RELATED']= 'END';
$vevent7 = $calendar->createComponent('VEVENT');
$vevent7->DTSTART = '20120301T130000Z';
$vevent7->add($valarm7);
$tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true);
$tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false);
return $tests;
}
/**
* @expectedException LogicException
*/
public function testInTimeRangeInvalidComponent() {
$calendar = new VCalendar();
$valarm = $calendar->createComponent('VALARM');
$valarm->TRIGGER = '-P1D';
$valarm->TRIGGER['RELATED'] = 'END';
$vjournal = $calendar->createComponent('VJOURNAL');
$vjournal->add($valarm);
$valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
}
/**
* This bug was found and reported on the mailing list.
*/
public function testInTimeRangeBuggy() {
$input = <<<BLA
BEGIN:VCALENDAR
BEGIN:VTODO
DTSTAMP:20121003T064931Z
UID:b848cb9a7bb16e464a06c222ca1f8102@examle.com
STATUS:NEEDS-ACTION
DUE:20121005T000000Z
SUMMARY:Task 1
CATEGORIES:AlarmCategory
BEGIN:VALARM
TRIGGER:-PT10M
ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
END:VTODO
END:VCALENDAR
BLA;
$vobj = Reader::read($input);
$this->assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00')));
}
}

View File

@@ -0,0 +1,385 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
use Sabre\VObject\Reader;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VAvailability;
/**
* We use `RFCxxx` has a placeholder for the
* https://tools.ietf.org/html/draft-daboo-calendar-availability-05 name.
*/
class VAvailabilityTest extends \PHPUnit_Framework_TestCase {
function testVAvailabilityComponent() {
$vcal = <<<VCAL
BEGIN:VCALENDAR
BEGIN:VAVAILABILITY
END:VAVAILABILITY
END:VCALENDAR
VCAL;
$document = Reader::read($vcal);
$this->assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY);
}
function testRFCxxxSection3_1_availabilityprop_required() {
// UID and DTSTAMP are present.
$this->assertIsValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// UID and DTSTAMP are missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// DTSTAMP is missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// UID is missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
DTSTAMP:20111005T133225Z
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
}
function testRFCxxxSection3_1_availabilityprop_optional_once() {
$properties = array(
'BUSYTYPE:BUSY',
'CLASS:PUBLIC',
'CREATED:20111005T135125Z',
'DESCRIPTION:Long bla bla',
'DTSTART:20111005T020000',
'LAST-MODIFIED:20111005T135325Z',
'ORGANIZER:mailto:foo@example.com',
'PRIORITY:1',
'SEQUENCE:0',
'SUMMARY:Bla bla',
'URL:http://example.org/'
);
// They are all present, only once.
$this->assertIsValid(Reader::read($this->template($properties)));
// We duplicate each one to see if it fails.
foreach ($properties as $property) {
$this->assertIsNotValid(Reader::read($this->template(array(
$property,
$property
))));
}
}
function testRFCxxxSection3_1_availabilityprop_dtend_duration() {
// Only DTEND.
$this->assertIsValid(Reader::read($this->template(array(
'DTEND:21111005T133225Z'
))));
// Only DURATION.
$this->assertIsValid(Reader::read($this->template(array(
'DURATION:PT1H'
))));
// Both (not allowed).
$this->assertIsNotValid(Reader::read($this->template(array(
'DTEND:21111005T133225Z',
'DURATION:PT1H'
))));
}
function testAvailableSubComponent() {
$vcal = <<<VCAL
BEGIN:VCALENDAR
BEGIN:VAVAILABILITY
BEGIN:AVAILABLE
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL;
$document = Reader::read($vcal);
$this->assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE);
}
function testRFCxxxSection3_1_availableprop_required() {
// UID, DTSTAMP and DTSTART are present.
$this->assertIsValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
UID:foo@test
DTSTAMP:20111005T133225Z
DTSTART:20111005T133225Z
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// UID, DTSTAMP and DTSTART are missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// UID is missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
DTSTAMP:20111005T133225Z
DTSTART:20111005T133225Z
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// DTSTAMP is missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
UID:foo@test
DTSTART:20111005T133225Z
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
// DTSTART is missing.
$this->assertIsNotValid(Reader::read(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
UID:foo@test
DTSTAMP:20111005T133225Z
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
));
}
function testRFCxxxSection3_1_available_dtend_duration() {
// Only DTEND.
$this->assertIsValid(Reader::read($this->templateAvailable(array(
'DTEND:21111005T133225Z'
))));
// Only DURATION.
$this->assertIsValid(Reader::read($this->templateAvailable(array(
'DURATION:PT1H'
))));
// Both (not allowed).
$this->assertIsNotValid(Reader::read($this->templateAvailable(array(
'DTEND:21111005T133225Z',
'DURATION:PT1H'
))));
}
function testRFCxxxSection3_1_available_optional_once() {
$properties = array(
'CREATED:20111005T135125Z',
'DESCRIPTION:Long bla bla',
'LAST-MODIFIED:20111005T135325Z',
'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z',
'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
'SUMMARY:Bla bla'
);
// They are all present, only once.
$this->assertIsValid(Reader::read($this->templateAvailable($properties)));
// We duplicate each one to see if it fails.
foreach ($properties as $property) {
$this->assertIsNotValid(Reader::read($this->templateAvailable(array(
$property,
$property
))));
}
}
function testRFCxxxSection3_2() {
$this->assertEquals(
'BUSY',
Reader::read($this->templateAvailable(array(
'BUSYTYPE:BUSY'
)))
->VAVAILABILITY
->AVAILABLE
->BUSYTYPE
->getValue()
);
$this->assertEquals(
'BUSY-UNAVAILABLE',
Reader::read($this->templateAvailable(array(
'BUSYTYPE:BUSY-UNAVAILABLE'
)))
->VAVAILABILITY
->AVAILABLE
->BUSYTYPE
->getValue()
);
$this->assertEquals(
'BUSY-TENTATIVE',
Reader::read($this->templateAvailable(array(
'BUSYTYPE:BUSY-TENTATIVE'
)))
->VAVAILABILITY
->AVAILABLE
->BUSYTYPE
->getValue()
);
}
protected function assertIsValid(VObject\Document $document) {
$this->assertEmpty($document->validate());
}
protected function assertIsNotValid(VObject\Document $document) {
$this->assertNotEmpty($document->validate());
}
protected function template(array $properties) {
return $this->_template(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
END:VAVAILABILITY
END:VCALENDAR
VCAL
,
$properties
);
}
protected function templateAvailable(array $properties) {
return $this->_template(
<<<VCAL
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id
BEGIN:VAVAILABILITY
UID:foo@test
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
UID:foo@test
DTSTAMP:20111005T133225Z
DTSTART:20111005T133225Z
END:AVAILABLE
END:VAVAILABILITY
END:VCALENDAR
VCAL
,
$properties
);
}
protected function _template($template, array $properties) {
return str_replace('…', implode("\r\n", $properties), $template);
}
}

View File

@@ -0,0 +1,697 @@
<?php
namespace Sabre\VObject\Component;
use DateTimeZone;
use Sabre\VObject;
class VCalendarTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider expandData
*/
public function testExpand($input, $output, $timeZone = 'UTC', $start = '2011-12-01', $end = '2011-12-31') {
$vcal = VObject\Reader::read($input);
$timeZone = new DateTimeZone($timeZone);
$vcal->expand(
new \DateTime($start),
new \DateTime($end),
$timeZone
);
// This will normalize the output
$output = VObject\Reader::read($output)->serialize();
$this->assertEquals($output, $vcal->serialize());
}
public function expandData() {
$tests = array();
// No data
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
END:VCALENDAR
';
$output = $input;
$tests[] = array($input,$output);
// Simple events
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla
SUMMARY:InExpand
DTSTART;VALUE=DATE:20111202
END:VEVENT
BEGIN:VEVENT
UID:bla2
SUMMARY:NotInExpand
DTSTART;VALUE=DATE:20120101
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla
SUMMARY:InExpand
DTSTART;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Removing timezone info
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
END:VTIMEZONE
BEGIN:VEVENT
UID:bla4
SUMMARY:RemoveTZ info
DTSTART;TZID=Europe/Paris:20111203T130102
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla4
SUMMARY:RemoveTZ info
DTSTART:20111203T120102Z
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Recurrence rule
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111125T120000Z
DTEND:20111125T130000Z
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111202T120000Z
DTEND:20111202T130000Z
RECURRENCE-ID:20111202T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111209T120000Z
DTEND:20111209T130000Z
RECURRENCE-ID:20111209T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111216T120000Z
DTEND:20111216T130000Z
RECURRENCE-ID:20111216T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111223T120000Z
DTEND:20111223T130000Z
RECURRENCE-ID:20111223T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111230T120000Z
DTEND:20111230T130000Z
RECURRENCE-ID:20111230T120000Z
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Recurrence rule + override
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111125T120000Z
DTEND:20111125T130000Z
RRULE:FREQ=WEEKLY
END:VEVENT
BEGIN:VEVENT
UID:bla6
RECURRENCE-ID:20111209T120000Z
DTSTART:20111209T140000Z
DTEND:20111209T150000Z
SUMMARY:Override!
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111202T120000Z
DTEND:20111202T130000Z
RECURRENCE-ID:20111202T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
RECURRENCE-ID:20111209T120000Z
DTSTART:20111209T140000Z
DTEND:20111209T150000Z
SUMMARY:Override!
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111216T120000Z
DTEND:20111216T130000Z
RECURRENCE-ID:20111216T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111223T120000Z
DTEND:20111223T130000Z
RECURRENCE-ID:20111223T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111230T120000Z
DTEND:20111230T130000Z
RECURRENCE-ID:20111230T120000Z
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Floating dates and times.
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:bla1
DTSTART:20141112T195000
END:VEVENT
BEGIN:VEVENT
UID:bla2
DTSTART;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141112
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
END:VCALENDAR
ICS;
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:bla1
DTSTART:20141112T225000Z
END:VEVENT
BEGIN:VEVENT
UID:bla2
DTSTART;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141112
RECURRENCE-ID;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141113
RECURRENCE-ID;VALUE=DATE:20141113
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array($input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01');
// Recurrence rule with no valid instances
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule3
DTSTART:20111125T120000Z
DTEND:20111125T130000Z
RRULE:FREQ=WEEKLY;COUNT=1
EXDATE:20111125T120000Z
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
END:VCALENDAR
';
$tests[] = array($input, $output);
return $tests;
}
/**
* @expectedException LogicException
*/
public function testBrokenEventExpand() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
RRULE:FREQ=WEEKLY
DTSTART;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$vcal->expand(
new \DateTime('2011-12-01'),
new \DateTime('2011-12-31')
);
}
function testGetDocumentType() {
$vcard = new VCalendar();
$vcard->VERSION = '2.0';
$this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType());
}
function testValidateCorrect() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
PRODID:foo
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
DTSTAMP:20140122T233226Z
UID:foo
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(array(), $vcal->validate(), 'Got an error');
}
function testValidateNoVersion() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:foo
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateWrongVersion() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:3.0
PRODID:foo
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateNoProdId() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateDoubleCalScale() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
CALSCALE:GREGORIAN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateDoubleMethod() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
METHOD:REQUEST
METHOD:REQUEST
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateTwoMasterEvents() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
METHOD:REQUEST
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(1, count($vcal->validate()));
}
function testValidateOneMasterEvent() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
METHOD:REQUEST
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
RECURRENCE-ID;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$this->assertEquals(0, count($vcal->validate()));
}
function testGetBaseComponent() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
METHOD:REQUEST
BEGIN:VEVENT
SUMMARY:test
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
RECURRENCE-ID;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$result = $vcal->getBaseComponent();
$this->assertEquals('test', $result->SUMMARY->getValue());
}
function testGetBaseComponentNoResult() {
$input = 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:foo
METHOD:REQUEST
BEGIN:VEVENT
SUMMARY:test
RECURRENCE-ID;VALUE=DATE:20111202
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20111202
UID:foo
DTSTAMP:20140122T234434Z
RECURRENCE-ID;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$result = $vcal->getBaseComponent();
$this->assertNull($result);
}
function testNoComponents() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:vobject
END:VCALENDAR
ICS;
$this->assertValidate(
$input,
0,
3,
"An iCalendar object must have at least 1 component."
);
}
function testCalDAVNoComponents() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:vobject
BEGIN:VTIMEZONE
TZID:America/Toronto
END:VTIMEZONE
END:VCALENDAR
ICS;
$this->assertValidate(
$input,
VCalendar::PROFILE_CALDAV,
3,
"A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)."
);
}
function testCalDAVMultiUID() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:vobject
BEGIN:VEVENT
UID:foo
DTSTAMP:20150109T184500Z
DTSTART:20150109T184500Z
END:VEVENT
BEGIN:VEVENT
UID:bar
DTSTAMP:20150109T184500Z
DTSTART:20150109T184500Z
END:VEVENT
END:VCALENDAR
ICS;
$this->assertValidate(
$input,
VCalendar::PROFILE_CALDAV,
3,
"A calendar object on a CalDAV server may only have components with the same UID."
);
}
function testCalDAVMultiComponent() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:vobject
BEGIN:VEVENT
UID:foo
RECURRENCE-ID:20150109T185200Z
DTSTAMP:20150109T184500Z
DTSTART:20150109T184500Z
END:VEVENT
BEGIN:VTODO
UID:foo
DTSTAMP:20150109T184500Z
DTSTART:20150109T184500Z
END:VTODO
END:VCALENDAR
ICS;
$this->assertValidate(
$input,
VCalendar::PROFILE_CALDAV,
3,
"A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)."
);
}
function testCalDAVMETHOD() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
PRODID:vobject
BEGIN:VEVENT
UID:foo
RECURRENCE-ID:20150109T185200Z
DTSTAMP:20150109T184500Z
DTSTART:20150109T184500Z
END:VEVENT
END:VCALENDAR
ICS;
$this->assertValidate(
$input,
VCalendar::PROFILE_CALDAV,
3,
"A calendar object on a CalDAV server MUST NOT have a METHOD property."
);
}
function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) {
$vcal = VObject\Reader::read($ics);
$result = $vcal->validate($options);
$this->assertValidateResult($result, $expectedLevel, $expectedMessage);
}
function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
$messages = array();
foreach($input as $warning) {
$messages[] = $warning['message'];
}
if ($expectedLevel === 0) {
$this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
} else {
$this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
$this->assertEquals($expectedMessage, $input[0]['message']);
$this->assertEquals($expectedLevel, $input[0]['level']);
}
}
}

View File

@@ -0,0 +1,288 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
class VCardTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider validateData
*/
function testValidate($input, $expectedWarnings, $expectedRepairedOutput) {
$vcard = VObject\Reader::read($input);
$warnings = $vcard->validate();
$warnMsg = array();
foreach($warnings as $warning) {
$warnMsg[] = $warning['message'];
}
$this->assertEquals($expectedWarnings, $warnMsg);
$vcard->validate(VObject\Component::REPAIR);
$this->assertEquals(
$expectedRepairedOutput,
$vcard->serialize()
);
}
public function validateData() {
$tests = array();
// Correct
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
array(),
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
);
// No VERSION
$tests[] = array(
"BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
array(
'VERSION MUST appear exactly once in a VCARD component',
),
"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
);
// Unknown version
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
array(
'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
),
"BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
);
// No FN
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n",
);
// No FN, N fallback
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n",
);
// No FN, N fallback, no first name
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n",
);
// No FN, ORG fallback
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n",
);
return $tests;
}
function testGetDocumentType() {
$vcard = new VCard(array(), false);
$vcard->VERSION = '2.1';
$this->assertEquals(VCard::VCARD21, $vcard->getDocumentType());
$vcard = new VCard(array(), false);
$vcard->VERSION = '3.0';
$this->assertEquals(VCard::VCARD30, $vcard->getDocumentType());
$vcard = new VCard(array(), false);
$vcard->VERSION = '4.0';
$this->assertEquals(VCard::VCARD40, $vcard->getDocumentType());
$vcard = new VCard(array(), false);
$this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType());
}
function testPreferredNoPref() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:3.0
EMAIL:1@example.org
EMAIL:2@example.org
END:VCARD
VCF;
$vcard = VObject\Reader::read($vcard);
$this->assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue());
}
function testPreferredWithPref() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:3.0
EMAIL:1@example.org
EMAIL;TYPE=PREF:2@example.org
END:VCARD
VCF;
$vcard = VObject\Reader::read($vcard);
$this->assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue());
}
function testPreferredWith40Pref() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
EMAIL:1@example.org
EMAIL;PREF=3:2@example.org
EMAIL;PREF=2:3@example.org
END:VCARD
VCF;
$vcard = VObject\Reader::read($vcard);
$this->assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue());
}
function testPreferredNotFound() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
END:VCARD
VCF;
$vcard = VObject\Reader::read($vcard);
$this->assertNull($vcard->preferred('EMAIL'));
}
function testNoUIDCardDAV() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
FN:John Doe
END:VCARD
VCF;
$this->assertValidate(
$vcard,
VCARD::PROFILE_CARDDAV,
3,
'vCards on CardDAV servers MUST have a UID property.'
);
}
function testNoUIDNoCardDAV() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
FN:John Doe
END:VCARD
VCF;
$this->assertValidate(
$vcard,
0,
2,
'Adding a UID to a vCard property is recommended.'
);
}
function testNoUIDNoCardDAVRepair() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
FN:John Doe
END:VCARD
VCF;
$this->assertValidate(
$vcard,
VCARD::REPAIR,
1,
'Adding a UID to a vCard property is recommended.'
);
}
function testVCard21CardDAV() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:2.1
FN:John Doe
UID:foo
END:VCARD
VCF;
$this->assertValidate(
$vcard,
VCARD::PROFILE_CARDDAV,
3,
'CardDAV servers are not allowed to accept vCard 2.1.'
);
}
function testVCard21NoCardDAV() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:2.1
FN:John Doe
UID:foo
END:VCARD
VCF;
$this->assertValidate(
$vcard,
0,
0
);
}
function assertValidate($vcf, $options, $expectedLevel, $expectedMessage = null) {
$vcal = VObject\Reader::read($vcf);
$result = $vcal->validate($options);
$this->assertValidateResult($result, $expectedLevel, $expectedMessage);
}
function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
$messages = array();
foreach($input as $warning) {
$messages[] = $warning['message'];
}
if ($expectedLevel === 0) {
$this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
} else {
$this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
$this->assertEquals($expectedMessage, $input[0]['message']);
$this->assertEquals($expectedLevel, $input[0]['level']);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
class VEventTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VEvent $vevent,$start,$end,$outcome) {
$this->assertEquals($outcome, $vevent->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$calendar = new VCalendar();
$vevent = $calendar->createComponent('VEVENT');
$vevent->DTSTART = '20111223T120000Z';
$tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent2 = clone $vevent;
$vevent2->DTEND = '20111225T120000Z';
$tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent3 = clone $vevent;
$vevent3->DURATION = 'P1D';
$tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent4 = clone $vevent;
$vevent4->DTSTART = '20111225';
$vevent4->DTSTART['VALUE'] = 'DATE';
$tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
// Event with no end date should be treated as lasting the entire day.
$tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true);
// DTEND is non inclusive so all day events should not be returned on the next day.
$tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00'), new \DateTime('2011-12-26 17:00:00'), false);
// The timezone of timerange in question also needs to be considered.
$tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2011-12-26 17:00:00', new \DateTimeZone('Europe/Berlin')), false);
$vevent5 = clone $vevent;
$vevent5->DURATION = 'P1D';
$vevent5->RRULE = 'FREQ=YEARLY';
$tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true);
$vevent6 = clone $vevent;
$vevent6->DTSTART = '20111225';
$vevent6->DTSTART['VALUE'] = 'DATE';
$vevent6->DTEND = '20111225';
$vevent6->DTEND['VALUE'] = 'DATE';
$tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
// Added this test to ensure that recurrence rules with no DTEND also
// get checked for the entire day.
$vevent7 = clone $vevent;
$vevent7->DTSTART = '20120101';
$vevent7->DTSTART['VALUE'] = 'DATE';
$vevent7->RRULE = 'FREQ=MONTHLY';
$tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true);
// The timezone of timerange in question should also be considered.
// Added this test to check recurring events that have no instances.
$vevent8 = clone $vevent;
$vevent8->DTSTART = '20130329T140000';
$vevent8->DTEND = '20130329T153000';
$vevent8->RRULE = array('FREQ' => 'WEEKLY', 'BYDAY' => array('FR'), 'UNTIL' => '20130412T115959Z');
$vevent8->add('EXDATE', '20130405T140000');
$vevent8->add('EXDATE', '20130329T140000');
$tests[] = array($vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false);
return $tests;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
use Sabre\VObject\Reader;
class VFreeBusyTest extends \PHPUnit_Framework_TestCase {
function testIsFree() {
$input = <<<BLA
BEGIN:VCALENDAR
BEGIN:VFREEBUSY
FREEBUSY;FBTYPE=FREE:20120912T000500Z/PT1H
FREEBUSY;FBTYPE=BUSY:20120912T010000Z/20120912T020000Z
FREEBUSY;FBTYPE=BUSY-TENTATIVE:20120912T020000Z/20120912T030000Z
FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20120912T030000Z/20120912T040000Z
FREEBUSY;FBTYPE=BUSY:20120912T050000Z/20120912T060000Z,20120912T080000Z/20120912T090000Z
FREEBUSY;FBTYPE=BUSY:20120912T100000Z/PT1H
END:VFREEBUSY
END:VCALENDAR
BLA;
$obj = VObject\Reader::read($input);
$vfb = $obj->VFREEBUSY;
$tz = new \DateTimeZone('UTC');
$this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz)));
$this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz)));
$this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz)));
// Checking whether the end time is treated as non-inclusive
$this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz)));
$this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz)));
$this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz)));
}
public function testValidate() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VFREEBUSY
UID:some-random-id
DTSTAMP:20140402T180200Z
END:VFREEBUSY
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(), $messages);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject\Component;
use Sabre\VObject\Reader;
class VJournalTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VJournal $vtodo,$start,$end,$outcome) {
$this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
}
public function testValidate() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VJOURNAL
UID:12345678
DTSTAMP:20140402T174100Z
END:VJOURNAL
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(), $messages);
}
public function testValidateBroken() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VJOURNAL
UID:12345678
DTSTAMP:20140402T174100Z
URL:http://example.org/
URL:http://example.com/
END:VJOURNAL
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(
array("URL MUST NOT appear more than once in a VJOURNAL component"),
$messages
);
}
public function timeRangeTestData() {
$calendar = new VCalendar();
$tests = array();
$vjournal = $calendar->createComponent('VJOURNAL');
$vjournal->DTSTART = '20111223T120000Z';
$tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vjournal2 = $calendar->createComponent('VJOURNAL');
$vjournal2->DTSTART = '20111223';
$vjournal2->DTSTART['VALUE'] = 'DATE';
$tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vjournal3 = $calendar->createComponent('VJOURNAL');
$tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false);
$tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
return $tests;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
use Sabre\VObject\Reader;
class VTimeZoneTest extends \PHPUnit_Framework_TestCase {
function testValidate() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTIMEZONE
TZID:America/Toronto
END:VTIMEZONE
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(), $messages);
}
function testGetTimeZone() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTIMEZONE
TZID:America/Toronto
END:VTIMEZONE
END:VCALENDAR
HI;
$obj = Reader::read($input);
$tz = new \DateTimeZone('America/Toronto');
$this->assertEquals(
$tz,
$obj->VTIMEZONE->getTimeZone()
);
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Sabre\VObject\Component;
use
Sabre\VObject\Component,
Sabre\VObject\Reader;
class VTodoTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VTodo $vtodo,$start,$end,$outcome) {
$this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$calendar = new VCalendar();
$vtodo = $calendar->createComponent('VTODO');
$vtodo->DTSTART = '20111223T120000Z';
$tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo2 = clone $vtodo;
$vtodo2->DURATION = 'P1D';
$tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo3 = clone $vtodo;
$vtodo3->DUE = '20111225';
$tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo4 = $calendar->createComponent('VTODO');
$vtodo4->DUE = '20111225';
$tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo5 = $calendar->createComponent('VTODO');
$vtodo5->COMPLETED = '20111225';
$tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo6 = $calendar->createComponent('VTODO');
$vtodo6->CREATED = '20111225';
$tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo7 = $calendar->createComponent('VTODO');
$vtodo7->CREATED = '20111225';
$vtodo7->COMPLETED = '20111226';
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo7 = $calendar->createComponent('VTODO');
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true);
return $tests;
}
public function testValidate() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTODO
UID:1234-21355-123156
DTSTAMP:20140402T183400Z
END:VTODO
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(), $messages);
}
public function testValidateInvalid() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTODO
END:VTODO
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(
"UID MUST appear exactly once in a VTODO component",
"DTSTAMP MUST appear exactly once in a VTODO component",
), $messages);
}
public function testValidateDUEDTSTARTMisMatch() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTODO
UID:FOO
DTSTART;VALUE=DATE-TIME:20140520T131600Z
DUE;VALUE=DATE:20140520
DTSTAMP;VALUE=DATE-TIME:20140520T131600Z
END:VTODO
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(
"The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART",
), $messages);
}
public function testValidateDUEbeforeDTSTART() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTODO
UID:FOO
DTSTART;VALUE=DATE:20140520
DUE;VALUE=DATE:20140518
DTSTAMP;VALUE=DATE-TIME:20140520T131600Z
END:VTODO
END:VCALENDAR
HI;
$obj = Reader::read($input);
$warnings = $obj->validate();
$messages = array();
foreach($warnings as $warning) {
$messages[] = $warning['message'];
}
$this->assertEquals(array(
"DUE must occur after DTSTART",
), $messages);
}
}

View File

@@ -0,0 +1,528 @@
<?php
namespace Sabre\VObject;
use
Sabre\VObject\Component\VCalendar,
Sabre\VObject\Component\VCard;
class ComponentTest extends \PHPUnit_Framework_TestCase {
function testIterate() {
$comp = new VCalendar(array(), false);
$sub = $comp->createComponent('VEVENT');
$comp->add($sub);
$sub = $comp->createComponent('VTODO');
$comp->add($sub);
$count = 0;
foreach($comp->children() as $key=>$subcomponent) {
$count++;
$this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
}
$this->assertEquals(2,$count);
$this->assertEquals(1,$key);
}
function testMagicGet() {
$comp = new VCalendar(array(), false);
$sub = $comp->createComponent('VEVENT');
$comp->add($sub);
$sub = $comp->createComponent('VTODO');
$comp->add($sub);
$event = $comp->vevent;
$this->assertInstanceOf('Sabre\\VObject\\Component', $event);
$this->assertEquals('VEVENT', $event->name);
$this->assertInternalType('null', $comp->vjournal);
}
function testMagicGetGroups() {
$comp = new VCard();
$sub = $comp->createProperty('GROUP1.EMAIL','1@1.com');
$comp->add($sub);
$sub = $comp->createProperty('GROUP2.EMAIL','2@2.com');
$comp->add($sub);
$sub = $comp->createProperty('EMAIL','3@3.com');
$comp->add($sub);
$emails = $comp->email;
$this->assertEquals(3, count($emails));
$email1 = $comp->{"group1.email"};
$this->assertEquals('EMAIL', $email1[0]->name);
$this->assertEquals('GROUP1', $email1[0]->group);
$email3 = $comp->{".email"};
$this->assertEquals('EMAIL', $email3[0]->name);
$this->assertEquals(null, $email3[0]->group);
}
function testMagicIsset() {
$comp = new VCalendar();
$sub = $comp->createComponent('VEVENT');
$comp->add($sub);
$sub = $comp->createComponent('VTODO');
$comp->add($sub);
$this->assertTrue(isset($comp->vevent));
$this->assertTrue(isset($comp->vtodo));
$this->assertFalse(isset($comp->vjournal));
}
function testMagicSetScalar() {
$comp = new VCalendar();
$comp->myProp = 'myValue';
$this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
$this->assertEquals('myValue',(string)$comp->MYPROP);
}
function testMagicSetScalarTwice() {
$comp = new VCalendar(array(), false);
$comp->myProp = 'myValue';
$comp->myProp = 'myValue';
$this->assertEquals(1,count($comp->children()));
$this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
$this->assertEquals('myValue',(string)$comp->MYPROP);
}
function testMagicSetArray() {
$comp = new VCalendar();
$comp->ORG = array('Acme Inc', 'Section 9');
$this->assertInstanceOf('Sabre\\VObject\\Property',$comp->ORG);
$this->assertEquals(array('Acme Inc', 'Section 9'),$comp->ORG->getParts());
}
function testMagicSetComponent() {
$comp = new VCalendar();
// Note that 'myProp' is ignored here.
$comp->myProp = $comp->createComponent('VEVENT');
$this->assertEquals(1, count($comp));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testMagicSetTwice() {
$comp = new VCalendar(array(), false);
$comp->VEVENT = $comp->createComponent('VEVENT');
$comp->VEVENT = $comp->createComponent('VEVENT');
$this->assertEquals(1, count($comp->children()));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testArrayAccessGet() {
$comp = new VCalendar(array(), false);
$event = $comp->createComponent('VEVENT');
$event->summary = 'Event 1';
$comp->add($event);
$event2 = clone $event;
$event2->summary = 'Event 2';
$comp->add($event2);
$this->assertEquals(2,count($comp->children()));
$this->assertTrue($comp->vevent[1] instanceof Component);
$this->assertEquals('Event 2', (string)$comp->vevent[1]->summary);
}
function testArrayAccessExists() {
$comp = new VCalendar();
$event = $comp->createComponent('VEVENT');
$event->summary = 'Event 1';
$comp->add($event);
$event2 = clone $event;
$event2->summary = 'Event 2';
$comp->add($event2);
$this->assertTrue(isset($comp->vevent[0]));
$this->assertTrue(isset($comp->vevent[1]));
}
/**
* @expectedException LogicException
*/
function testArrayAccessSet() {
$comp = new VCalendar();
$comp['hey'] = 'hi there';
}
/**
* @expectedException LogicException
*/
function testArrayAccessUnset() {
$comp = new VCalendar();
unset($comp[0]);
}
function testAddScalar() {
$comp = new VCalendar(array(), false);
$comp->add('myprop','value');
$this->assertEquals(1, count($comp->children()));
$bla = $comp->children[0];
$this->assertTrue($bla instanceof Property);
$this->assertEquals('MYPROP',$bla->name);
$this->assertEquals('value',(string)$bla);
}
function testAddScalarParams() {
$comp = new VCalendar(array(), false);
$comp->add('myprop','value',array('param1'=>'value1'));
$this->assertEquals(1, count($comp->children()));
$bla = $comp->children[0];
$this->assertInstanceOf('Sabre\\VObject\\Property', $bla);
$this->assertEquals('MYPROP',$bla->name);
$this->assertEquals('value', (string)$bla);
$this->assertEquals(1, count($bla->parameters()));
$this->assertEquals('PARAM1',$bla->parameters['PARAM1']->name);
$this->assertEquals('value1',$bla->parameters['PARAM1']->getValue());
}
function testAddComponent() {
$comp = new VCalendar(array(), false);
$comp->add($comp->createComponent('VEVENT'));
$this->assertEquals(1, count($comp->children()));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testAddComponentTwice() {
$comp = new VCalendar(array(), false);
$comp->add($comp->createComponent('VEVENT'));
$comp->add($comp->createComponent('VEVENT'));
$this->assertEquals(2, count($comp->children()));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail() {
$comp = new VCalendar();
$comp->add($comp->createComponent('VEVENT'),'hello');
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail2() {
$comp = new VCalendar();
$comp->add(array());
}
function testMagicUnset() {
$comp = new VCalendar(array(), false);
$comp->add($comp->createComponent('VEVENT'));
unset($comp->vevent);
$this->assertEquals(0, count($comp->children()));
}
function testCount() {
$comp = new VCalendar();
$this->assertEquals(1,$comp->count());
}
function testChildren() {
$comp = new VCalendar(array(), false);
// Note that 'myProp' is ignored here.
$comp->add($comp->createComponent('VEVENT'));
$comp->add($comp->createComponent('VTODO'));
$r = $comp->children();
$this->assertInternalType('array', $r);
$this->assertEquals(2,count($r));
}
function testGetComponents() {
$comp = new VCalendar();
$comp->add($comp->createProperty('FOO','BAR'));
$comp->add($comp->createComponent('VTODO'));
$r = $comp->getComponents();
$this->assertInternalType('array', $r);
$this->assertEquals(1, count($r));
$this->assertEquals('VTODO', $r[0]->name);
}
function testSerialize() {
$comp = new VCalendar(array(), false);
$this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
}
function testSerializeChildren() {
$comp = new VCalendar(array(), false);
$event = $comp->add($comp->createComponent('VEVENT'));
unset($event->DTSTAMP, $event->UID);
$comp->add($comp->createComponent('VTODO'));
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str);
}
function testSerializeOrderCompAndProp() {
$comp = new VCalendar(array(), false);
$comp->add($event = $comp->createComponent('VEVENT'));
$comp->add('PROP1','BLABLA');
$comp->add('VERSION','2.0');
$comp->add($comp->createComponent('VTIMEZONE'));
unset($event->DTSTAMP, $event->UID);
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str);
}
function testAnotherSerializeOrderProp() {
$prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
$comp = new VCard(array(), false);
$comp->__set('SOMEPROP','FOO');
$comp->__set('ANOTHERPROP','FOO');
$comp->__set('THIRDPROP','FOO');
foreach ($prop4s as $prop4) {
$comp->add('PROP4', 'FOO '.$prop4);
}
$comp->__set('PROPNUMBERFIVE', 'FOO');
$comp->__set('PROPNUMBERSIX', 'FOO');
$comp->__set('PROPNUMBERSEVEN', 'FOO');
$comp->__set('PROPNUMBEREIGHT', 'FOO');
$comp->__set('PROPNUMBERNINE', 'FOO');
$comp->__set('PROPNUMBERTEN', 'FOO');
$comp->__set('VERSION','2.0');
$comp->__set('UID', 'FOO');
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str);
}
function testInstantiateWithChildren() {
$comp = new VCard(array(
'ORG' => array('Acme Inc.', 'Section 9'),
'FN' => 'Finn The Human',
));
$this->assertEquals(array('Acme Inc.', 'Section 9'), $comp->ORG->getParts());
$this->assertEquals('Finn The Human', $comp->FN->getValue());
}
function testInstantiateSubComponent() {
$comp = new VCalendar();
$event = $comp->createComponent('VEVENT', array(
$comp->createProperty('UID', '12345'),
));
$comp->add($event);
$this->assertEquals('12345', $comp->VEVENT->UID->getValue());
}
function testRemoveByName() {
$comp = new VCalendar(array(), false);
$comp->add('prop1','val1');
$comp->add('prop2','val2');
$comp->add('prop2','val2');
$comp->remove('prop2');
$this->assertFalse(isset($comp->prop2));
$this->assertTrue(isset($comp->prop1));
}
function testRemoveByObj() {
$comp = new VCalendar(array(), false);
$comp->add('prop1','val1');
$prop = $comp->add('prop2','val2');
$comp->remove($prop);
$this->assertFalse(isset($comp->prop2));
$this->assertTrue(isset($comp->prop1));
}
/**
* @expectedException InvalidArgumentException
*/
function testRemoveNotFound() {
$comp = new VCalendar(array(), false);
$prop = $comp->createProperty('A','B');
$comp->remove($prop);
}
/**
* @dataProvider ruleData
*/
function testValidateRules($componentList, $errorCount) {
$vcard = new Component\VCard();
$component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
foreach($componentList as $v) {
$component->add($v,'Hello.');
}
$this->assertEquals($errorCount, count($component->validate()));
}
function testValidateRepair() {
$vcard = new Component\VCard();
$component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
$component->validate(Component::REPAIR);
$this->assertEquals('yow', $component->BAR->getValue());
}
function ruleData() {
return array(
array(array(), 2),
array(array('FOO'), 3),
array(array('BAR'), 1),
array(array('BAZ'), 1),
array(array('BAR','BAZ'), 0),
array(array('BAR','BAZ','ZIM',), 0),
array(array('BAR','BAZ','ZIM','GIR'), 0),
array(array('BAR','BAZ','ZIM','GIR','GIR'), 1),
);
}
}
class FakeComponent extends Component {
public function getValidationRules() {
return array(
'FOO' => '0',
'BAR' => '1',
'BAZ' => '+',
'ZIM' => '*',
'GIR' => '?',
);
}
public function getDefaults() {
return array(
'BAR' => 'yow',
);
}
}

View File

@@ -0,0 +1,417 @@
<?php
namespace Sabre\VObject;
use DateTime;
use DateTimeZone;
use DateInterval;
class DateTimeParserTest extends \PHPUnit_Framework_TestCase {
function testParseICalendarDuration() {
$this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true));
$this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D',true));
$this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true));
$this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true));
$this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true));
$this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S',true));
$this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S'));
}
function testParseICalendarDurationDateInterval() {
$expected = new DateInterval('P7D');
$this->assertEquals($expected, DateTimeParser::parseDuration('P1W'));
$this->assertEquals($expected, DateTimeParser::parse('P1W'));
$expected = new DateInterval('PT3M');
$expected->invert = true;
$this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M'));
}
/**
* @expectedException LogicException
*/
function testParseICalendarDurationFail() {
DateTimeParser::parseDuration('P1X',true);
}
function testParseICalendarDateTime() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405');
$compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
* @expectedException LogicException
*/
function testParseICalendarDateTimeBadFormat() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405 ');
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeUTC() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405Z');
$compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeUTC2() {
$dateTime = DateTimeParser::parseDateTime('20101211T160000Z');
$compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeCustomTimeZone() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
$compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('Europe/Amsterdam'));
$this->assertEquals($compare, $dateTime);
}
function testParseICalendarDate() {
$dateTime = DateTimeParser::parseDate('20100316');
$expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC'));
$this->assertEquals($expected, $dateTime);
$dateTime = DateTimeParser::parse('20100316');
$this->assertEquals($expected, $dateTime);
}
/**
* TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events
*/
function testParseICalendarDateGreaterThan4000() {
$dateTime = DateTimeParser::parseDate('45001231');
$expected = new DateTime('4500-12-31 00:00:00',new DateTimeZone('UTC'));
$this->assertEquals($expected, $dateTime);
$dateTime = DateTimeParser::parse('45001231');
$this->assertEquals($expected, $dateTime);
}
/**
* Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events
*/
function testParseICalendarDateTimeGreaterThan4000() {
$dateTime = DateTimeParser::parseDateTime('45001231T235959');
$expected = new DateTime('4500-12-31 23:59:59',new DateTimeZone('UTC'));
$this->assertEquals($expected, $dateTime);
$dateTime = DateTimeParser::parse('45001231T235959');
$this->assertEquals($expected, $dateTime);
}
/**
* @depends testParseICalendarDate
* @expectedException LogicException
*/
function testParseICalendarDateBadFormat() {
$dateTime = DateTimeParser::parseDate('20100316T141405');
}
/**
* @dataProvider vcardDates
*/
function testVCardDate($input, $output) {
$this->assertEquals(
$output,
DateTimeParser::parseVCardDateTime($input)
);
}
/**
* @dataProvider vcardDates
* @expectedException \InvalidArgumentException
*/
function testBadVCardDate() {
DateTimeParser::parseVCardDateTime('1985---01');
}
/**
* @dataProvider vcardDates
* @expectedException \InvalidArgumentException
*/
function testBadVCardTime() {
DateTimeParser::parseVCardTime('23:12:166');
}
function vcardDates() {
return array(
array(
"19961022T140000",
array(
"year" => 1996,
"month" => 10,
"date" => 22,
"hour" => 14,
"minute" => 00,
"second" => 00,
"timezone" => null
),
),
array(
"--1022T1400",
array(
"year" => null,
"month" => 10,
"date" => 22,
"hour" => 14,
"minute" => 00,
"second" => null,
"timezone" => null
),
),
array(
"---22T14",
array(
"year" => null,
"month" => null,
"date" => 22,
"hour" => 14,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"19850412",
array(
"year" => 1985,
"month" => 4,
"date" => 12,
"hour" => null,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"1985-04",
array(
"year" => 1985,
"month" => 04,
"date" => null,
"hour" => null,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"1985",
array(
"year" => 1985,
"month" => null,
"date" => null,
"hour" => null,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"--0412",
array(
"year" => null,
"month" => 4,
"date" => 12,
"hour" => null,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"---12",
array(
"year" => null,
"month" => null,
"date" => 12,
"hour" => null,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"T102200",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => 10,
"minute" => 22,
"second" => 0,
"timezone" => null
),
),
array(
"T1022",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => 10,
"minute" => 22,
"second" => null,
"timezone" => null
),
),
array(
"T10",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => 10,
"minute" => null,
"second" => null,
"timezone" => null
),
),
array(
"T-2200",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => null,
"minute" => 22,
"second" => 00,
"timezone" => null
),
),
array(
"T--00",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => null,
"minute" => null,
"second" => 00,
"timezone" => null
),
),
array(
"T102200Z",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => 10,
"minute" => 22,
"second" => 00,
"timezone" => 'Z'
),
),
array(
"T102200-0800",
array(
"year" => null,
"month" => null,
"date" => null,
"hour" => 10,
"minute" => 22,
"second" => 00,
"timezone" => '-0800'
),
),
// extended format
array(
"2012-11-29T15:10:53Z",
array(
"year" => 2012,
"month" => 11,
"date" => 29,
"hour" => 15,
"minute" => 10,
"second" => 53,
"timezone" => 'Z'
),
),
// with milliseconds
array(
"20121129T151053.123Z",
array(
"year" => 2012,
"month" => 11,
"date" => 29,
"hour" => 15,
"minute" => 10,
"second" => 53,
"timezone" => 'Z'
),
),
// extended format with milliseconds
array(
"2012-11-29T15:10:53.123Z",
array(
"year" => 2012,
"month" => 11,
"date" => 29,
"hour" => 15,
"minute" => 10,
"second" => 53,
"timezone" => 'Z'
),
),
);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Sabre\VObject;
class DocumentTest extends \PHPUnit_Framework_TestCase {
function testGetDocumentType() {
$doc = new MockDocument();
$this->assertEquals(Document::UNKNOWN, $doc->getDocumentType());
}
function testConstruct() {
$doc = new MockDocument('VLIST');
$this->assertEquals('VLIST', $doc->name);
}
function testCreateComponent() {
$vcal = new Component\VCalendar(array(), false);
$event = $vcal->createComponent('VEVENT');
$this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
$vcal->add($event);
$prop = $vcal->createProperty('X-PROP','1234256',array('X-PARAM' => '3'));
$this->assertInstanceOf('Sabre\VObject\Property', $prop);
$event->add($prop);
unset(
$event->DTSTAMP,
$event->UID
);
$out = $vcal->serialize();
$this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out);
}
function testCreate() {
$vcal = new Component\VCalendar(array(), false);
$event = $vcal->create('VEVENT');
$this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
$event = $vcal->create('CALSCALE');
$this->assertInstanceOf('Sabre\VObject\Property\Text', $event);
}
function testGetClassNameForPropertyValue() {
$vcal = new Component\VCalendar(array(), false);
$this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT'));
$this->assertNull($vcal->getClassNameForPropertyValue('FOO'));
}
}
class MockDocument extends Document {
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Sabre\VObject;
class ElementListTest extends \PHPUnit_Framework_TestCase {
function testIterate() {
$cal = new Component\VCalendar();
$sub = $cal->createComponent('VEVENT');
$elems = array(
$sub,
clone $sub,
clone $sub
);
$elemList = new ElementList($elems);
$count = 0;
foreach($elemList as $key=>$subcomponent) {
$count++;
$this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
}
$this->assertEquals(3,$count);
$this->assertEquals(2,$key);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Sabre\VObject;
class EmClientTest extends \PHPUnit_Framework_TestCase {
function testParseTz() {
$str = 'BEGIN:VCALENDAR
X-WR-CALNAME:Blackhawks Schedule 2011-12
X-APPLE-CALENDAR-COLOR:#E51717
X-WR-TIMEZONE:America/Chicago
CALSCALE:GREGORIAN
PRODID:-//eM Client/4.0.13961.0
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
DTSTART:20070311T020000
TZNAME:CDT
TZOFFSETTO:-0500
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
DTSTART:20071104T020000
TZNAME:CST
TZOFFSETTO:-0600
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20110624T181236Z
UID:be3bbfff-96e8-4c66-9908-ab791a62231d
DTEND;TZID="America/Chicago":20111008T223000
TRANSP:OPAQUE
SUMMARY:Stars @ Blackhawks (Home Opener)
DTSTART;TZID="America/Chicago":20111008T193000
DTSTAMP:20120330T013232Z
SEQUENCE:2
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
LAST-MODIFIED:20120330T013237Z
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR';
$vObject = Reader::read($str);
$dt = $vObject->VEVENT->DTSTART->getDateTime();
$this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Sabre\VObject;
class IssueEmptyParameterTest extends \PHPUnit_Framework_TestCase {
function testRead() {
$input = <<<VCF
BEGIN:VCARD
VERSION:2.1
N:Doe;Jon;;;
FN:Jon Doe
EMAIL;X-INTERN:foo@example.org
UID:foo
END:VCARD
VCF;
$vcard = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$vcard = $vcard->serialize();
$converted = Reader::read($vcard);
$converted->validate();
$this->assertTrue(isset($converted->EMAIL['X-INTERN']));
$version = Version::VERSION;
$expected = <<<VCF
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject $version//EN
N:Doe;Jon;;;
FN:Jon Doe
EMAIL;X-INTERN=:foo@example.org
UID:foo
END:VCARD
VCF;
$this->assertEquals($expected, str_replace("\r","", $vcard));
}
function testVCard21Parameter() {
$vcard = new Component\VCard(array(), false);
$vcard->VERSION = '2.1';
$vcard->PHOTO = 'random_stuff';
$vcard->PHOTO->add(null,'BASE64');
$vcard->UID = 'foo-bar';
$result = $vcard->serialize();
$expected = array(
"BEGIN:VCARD",
"VERSION:2.1",
"PHOTO;BASE64:" . base64_encode('random_stuff'),
"UID:foo-bar",
"END:VCARD",
"",
);
$this->assertEquals(implode("\r\n", $expected), $result);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Sabre\VObject;
/**
* This test is written for Issue 68:
*
* https://github.com/fruux/sabre-vobject/issues/68
*/
class EmptyValueIssueTest extends \PHPUnit_Framework_TestCase {
function testDecodeValue() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
DESCRIPTION:This is a descpription\\nwith a linebreak and a \\; \\, and :
END:VEVENT
END:VCALENDAR
ICS;
$vobj = Reader::read($input);
// Before this bug was fixed, getValue() would return nothing.
$this->assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue());
}
}

View File

@@ -0,0 +1,394 @@
<?php
namespace Sabre\VObject;
class FreeBusyGeneratorTest extends \PHPUnit_Framework_TestCase {
function getInput() {
$tests = array();
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T120000Z
DTEND:20110101T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T120000Z/20110101T130000Z"
);
// opaque, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar2
TRANSP:OPAQUE
DTSTART:20110101T130000Z
DTEND:20110101T140000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T130000Z/20110101T140000Z"
);
// transparent, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar3
TRANSP:TRANSPARENT
DTSTART:20110101T140000Z
DTEND:20110101T150000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
null,
);
// cancelled, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar4
STATUS:CANCELLED
DTSTART:20110101T160000Z
DTEND:20110101T170000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
null,
);
// tentative, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar5
STATUS:TENTATIVE
DTSTART:20110101T180000Z
DTEND:20110101T190000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
'20110101T180000Z/20110101T190000Z',
);
// outside of time-range, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar6
DTSTART:20110101T090000Z
DTEND:20110101T100000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
null,
);
// outside of time-range, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar7
DTSTART:20110104T090000Z
DTEND:20110104T100000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
null,
);
// using duration, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar8
DTSTART:20110101T190000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
'20110101T190000Z/20110101T200000Z',
);
// Day-long event, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar9
DTSTART;VALUE=DATE:20110102
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
'20110102T000000Z/20110103T000000Z',
);
// No duration, does not show up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar10
DTSTART:20110101T200000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
null,
);
// encoded as object, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar11
DTSTART:20110101T210000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
Reader::read($blob),
'20110101T210000Z/20110101T220000Z',
);
// Freebusy. Some parts show up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VFREEBUSY
FREEBUSY:20110103T010000Z/20110103T020000Z
FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z
FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z
FREEBUSY:20120101T000000Z/20120101T010000Z
FREEBUSY:20110103T050000Z/PT1H
END:VFREEBUSY
END:VCALENDAR
ICS;
$tests[] = array(
Reader::read($blob),
array(
'20110103T010000Z/20110103T020000Z',
'20110103T030000Z/20110103T040000Z',
'20110103T040000Z/20110103T050000Z',
'20110103T050000Z/20110103T060000Z',
)
);
// Yearly recurrence rule, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar13
DTSTART:20100101T220000Z
DTEND:20100101T230000Z
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
Reader::read($blob),
'20110101T220000Z/20110101T230000Z',
);
// Yearly recurrence rule + duration, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar14
DTSTART:20100101T230000Z
DURATION:PT1H
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
Reader::read($blob),
'20110101T230000Z/20110102T000000Z',
);
// Floating time, no timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T120000
DTEND:20110101T130000
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T120000Z/20110101T130000Z"
);
// Floating time + reference timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T120000
DTEND:20110101T130000
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T170000Z/20110101T180000Z",
new \DateTimeZone('America/Toronto')
);
// All-day event
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20110101
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T000000Z/20110102T000000Z"
);
// All-day event + reference timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20110101
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T050000Z/20110102T050000Z",
new \DateTimeZone('America/Toronto')
);
// Recurrence rule with no valid instances
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T100000Z
DTEND:20110103T120000Z
RRULE:FREQ=WEEKLY;COUNT=1
EXDATE:20110101T100000Z
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
array()
);
return $tests;
}
/**
* @dataProvider getInput
*/
function testGenerator($input, $expected, $timeZone = null) {
$gen = new FreeBusyGenerator(
new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')),
new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')),
$input,
$timeZone
);
$result = $gen->getResult();
$expected = (array)$expected;
$freebusy = $result->VFREEBUSY->select('FREEBUSY');
foreach($freebusy as $fb) {
$this->assertContains((string)$fb, $expected, "$fb did not appear in our list of expected freebusy strings. This is concerning!");
$k = array_search((string)$fb, $expected);
unset($expected[$k]);
}
$this->assertTrue(
count($expected) === 0,
'There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize()
);
}
function testGeneratorBaseObject() {
$obj = new Component\VCalendar();
$obj->METHOD = 'PUBLISH';
$gen = new FreeBusyGenerator();
$gen->setObjects(array());
$gen->setBaseObject($obj);
$result = $gen->getResult();
$this->assertEquals('PUBLISH', $result->METHOD->getValue());
}
/**
* @expectedException InvalidArgumentException
*/
function testInvalidArg() {
$gen = new FreeBusyGenerator(
new \DateTime('2012-01-01'),
new \DateTime('2012-12-31'),
new \StdClass()
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Sabre\VObject;
/**
* Google produces vcards with a weird escaping of urls.
*
* VObject will provide a workaround for this, so end-user still get expected
* values.
*/
class GoogleColonEscaping extends \PHPUnit_Framework_TestCase {
function testDecode() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:3.0
FN:Evert Pot
N:Pot;Evert;;;
EMAIL;TYPE=INTERNET;TYPE=WORK:evert@fruux.com
BDAY:1985-04-07
item7.URL:http\://www.rooftopsolutions.nl/
END:VCARD
VCF;
$vobj = Reader::read($vcard);
$this->assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue());
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Sabre\VObject\ICalendar;
use Sabre\VObject\Reader;
class AttachParseTest extends \PHPUnit_Framework_TestCase {
/**
* See issue #128 for more info.
*/
function testParseAttach() {
$vcal = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/reports/r-960812.ps
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($vcal);
$prop = $vcal->VEVENT->ATTACH;
$this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop);
$this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
<?php
namespace Sabre\VObject\ITip;
class BrokerDeleteEventTest extends BrokerTester {
function testOrganizerDeleteWithDtend() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = null;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testOrganizerDeleteWithDuration() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = null;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DURATION:PT1H
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DURATION:PT1H
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testAttendeeDeleteWithDtend() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = null;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REPLY',
'component' => 'VEVENT',
'sender' => 'mailto:one@example.org',
'senderName' => 'One',
'recipient' => 'mailto:strunk@example.org',
'recipientName' => 'Strunk',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
}
function testAttendeeDeleteWithDuration() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = null;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REPLY',
'component' => 'VEVENT',
'sender' => 'mailto:one@example.org',
'senderName' => 'One',
'recipient' => 'mailto:strunk@example.org',
'recipientName' => 'Strunk',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DURATION:PT1H
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
}
function testAttendeeDeleteCancelledEvent() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
STATUS:CANCELLED
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = null;
$version = \Sabre\VObject\Version::VERSION;
$expected = array();
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
}
function testNoCalendar() {
$this->parse(null, null, array(), 'mailto:one@example.org');
}
function testVTodo() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VTODO
UID:foobar
SEQUENCE:1
END:VTODO
END:VCALENDAR
ICS;
$this->parse($oldMessage, null, array(), 'mailto:one@example.org');
}
}

View File

@@ -0,0 +1,528 @@
<?php
namespace Sabre\VObject\ITip;
class BrokerNewEventTest extends \PHPUnit_Framework_TestCase {
function testNoAttendee() {
$message = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20140811T220000Z
DTEND:20140811T230000Z
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->parse($message);
}
function testVTODO() {
$message = <<<ICS
BEGIN:VCALENDAR
BEGIN:VTODO
UID:foobar
END:VTODO
END:VCALENDAR
ICS;
$result = $this->parse($message);
}
function testSimpleInvite() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
DTSTART:20140811T220000Z
DTEND:20140811T230000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White:mailto:white@example.org
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expectedMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
DTSTART:20140811T220000Z
DTEND:20140811T230000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White;PARTSTAT=NEEDS-ACTION:mailto:white@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:white@example.org',
'recipientName' => 'White',
'message' => $expectedMessage,
),
);
$result = $this->parse($message, $expected);
}
/**
* @expectedException \Sabre\VObject\ITip\ITipException
*/
function testBrokenEventUIDMisMatch() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White:mailto:white@example.org
END:VEVENT
BEGIN:VEVENT
UID:foobar2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White:mailto:white@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = array();
$this->parse($message, array());
}
/**
* @expectedException \Sabre\VObject\ITip\ITipException
*/
function testBrokenEventOrganizerMisMatch() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White:mailto:white@example.org
END:VEVENT
BEGIN:VEVENT
UID:foobar
ORGANIZER:mailto:foo@example.org
ATTENDEE;CN=White:mailto:white@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = array();
$this->parse($message, array());
}
function testRecurrenceInvite() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY
EXDATE:20140717T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
DTSTART:20140716T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY
EXDATE:20140717T120000Z,20140718T120000Z
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
DTSTART:20140716T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY
EXDATE:20140717T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:three@example.org',
'recipientName' => 'Three',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($message, $expected);
}
function testRecurrenceInvite2() {
// This method tests a nearly identical path, but in this case the
// master event does not have an EXDATE.
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DTEND:20140718T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
RRULE:FREQ=DAILY
EXDATE:20140718T120000Z
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DTEND:20140718T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:three@example.org',
'recipientName' => 'Three',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DTEND:20140718T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($message, $expected);
}
function testScheduleAgentClient() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
DTSTART:20140811T220000Z
DTEND:20140811T230000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=White;SCHEDULE-AGENT=CLIENT:mailto:white@example.org
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array();
$result = $this->parse($message, $expected);
}
/**
* @expectedException Sabre\VObject\ITip\ITipException
*/
function testMultipleUID() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:foobar2
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DTEND:20140718T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$result = $this->parse($message, array());
}
/**
* @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException
*
*/
function testChangingOrganizers() {
$message = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140718T120000Z
ORGANIZER;CN=Strunk:mailto:ew@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140718T120000Z
DTEND:20140718T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$result = $this->parse($message, array());
}
function testNoOrganizerHasAttendee() {
$message = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20140811T220000Z
DTEND:20140811T230000Z
ATTENDEE;CN=Two:mailto:two@example.org
END:VEVENT
END:VCALENDAR
ICS;
$this->parse($message, array());
}
function parse($message, $expected = array()) {
$broker = new Broker();
$result = $broker->parseEvent($message, 'mailto:strunk@example.org');
$this->assertEquals(count($expected), count($result));
foreach($expected as $index=>$ex) {
$message = $result[$index];
foreach($ex as $key=>$val) {
if ($key==='message') {
$this->assertEquals(
str_replace("\n", "\r\n", $val),
rtrim($message->message->serialize(), "\r\n")
);
} else {
$this->assertEquals($val, $message->$key);
}
}
}
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace Sabre\VObject\ITip;
class BrokerProcessMessageTest extends BrokerTester {
function testRequestNew() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
SEQUENCE:1
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
%foo%
BEGIN:VEVENT
SEQUENCE:1
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, null, $expected);
}
function testRequestUpdate() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
%foo%
BEGIN:VEVENT
SEQUENCE:1
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
%foo%
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testCancel() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:CANCEL
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
%foo%
BEGIN:VEVENT
SEQUENCE:1
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
%foo%
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
STATUS:CANCELLED
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testCancelNoExistingEvent() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:CANCEL
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = null;
$expected = null;
$result = $this->process($itip, $old, $expected);
}
function testUnsupportedComponent() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VTODO
SEQUENCE:2
UID:foobar
END:VTODO
END:VCALENDAR
ICS;
$old = null;
$expected = null;
$result = $this->process($itip, $old, $expected);
}
function testUnsupportedMethod() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = null;
$expected = null;
$result = $this->process($itip, $old, $expected);
}
}

View File

@@ -0,0 +1,496 @@
<?php
namespace Sabre\VObject\ITip;
class BrokerProcessReplyTest extends BrokerTester {
function testReplyNoOriginal() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$old = null;
$expected = null;
$result = $this->process($itip, $old, $expected);
}
function testReplyAccept() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyRequestStatus() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
UID:foobar
REQUEST-STATUS:2.3;foo-bar!
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.3:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyPartyCrasher() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyNewException() {
// This is a reply to 1 instance of a recurring event. This should
// automatically create an exception.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140725T000000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART:20140725T000000Z
DTEND:20140725T010000Z
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID:20140725T000000Z
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyNewExceptionTz() {
// This is a reply to 1 instance of a recurring event. This should
// automatically create an exception.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART;TZID=America/Toronto:20140725T000000
DTEND;TZID=America/Toronto:20140725T010000
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyPartyCrashCreateExcepton() {
// IN this test there's a recurring event that has an exception. The
// exception is missing the attendee.
//
// The attendee party crashes the instance, so it should show up in the
// resulting object.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140725T000000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART:20140725T000000Z
DTEND:20140725T010000Z
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID:20140725T000000Z
ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyNewExceptionNoMasterEvent() {
/**
* This iTip message would normally create a new exception, but the
* server is not able to create this new instance, because there's no
* master event to clone from.
*
* This test checks if the message is ignored.
*/
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140725T000000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
RECURRENCE-ID:20140724T000000Z
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = null;
$result = $this->process($itip, $old, $expected);
}
/**
* @depends testReplyAccept
*/
function testReplyAcceptUpdateRSVP() {
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE;RSVP=TRUE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
function testReplyNewExceptionFirstOccurence() {
// This is a reply to 1 instance of a recurring event. This should
// automatically create an exception.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140724T000000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;
$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;
$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART:20140724T000000Z
DTEND:20140724T010000Z
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID:20140724T000000Z
END:VEVENT
END:VCALENDAR
ICS;
$result = $this->process($itip, $old, $expected);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Sabre\VObject\ITip;
use Sabre\VObject\Reader;
/**
* Utilities for testing the broker
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class BrokerTester extends \Sabre\VObject\TestCase {
function parse($oldMessage, $newMessage, $expected = array(), $currentUser = 'mailto:one@example.org') {
$broker = new Broker();
$result = $broker->parseEvent($newMessage, $currentUser, $oldMessage);
$this->assertEquals(count($expected), count($result));
foreach($expected as $index=>$ex) {
$message = $result[$index];
foreach($ex as $key=>$val) {
if ($key==='message') {
$this->assertVObjEquals(
$val,
$message->message->serialize()
);
} else {
$this->assertEquals($val, $message->$key);
}
}
}
}
function process($input, $existingObject = null, $expected = false) {
$version = \Sabre\VObject\Version::VERSION;
$vcal = Reader::read($input);
foreach($vcal->getComponents() as $mainComponent) {
break;
}
$message = new Message();
$message->message = $vcal;
$message->method = isset($vcal->METHOD)?$vcal->METHOD->getValue():null;
$message->component = $mainComponent->name;
$message->uid = $mainComponent->uid->getValue();
$message->sequence = isset($vcal->VEVENT[0])?(string)$vcal->VEVENT[0]->SEQUENCE:null;
if ($message->method === 'REPLY') {
$message->sender = $mainComponent->ATTENDEE->getValue();
$message->senderName = isset($mainComponent->ATTENDEE['CN'])?$mainComponent->ATTENDEE['CN']->getValue():null;
$message->recipient = $mainComponent->ORGANIZER->getValue();
$message->recipientName = isset($mainComponent->ORGANIZER['CN'])?$mainComponent->ORGANIZER['CN']:null;
}
$broker = new Broker();
if (is_string($existingObject)) {
$existingObject = str_replace(
'%foo%',
"VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN",
$existingObject
);
$existingObject = Reader::read($existingObject);
}
$result = $broker->processMessage($message, $existingObject);
if (is_string($expected)) {
$expected = str_replace(
'%foo%',
"VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN",
$expected
);
$expected = str_replace("\n", "\r\n", $expected);
}
if ($result instanceof \Sabre\VObject\Component\VCalendar) {
$result = $result->serialize();
$result = rtrim($result,"\r\n");
}
$this->assertEquals(
$expected,
$result
);
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Sabre\VObject\ITip;
use Sabre\VObject\Reader;
class BrokerTimezoneInParseEventInfoWithoutMasterTest extends \PHPUnit_Framework_TestCase {
function testTimezoneInParseEventInfoWithoutMaster()
{
$calendar = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Europe/Minsk
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
RRULE:FREQ=YEARLY;UNTIL=20100328T000000Z;BYMONTH=3;BYDAY=-1SU
DTSTART:19930328T020000
TZNAME:GMT+3
TZOFFSETTO:+0300
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
DTSTART:20110327T020000
TZNAME:GMT+3
TZOFFSETTO:+0300
RDATE:20110327T020000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20160331T163031Z
UID:B9301437-417C-4136-8DB3-8D1555863791
DTEND;TZID=Europe/Minsk:20160405T100000
TRANSP:OPAQUE
ATTENDEE;CN=User Invitee;CUTYPE=INDIVIDUAL;EMAIL=invitee@test.com;PARTSTAT=
ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:invitee@test.com
ATTENDEE;CN=User Organizer;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:organ
izer@test.com
SUMMARY:Event title
DTSTART;TZID=Europe/Minsk:20160405T090000
DTSTAMP:20160331T164108Z
ORGANIZER;CN=User Organizer:mailto:organizer@test.com
SEQUENCE:6
RECURRENCE-ID;TZID=Europe/Minsk:20160405T090000
END:VEVENT
BEGIN:VEVENT
CREATED:20160331T163031Z
UID:B9301437-417C-4136-8DB3-8D1555863791
DTEND;TZID=Europe/Minsk:20160406T100000
TRANSP:OPAQUE
ATTENDEE;CN=User Invitee;CUTYPE=INDIVIDUAL;EMAIL=invitee@test.com;PARTSTAT=
ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:invitee@test.com
ATTENDEE;CN=User Organizer;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:organ
izer@test.com
SUMMARY:Event title
DTSTART;TZID=Europe/Minsk:20160406T090000
DTSTAMP:20160331T165845Z
ORGANIZER;CN=User Organizer:mailto:organizer@test.com
SEQUENCE:6
RECURRENCE-ID;TZID=Europe/Minsk:20160406T090000
END:VEVENT
END:VCALENDAR
ICS;
$calendar = Reader::read($calendar);
$broker = new Broker();
$reflectionMethod = new \ReflectionMethod($broker, 'parseEventInfo');
$reflectionMethod->setAccessible(true);
$data = $reflectionMethod->invoke($broker, $calendar);
$this->assertInstanceOf('DateTimeZone', $data['timezone']);
$this->assertEquals($data['timezone']->getName(), 'Europe/Minsk');
}
}

View File

@@ -0,0 +1,841 @@
<?php
namespace Sabre\VObject\ITip;
class BrokerUpdateTest extends BrokerTester {
function testInviteChange() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'significantChange' => false,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:three@example.org',
'recipientName' => 'Three',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteChangeFromNonSchedulingToSchedulingObject() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteChangeFromSchedulingToNonSchedulingObject() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testNoAttendees() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array();
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testRemoveInstance() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART;TZID=America/Toronto:20140716T120000
DTEND;TZID=America/Toronto:20140716T130000
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART;TZID=America/Toronto:20140716T120000
DTEND;TZID=America/Toronto:20140716T130000
RRULE:FREQ=WEEKLY
EXDATE;TZID=America/Toronto:20140724T120000
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
DTSTART;TZID=America/Toronto:20140716T120000
DTEND;TZID=America/Toronto:20140716T130000
RRULE:FREQ=WEEKLY
EXDATE;TZID=America/Toronto:20140724T120000
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
/**
* This test is identical to the first test, except this time we change the
* DURATION property.
*
* This should ensure that the message is significant for every attendee,
*/
function testInviteChangeSignificantChange() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
DURATION:PT1H
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
DURATION:PT2H
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
ATTENDEE;CN=Three:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
DURATION:PT2H
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:three@example.org',
'recipientName' => 'Three',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
DURATION:PT2H
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org
ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteNoChange() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => false,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteNoChangeForceSend() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;SCHEDULE-FORCE-SEND=REQUEST;CN=One:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org
ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteRemoveAttendees() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
SUMMARY:foo
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
ATTENDEE;CN=Two:mailto:two@example.org
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
array(
'uid' => 'foobar',
'method' => 'CANCEL',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:two@example.org',
'recipientName' => 'Two',
'significantChange' => true,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:foobar
SEQUENCE:2
SUMMARY:foo
DTSTART:20140716T120000Z
DTEND:20140716T130000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Two:mailto:two@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
function testInviteChangeExdateOrder() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:foobar
SEQUENCE:0
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE
PTED:mailto:strunk@example.org
ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R
OLE=REQ-PARTICIPANT;SCHEDULE-STATUS="1.2;Message delivered locally":mailto
:one@example.org
SUMMARY:foo
DTSTART:20141211T160000Z
DTEND:20141211T170000Z
RRULE:FREQ=WEEKLY
EXDATE:20141225T160000Z,20150101T160000Z
EXDATE:20150108T160000Z
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE
PTED:mailto:strunk@example.org
ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R
OLE=REQ-PARTICIPANT;SCHEDULE-STATUS=1.2:mailto:one@example.org
DTSTART:20141211T160000Z
DTEND:20141211T170000Z
RRULE:FREQ=WEEKLY
EXDATE:20150101T160000Z
EXDATE:20150108T160000Z,20141225T160000Z
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REQUEST',
'component' => 'VEVENT',
'sender' => 'mailto:strunk@example.org',
'senderName' => 'Strunk',
'recipient' => 'mailto:one@example.org',
'recipientName' => 'One',
'significantChange' => false,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE
PTED:mailto:strunk@example.org
ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R
OLE=REQ-PARTICIPANT:mailto:one@example.org
DTSTART:20141211T160000Z
DTEND:20141211T170000Z
RRULE:FREQ=WEEKLY
EXDATE:20150101T160000Z
EXDATE:20150108T160000Z,20141225T160000Z
END:VEVENT
END:VCALENDAR
ICS
),
);
$this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
<?php
namespace Sabre\VObject\ITip;
class MessageTest extends \PHPUnit_Framework_TestCase {
public function testNoScheduleStatus() {
$message = new Message();
$this->assertFalse($message->getScheduleStatus());
}
public function testScheduleStatus() {
$message = new Message();
$message->scheduleStatus = '1.2;Delivered';
$this->assertEquals('1.2', $message->getScheduleStatus());
}
public function testUnexpectedScheduleStatus() {
$message = new Message();
$message->scheduleStatus = '9.9.9';
$this->assertEquals('9.9.9', $message->getScheduleStatus());
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Sabre\VObject;
class Issue153Test extends \PHPUnit_Framework_TestCase {
function testRead() {
$obj = Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf'));
$this->assertEquals('Test Benutzer', (string)$obj->fn);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Sabre\VObject;
use
DateTime,
DateTimeZone;
class Issue26Test extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \InvalidArgumentException
*/
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:bae5d57a98
RRULE:FREQ=MONTHLY;BYDAY=0MO,0TU,0WE,0TH,0FR;INTERVAL=1
DTSTART;VALUE=DATE:20130401
DTEND;VALUE=DATE:20130402
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$it = new Recur\EventIterator($vcal, 'bae5d57a98');
iterator_to_array($it);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Sabre\VObject;
class Issue36WorkAroundTest extends \PHPUnit_Framework_TestCase {
function testWorkaround() {
// See https://github.com/fruux/sabre-vobject/issues/36
$event = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Titel
SEQUENCE:1
TRANSP:TRANSPARENT
RRULE:FREQ=YEARLY
LAST-MODIFIED:20130323T225737Z
DTSTAMP:20130323T225737Z
UID:1833bd44-188b-405c-9f85-1a12105318aa
CATEGORIES:Jubiläum
X-MOZ-GENERATION:3
RECURRENCE-ID;RANGE=THISANDFUTURE;VALUE=DATE:20131013
DTSTART;VALUE=DATE:20131013
CREATED:20100721T121914Z
DURATION:P1D
END:VEVENT
END:VCALENDAR
ICS;
$obj = Reader::read($event);
// If this does not throw an exception, it's all good.
$it = new Recur\EventIterator($obj,'1833bd44-188b-405c-9f85-1a12105318aa');
$this->assertInstanceOf('Sabre\\VObject\\Recur\EventIterator', $it);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Sabre\VObject;
/**
* This test is created to handle the issues brought forward by issue 40.
*
* https://github.com/fruux/sabre-vobject/issues/40
*/
class Issue40Test extends \PHPUnit_Framework_TestCase {
function testEncode() {
$card = new Component\VCard();
$card->add('N', array('van der Harten', array('Rene','J.'), "", 'Sir','R.D.O.N.'), array('SORT-AS' => array('Harten','Rene')));
$expected = implode("\r\n", array(
"BEGIN:VCARD",
"VERSION:3.0",
"PRODID:-//Sabre//Sabre VObject " . Version::VERSION . '//EN',
"N;SORT-AS=Harten,Rene:van der Harten;Rene,J.;;Sir;R.D.O.N.",
"END:VCARD",
""
));
$this->assertEquals($expected, $card->serialize());
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Sabre\VObject;
class Issue64Test extends \PHPUnit_Framework_TestCase {
function testRead() {
$vcard = Reader::read(file_get_contents(dirname(__FILE__) . '/issue64.vcf'));
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$vcard = $vcard->serialize();
$converted = Reader::read($vcard);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Sabre\VObject;
class Issue96Test extends \PHPUnit_Framework_TestCase {
function testRead() {
$input = <<<VCF
BEGIN:VCARD
VERSION:2.1
SOURCE:Yahoo Contacts (http://contacts.yahoo.com)
URL;CHARSET=utf-8;ENCODING=QUOTED-PRINTABLE:=
http://www.example.org
END:VCARD
VCF;
$vcard = Reader::read($input, Reader::OPTION_FORGIVING);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
$this->assertEquals("http://www.example.org", $vcard->url->getValue());
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Sabre\VObject;
class JCalTest extends \PHPUnit_Framework_TestCase {
function testToJCal() {
$cal = new Component\VCalendar();
$event = $cal->add('VEVENT', array(
"UID" => "foo",
"DTSTART" => new \DateTime("2013-05-26 18:10:00Z"),
"DURATION" => "P1D",
"CATEGORIES" => array('home', 'testing'),
"CREATED" => new \DateTime("2013-05-26 18:10:00Z"),
"ATTENDEE" => "mailto:armin@example.org",
"GEO" => array(51.96668, 7.61876),
"SEQUENCE" => 5,
"FREEBUSY" => array("20130526T210213Z/PT1H", "20130626T120000Z/20130626T130000Z"),
"URL" => "http://example.org/",
"TZOFFSETFROM" => "+05:00",
"RRULE" => array('FREQ' => 'WEEKLY', 'BYDAY' => array('MO','TU')),
));
// Modifying DTSTART to be a date-only.
$event->dtstart['VALUE'] = 'DATE';
$event->add("X-BOOL", true, array('VALUE' => 'BOOLEAN'));
$event->add("X-TIME", "08:00:00", array('VALUE' => 'TIME'));
$event->add("ATTACH", "attachment", array('VALUE' => 'BINARY'));
$event->add("ATTENDEE", "mailto:dominik@example.org", array("CN" => "Dominik", "PARTSTAT" => "DECLINED"));
$event->add('REQUEST-STATUS', array("2.0", "Success"));
$event->add('REQUEST-STATUS', array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"));
$event->add('DTEND', '20150108T133000');
$expected = array(
"vcalendar",
array(
array(
"version",
new \StdClass(),
"text",
"2.0"
),
array(
"prodid",
new \StdClass(),
"text",
"-//Sabre//Sabre VObject " . Version::VERSION . "//EN",
),
array(
"calscale",
new \StdClass(),
"text",
"GREGORIAN"
),
),
array(
array("vevent",
array(
array(
"uid", new \StdClass(), "text", "foo",
),
array(
"dtstart", new \StdClass(), "date", "2013-05-26",
),
array(
"duration", new \StdClass(), "duration", "P1D",
),
array(
"categories", new \StdClass(), "text", "home", "testing",
),
array(
"created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z",
),
array(
"attendee", new \StdClass(), "cal-address", "mailto:armin@example.org",
),
array(
"geo", new \StdClass(), "float", array(51.96668, 7.61876),
),
array(
"sequence", new \StdClass(), "integer", 5
),
array(
"freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"),
),
array(
"url", new \StdClass(), "uri", "http://example.org/",
),
array(
"tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00",
),
array(
"rrule", new \StdClass(), "recur", array(
'freq' => 'WEEKLY',
'byday' => array('MO', 'TU'),
),
),
array(
"x-bool", new \StdClass(), "boolean", true
),
array(
"x-time", new \StdClass(), "time", "08:00:00",
),
array(
"attach", new \StdClass(), "binary", base64_encode('attachment')
),
array(
"attendee",
(object)array(
"cn" => "Dominik",
"partstat" => "DECLINED",
),
"cal-address",
"mailto:dominik@example.org"
),
array(
"request-status",
new \StdClass(),
"text",
array("2.0", "Success"),
),
array(
"request-status",
new \StdClass(),
"text",
array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"),
),
array(
'dtend',
new \StdClass(),
"date-time",
"2015-01-08T13:30:00",
),
),
array(),
)
),
);
$this->assertEquals($expected, $cal->jsonSerialize());
}
}

View File

@@ -0,0 +1,195 @@
<?php
namespace Sabre\VObject;
class JCardTest extends \PHPUnit_Framework_TestCase {
function testToJCard() {
$card = new Component\VCard(array(
"VERSION" => "4.0",
"UID" => "foo",
"BDAY" => "19850407",
"REV" => "19951031T222710Z",
"LANG" => "nl",
"N" => array("Last", "First", "Middle", "", ""),
"item1.TEL" => "+1 555 123456",
"item1.X-AB-LABEL" => "Walkie Talkie",
"ADR" => array(
"",
"",
array("My Street", "Left Side", "Second Shack"),
"Hometown",
"PA",
"18252",
"U.S.A",
),
));
$card->add('BDAY', '1979-12-25', array('VALUE' => 'DATE', 'X-PARAM' => array(1,2)));
$card->add('BDAY', '1979-12-25T02:00:00', array('VALUE' => 'DATE-TIME'));
$card->add('X-TRUNCATED', '--1225', array('VALUE' => 'DATE'));
$card->add('X-TIME-LOCAL', '123000', array('VALUE' => 'TIME'));
$card->add('X-TIME-UTC', '12:30:00Z', array('VALUE' => 'TIME'));
$card->add('X-TIME-OFFSET', '12:30:00-08:00', array('VALUE' => 'TIME'));
$card->add('X-TIME-REDUCED', '23', array('VALUE' => 'TIME'));
$card->add('X-TIME-TRUNCATED', '--30', array('VALUE' => 'TIME'));
$card->add('X-KARMA-POINTS', '42', array('VALUE' => 'INTEGER'));
$card->add('X-GRADE', '1.3', array('VALUE' => 'FLOAT'));
$card->add('TZ', '-05:00', array('VALUE' => 'UTC-OFFSET'));
$expected = array(
"vcard",
array(
array(
"version",
new \StdClass(),
"text",
"4.0"
),
array(
"prodid",
new \StdClass(),
"text",
"-//Sabre//Sabre VObject " . Version::VERSION . "//EN",
),
array(
"uid",
new \StdClass(),
"text",
"foo",
),
array(
"bday",
new \StdClass(),
"date-and-or-time",
"1985-04-07",
),
array(
"rev",
new \StdClass(),
"timestamp",
"1995-10-31T22:27:10Z",
),
array(
"lang",
new \StdClass(),
"language-tag",
"nl",
),
array(
"n",
new \StdClass(),
"text",
array("Last", "First", "Middle", "", ""),
),
array(
"tel",
(object)array(
"group" => "item1",
),
"text",
"+1 555 123456",
),
array(
"x-ab-label",
(object)array(
"group" => "item1",
),
"unknown",
"Walkie Talkie",
),
array(
"adr",
new \StdClass(),
"text",
array(
"",
"",
array("My Street", "Left Side", "Second Shack"),
"Hometown",
"PA",
"18252",
"U.S.A",
),
),
array(
"bday",
(object)array(
'x-param' => array(1,2),
),
"date",
"1979-12-25",
),
array(
"bday",
new \StdClass(),
"date-time",
"1979-12-25T02:00:00",
),
array(
"x-truncated",
new \StdClass(),
"date",
"--12-25",
),
array(
"x-time-local",
new \StdClass(),
"time",
"12:30:00"
),
array(
"x-time-utc",
new \StdClass(),
"time",
"12:30:00Z"
),
array(
"x-time-offset",
new \StdClass(),
"time",
"12:30:00-08:00"
),
array(
"x-time-reduced",
new \StdClass(),
"time",
"23"
),
array(
"x-time-truncated",
new \StdClass(),
"time",
"--30"
),
array(
"x-karma-points",
new \StdClass(),
"integer",
42
),
array(
"x-grade",
new \StdClass(),
"float",
1.3
),
array(
"tz",
new \StdClass(),
"utc-offset",
"-05:00",
),
),
);
$this->assertEquals($expected, $card->jsonSerialize());
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Sabre\VObject;
class LineFoldingIssueTest extends \PHPUnit_Framework_TestCase {
function testRead() {
$event = <<<ICS
BEGIN:VCALENDAR\r
BEGIN:VEVENT\r
DESCRIPTION:TEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\nTEST\\nTEST, TEST\r
END:VEVENT\r
END:VCALENDAR\r
ICS;
$obj = Reader::read($event);
$this->assertEquals($event, $obj->serialize());
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Sabre\VObject;
class ParameterTest extends \PHPUnit_Framework_TestCase {
function testSetup() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name','value');
$this->assertEquals('NAME',$param->name);
$this->assertEquals('value',$param->getValue());
}
function testSetupNameLess() {
$card = new Component\VCard();
$param = new Parameter($card, null,'URL');
$this->assertEquals('VALUE',$param->name);
$this->assertEquals('URL',$param->getValue());
$this->assertTrue($param->noName);
}
function testModify() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', null);
$param->addValue(1);
$this->assertEquals(array(1), $param->getParts());
$param->setParts(array(1,2));
$this->assertEquals(array(1,2), $param->getParts());
$param->addValue(3);
$this->assertEquals(array(1,2,3), $param->getParts());
$param->setValue(4);
$param->addValue(5);
$this->assertEquals(array(4,5), $param->getParts());
}
function testCastToString() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', 'value');
$this->assertEquals('value',$param->__toString());
$this->assertEquals('value',(string)$param);
}
function testCastNullToString() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', null);
$this->assertEquals('',$param->__toString());
$this->assertEquals('',(string)$param);
}
function testSerialize() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', 'value');
$this->assertEquals('NAME=value',$param->serialize());
}
function testSerializeEmpty() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', null);
$this->assertEquals('NAME=',$param->serialize());
}
function testSerializeComplex() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name',array("val1", "val2;", "val3^", "val4\n", "val5\""));
$this->assertEquals('NAME=val1,"val2;","val3^^","val4^n","val5^\'"',$param->serialize());
}
/**
* iCal 7.0 (OSX 10.9) has major issues with the EMAIL property, when the
* value contains a plus sign, and it's not quoted.
*
* So we specifically added support for that.
*/
function testSerializePlusSign() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'EMAIL',"user+something@example.org");
$this->assertEquals('EMAIL="user+something@example.org"',$param->serialize());
}
function testIterate() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name', array(1,2,3,4));
$result = array();
foreach($param as $value) {
$result[] = $value;
}
$this->assertEquals(array(1,2,3,4), $result);
}
function testSerializeColon() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name','va:lue');
$this->assertEquals('NAME="va:lue"',$param->serialize());
}
function testSerializeSemiColon() {
$cal = new Component\VCalendar();
$param = new Parameter($cal, 'name','va;lue');
$this->assertEquals('NAME="va;lue"',$param->serialize());
}
}

View File

@@ -0,0 +1,395 @@
<?php
namespace Sabre\VObject\Parser;
use
Sabre\VObject;
class JsonTest extends \PHPUnit_Framework_TestCase {
function testRoundTripJCard() {
$input = array(
"vcard",
array(
array(
"version",
new \StdClass(),
"text",
"4.0"
),
array(
"prodid",
new \StdClass(),
"text",
"-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN",
),
array(
"uid",
new \StdClass(),
"text",
"foo",
),
array(
"bday",
new \StdClass(),
"date-and-or-time",
"1985-04-07",
),
array(
"rev",
new \StdClass(),
"timestamp",
"1995-10-31T22:27:10Z",
),
array(
"lang",
new \StdClass(),
"language-tag",
"nl",
),
array(
"n",
new \StdClass(),
"text",
array("Last", "First", "Middle", "", ""),
),
array(
"tel",
(object)array(
"group" => "item1",
),
"text",
"+1 555 123456",
),
array(
"x-ab-label",
(object)array(
"group" => "item1",
),
"unknown",
"Walkie Talkie",
),
array(
"adr",
new \StdClass(),
"text",
array(
"",
"",
array("My Street", "Left Side", "Second Shack"),
"Hometown",
"PA",
"18252",
"U.S.A",
),
),
array(
"bday",
(object)array(
'x-param' => array(1,2),
),
"date",
"1979-12-25",
),
array(
"bday",
new \StdClass(),
"date-time",
"1979-12-25T02:00:00",
),
array(
"x-truncated",
new \StdClass(),
"date",
"--12-25",
),
array(
"x-time-local",
new \StdClass(),
"time",
"12:30:00"
),
array(
"x-time-utc",
new \StdClass(),
"time",
"12:30:00Z"
),
array(
"x-time-offset",
new \StdClass(),
"time",
"12:30:00-08:00"
),
array(
"x-time-reduced",
new \StdClass(),
"time",
"23"
),
array(
"x-time-truncated",
new \StdClass(),
"time",
"--30"
),
array(
"x-karma-points",
new \StdClass(),
"integer",
42
),
array(
"x-grade",
new \StdClass(),
"float",
1.3
),
array(
"tz",
new \StdClass(),
"utc-offset",
"-05:00",
),
),
);
$parser = new Json(json_encode($input));
$vobj = $parser->parse();
$version = VObject\Version::VERSION;
$result = $vobj->serialize();
$expected = <<<VCF
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject $version//EN
UID:foo
BDAY:1985-04-07
REV:1995-10-31T22:27:10Z
LANG:nl
N:Last;First;Middle;;
item1.TEL:+1 555 123456
item1.X-AB-LABEL:Walkie Talkie
ADR:;;My Street,Left Side,Second Shack;Hometown;PA;18252;U.S.A
BDAY;X-PARAM=1,2;VALUE=DATE:1979-12-25
BDAY;VALUE=DATE-TIME:1979-12-25T02:00:00
X-TRUNCATED;VALUE=DATE:--12-25
X-TIME-LOCAL;VALUE=TIME:12:30:00
X-TIME-UTC;VALUE=TIME:12:30:00Z
X-TIME-OFFSET;VALUE=TIME:12:30:00-08:00
X-TIME-REDUCED;VALUE=TIME:23
X-TIME-TRUNCATED;VALUE=TIME:--30
X-KARMA-POINTS;VALUE=INTEGER:42
X-GRADE;VALUE=FLOAT:1.3
TZ;VALUE=UTC-OFFSET:-05:00
END:VCARD
VCF;
$this->assertEquals($expected, str_replace("\r", "", $result));
$this->assertEquals(
$input,
$vobj->jsonSerialize()
);
}
function testRoundTripJCal() {
$input = array(
"vcalendar",
array(
array(
"version",
new \StdClass(),
"text",
"2.0"
),
array(
"prodid",
new \StdClass(),
"text",
"-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN",
),
array(
"calscale",
new \StdClass(),
"text",
"GREGORIAN"
),
),
array(
array("vevent",
array(
array(
"uid", new \StdClass(), "text", "foo",
),
array(
"dtstart", new \StdClass(), "date", "2013-05-26",
),
array(
"duration", new \StdClass(), "duration", "P1D",
),
array(
"categories", new \StdClass(), "text", "home", "testing",
),
array(
"created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z",
),
array(
"attach", new \StdClass(), "binary", base64_encode('attachment')
),
array(
"attendee", new \StdClass(), "cal-address", "mailto:armin@example.org",
),
array(
"geo", new \StdClass(), "float", array(51.96668, 7.61876),
),
array(
"sequence", new \StdClass(), "integer", 5
),
array(
"freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"),
),
array(
"url", new \StdClass(), "uri", "http://example.org/",
),
array(
"tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00",
),
array(
"rrule", new \StdClass(), "recur", array(
'freq' => 'WEEKLY',
'byday' => array('MO', 'TU'),
),
),
array(
"x-bool", new \StdClass(), "boolean", true
),
array(
"x-time", new \StdClass(), "time", "08:00:00",
),
array(
"attendee",
(object)array(
"cn" => "Dominik",
"partstat" => "DECLINED",
),
"cal-address",
"mailto:dominik@example.org"
),
array(
"request-status",
new \StdClass(),
"text",
array("2.0", "Success"),
),
array(
"request-status",
new \StdClass(),
"text",
array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"),
),
),
array(
array("valarm",
array(
array(
"action", new \StdClass(), "text", "DISPLAY",
),
),
array(),
),
),
)
),
);
$parser = new Json(json_encode($input));
$vobj = $parser->parse();
$result = $vobj->serialize();
$version = VObject\Version::VERSION;
$expected = <<<VCF
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:foo
DTSTART;VALUE=DATE:20130526
DURATION:P1D
CATEGORIES:home,testing
CREATED:20130526T181000Z
ATTACH;VALUE=BINARY:YXR0YWNobWVudA==
ATTENDEE:mailto:armin@example.org
GEO:51.96668;7.61876
SEQUENCE:5
FREEBUSY:20130526T210213/PT1H,20130626T120000/20130626T130000
URL:http://example.org/
TZOFFSETFROM:+05:00
RRULE:FREQ=WEEKLY;BYDAY=MO,TU
X-BOOL;VALUE=BOOLEAN:TRUE
X-TIME;VALUE=TIME:08:00:00
ATTENDEE;CN=Dominik;PARTSTAT=DECLINED:mailto:dominik@example.org
REQUEST-STATUS:2.0;Success
REQUEST-STATUS:3.7;Invalid Calendar User;ATTENDEE:mailto:jsmith@example.org
BEGIN:VALARM
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
VCF;
$this->assertEquals($expected, str_replace("\r", "", $result));
$this->assertEquals(
$input,
$vobj->jsonSerialize()
);
}
function testParseStreamArg() {
$input = array(
"vcard",
array(
array(
"FN", new \StdClass(), 'text', "foo",
),
),
);
$stream = fopen('php://memory','r+');
fwrite($stream, json_encode($input));
rewind($stream);
$result = VObject\Reader::readJson($stream,0);
$this->assertEquals('foo', $result->FN->getValue());
}
/**
* @expectedException \Sabre\VObject\ParseException
*/
function testParseInvalidData() {
$json = new Json();
$input = array(
"vlist",
array(
array(
"FN", new \StdClass(), 'text', "foo",
),
),
);
$json->parse(json_encode($input), 0);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Sabre\VObject\Parser;
/**
* Note that most MimeDir related tests can actually be found in the ReaderTest
* class one level up.
*/
class MimeDirTest extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \Sabre\VObject\ParseException
*/
function testParseError() {
$mimeDir = new MimeDir();
$mimeDir->parse(fopen(__FILE__,'a'));
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Sabre\VObject\Parser;
use
Sabre\VObject\Reader;
class QuotedPrintableTest extends \PHPUnit_Framework_TestCase {
function testReadQuotedPrintableSimple() {
$data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aach=65n\r\nEND:VCARD";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCARD', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertEquals("Aachen", $this->getPropertyValue($result->label));
}
function testReadQuotedPrintableNewlineSoft() {
$data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCARD', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertEquals("Aachen", $this->getPropertyValue($result->label));
}
function testReadQuotedPrintableNewlineHard() {
$data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCARD', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->label));
}
function testReadQuotedPrintableCompatibilityMS() {
$data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD";
$result = Reader::read($data, Reader::OPTION_FORGIVING);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCARD', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->label));
}
function testReadQuotesPrintableCompoundValues() {
$data = <<<VCF
BEGIN:VCARD
VERSION:2.1
N:Doe;John;;;
FN:John Doe
ADR;WORK;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;;M=C3=BCnster =
Str. 1;M=C3=BCnster;;48143;Deutschland
END:VCARD
VCF;
$result = Reader::read($data, Reader::OPTION_FORGIVING);
$this->assertEquals(array(
'','','Münster Str. 1','Münster','','48143','Deutschland'
), $result->ADR->getParts());
}
private function getPropertyValue(\Sabre\VObject\Property $property) {
return (string)$property;
/*
$param = $property['encoding'];
if ($param !== null) {
$encoding = strtoupper((string)$param);
if ($encoding === 'QUOTED-PRINTABLE') {
$value = quoted_printable_decode($value);
} else {
throw new Exception();
}
}
$param = $property['charset'];
if ($param !== null) {
$charset = strtoupper((string)$param);
if ($charset !== 'UTF-8') {
$value = mb_convert_encoding($value, 'UTF-8', $charset);
}
} else {
$value = StringUtil::convertToUTF8($value);
}
return $value;
*/
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
class BinaryTest extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \InvalidArgumentException
*/
function testMimeDir() {
$vcard = new VObject\Component\VCard();
$vcard->add('PHOTO', array('a','b'));
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
class BooleanTest extends \PHPUnit_Framework_TestCase {
function testMimeDir() {
$input = "BEGIN:VCARD\r\nX-AWESOME;VALUE=BOOLEAN:TRUE\r\nX-SUCKS;VALUE=BOOLEAN:FALSE\r\nEND:VCARD\r\n";
$vcard = VObject\Reader::read($input);
$this->assertTrue($vcard->{'X-AWESOME'}->getValue());
$this->assertFalse($vcard->{'X-SUCKS'}->getValue());
$this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType());
$this->assertEquals($input, $vcard->serialize());
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Component\VCard;
class CompoundTest extends \PHPUnit_Framework_TestCase {
function testSetParts() {
$arr = array(
'ABC, Inc.',
'North American Division',
'Marketing;Sales',
);
$vcard = new VCard();
$elem = $vcard->createProperty('ORG');
$elem->setParts($arr);
$this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $elem->getValue());
$this->assertEquals(3, count($elem->getParts()));
$parts = $elem->getParts();
$this->assertEquals('Marketing;Sales', $parts[2]);
}
function testGetParts() {
$str = 'ABC\, Inc.;North American Division;Marketing\;Sales';
$vcard = new VCard();
$elem = $vcard->createProperty('ORG');
$elem->setRawMimeDirValue($str);
$this->assertEquals(3, count($elem->getParts()));
$parts = $elem->getParts();
$this->assertEquals('Marketing;Sales', $parts[2]);
}
function testGetPartsNull() {
$vcard = new VCard();
$elem = $vcard->createProperty('ORG', null);
$this->assertEquals(0, count($elem->getParts()));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
class FloatTest extends \PHPUnit_Framework_TestCase {
function testMimeDir() {
$input = "BEGIN:VCARD\r\nVERSION:4.0\r\nX-FLOAT;VALUE=FLOAT:0.234;1.245\r\nEND:VCARD\r\n";
$mimeDir = new VObject\Parser\MimeDir($input);
$result = $mimeDir->parse($input);
$this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'});
$this->assertEquals(array(
0.234,
1.245,
), $result->{'X-FLOAT'}->getParts());
$this->assertEquals(
$input,
$result->serialize()
);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
class CalAddressTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider values
*/
function testGetNormalizedValue($expected, $input) {
$vobj = new \Sabre\VObject\Component\VCalendar();
$property = $vobj->add('ATTENDEE', $input);
$this->assertEquals(
$expected,
$property->getNormalizedValue()
);
}
function values() {
return array(
array('mailto:a@b.com', 'mailto:a@b.com'),
array('mailto:a@b.com', 'MAILTO:a@b.com'),
array('/foo/bar', '/foo/bar'),
);
}
}

View File

@@ -0,0 +1,359 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
class DateTimeTest extends \PHPUnit_Framework_TestCase {
protected $vcal;
function setUp() {
$this->vcal = new VCalendar();
}
function testSetDateTime() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals('19850704T013000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertNull($elem['VALUE']);
$this->assertTrue($elem->hasTime());
}
function testSetDateTimeLOCAL() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setDateTime($dt, $isFloating = true);
$this->assertEquals('19850704T013000', (string)$elem);
$this->assertNull($elem['TZID']);
$this->assertTrue($elem->hasTime());
}
function testSetDateTimeUTC() {
$tz = new \DateTimeZone('GMT');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals('19850704T013000Z', (string)$elem);
$this->assertNull($elem['TZID']);
$this->assertTrue($elem->hasTime());
}
function testSetDateTimeLOCALTZ() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals('19850704T013000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertTrue($elem->hasTime());
}
function testSetDateTimeDATE() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem['VALUE'] = 'DATE';
$elem->setDateTime($dt);
$this->assertEquals('19850704', (string)$elem);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE', (string)$elem['VALUE']);
$this->assertFalse($elem->hasTime());
}
function testSetValue() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setValue($dt);
$this->assertEquals('19850704T013000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertNull($elem['VALUE']);
$this->assertTrue($elem->hasTime());
}
function testSetValueArray() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1985-07-04 02:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setValue(array($dt1, $dt2));
$this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertNull($elem['VALUE']);
$this->assertTrue($elem->hasTime());
}
function testSetParts() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1985-07-04 02:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setParts(array($dt1, $dt2));
$this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertNull($elem['VALUE']);
$this->assertTrue($elem->hasTime());
}
function testSetPartsStrings() {
$dt1 = '19850704T013000Z';
$dt2 = '19850704T023000Z';
$elem = $this->vcal->createProperty('DTSTART');
$elem->setParts(array($dt1, $dt2));
$this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem);
$this->assertNull($elem['VALUE']);
$this->assertTrue($elem->hasTime());
}
function testGetDateTimeCached() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals($elem->getDateTime(), $dt);
}
function testGetDateTimeDateNULL() {
$elem = $this->vcal->createProperty('DTSTART');
$dt = $elem->getDateTime();
$this->assertNull($dt);
}
function testGetDateTimeDateDATE() {
$elem = $this->vcal->createProperty('DTSTART','19850704');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateDATEReferenceTimeZone() {
$elem = $this->vcal->createProperty('DTSTART','19850704');
$tz = new \DateTimeZone('America/Toronto');
$dt = $elem->getDateTime($tz);
$dt->setTimeZone(new \DateTimeZone('UTC'));
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateFloating() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateFloatingReferenceTimeZone() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$tz = new \DateTimeZone('America/Toronto');
$dt = $elem->getDateTime($tz);
$dt->setTimeZone(new \DateTimeZone('UTC'));
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateUTC() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000Z');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('UTC', $dt->getTimeZone()->getName());
}
function testGetDateTimeDateLOCALTZ() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$elem['TZID'] = 'Europe/Amsterdam';
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
}
/**
* @expectedException LogicException
*/
function testGetDateTimeDateInvalid() {
$elem = $this->vcal->createProperty('DTSTART','bla');
$dt = $elem->getDateTime();
}
function testGetDateTimeWeirdTZ() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
$event = $this->vcal->createComponent('VEVENT');
$event->add($elem);
$timezone = $this->vcal->createComponent('VTIMEZONE');
$timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
$timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam';
$this->vcal->add($event);
$this->vcal->add($timezone);
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
}
function testGetDateTimeBadTimeZone() {
$default = date_default_timezone_get();
date_default_timezone_set('Canada/Eastern');
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$elem['TZID'] = 'Moon';
$event = $this->vcal->createComponent('VEVENT');
$event->add($elem);
$timezone = $this->vcal->createComponent('VTIMEZONE');
$timezone->TZID = 'Moon';
$timezone->{'X-LIC-LOCATION'} = 'Moon';
$this->vcal->add($event);
$this->vcal->add($timezone);
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName());
date_default_timezone_set($default);
}
function testUpdateValueParameter() {
$dtStart = $this->vcal->createProperty('DTSTART', new \DateTime('2013-06-07 15:05:00'));
$dtStart['VALUE'] = 'DATE';
$this->assertEquals("DTSTART;VALUE=DATE:20130607\r\n", $dtStart->serialize());
}
function testValidate() {
$exDate = $this->vcal->createProperty('EXDATE', '-00011130T143000Z');
$messages = $exDate->validate();
$this->assertEquals(1, count($messages));
$this->assertEquals(3, $messages[0]['level']);
}
/**
* This issue was discovered on the sabredav mailing list.
*/
function testCreateDatePropertyThroughAdd() {
$vcal = new VCalendar();
$vevent = $vcal->add('VEVENT');
$dtstart = $vevent->add(
'DTSTART',
new \DateTime('2014-03-07'),
array('VALUE' => 'DATE')
);
$this->assertEquals("DTSTART;VALUE=DATE:20140307\r\n", $dtstart->serialize());
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
class DurationTest extends \PHPUnit_Framework_TestCase {
function testGetDateInterval() {
$vcal = new VCalendar();
$event = $vcal->add('VEVENT', array('DURATION' => array('PT1H')));
$this->assertEquals(
new \DateInterval('PT1H'),
$event->{'DURATION'}->getDateInterval()
);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\Component\VCalendar;
class RecurTest extends \PHPUnit_Framework_TestCase {
function testParts() {
$vcal = new VCalendar();
$recur = $vcal->add('RRULE', 'FREQ=Daily');
$this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur);
$this->assertEquals(array('FREQ'=>'DAILY'), $recur->getParts());
$recur->setParts(array('freq'=>'MONTHLY'));
$this->assertEquals(array('FREQ'=>'MONTHLY'), $recur->getParts());
}
/**
* @expectedException \InvalidArgumentException
*/
function testSetValueBadVal() {
$vcal = new VCalendar();
$recur = $vcal->add('RRULE', 'FREQ=Daily');
$recur->setValue(new \Exception());
}
function testSetSubParts() {
$vcal = new VCalendar();
$recur = $vcal->add('RRULE', array('FREQ'=>'DAILY', 'BYDAY'=>'mo,tu', 'BYMONTH' => array(0,1)));
$this->assertEquals(array(
'FREQ'=>'DAILY',
'BYDAY' => array('MO','TU'),
'BYMONTH' => array(0,1),
), $recur->getParts());
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Component\VCard;
class TextTest extends \PHPUnit_Framework_TestCase {
function assertVCard21serialization($propValue, $expected) {
$doc = new VCard(array(
'VERSION'=>'2.1',
'PROP' => $propValue
), false);
// Adding quoted-printable, because we're testing if it gets removed
// automatically.
$doc->PROP['ENCODING'] = 'QUOTED-PRINTABLE';
$doc->PROP['P1'] = 'V1';
$output = $doc->serialize();
$this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\n$expected\r\nEND:VCARD\r\n", $output);
}
function testSerializeVCard21() {
$this->assertVCard21Serialization(
'f;oo',
'PROP;P1=V1:f;oo'
);
}
function testSerializeVCard21Array() {
$this->assertVCard21Serialization(
array('f;oo','bar'),
'PROP;P1=V1:f\;oo;bar'
);
}
function testSerializeVCard21Fold() {
$this->assertVCard21Serialization(
str_repeat('x',80),
'PROP;P1=V1:' . str_repeat('x',64) . "\r\n " . str_repeat('x',16)
);
}
function testSerializeQuotedPrintable() {
$this->assertVCard21Serialization(
"foo\r\nbar",
'PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abar'
);
}
function testSerializeQuotedPrintableFold() {
$this->assertVCard21Serialization(
"foo\r\nbarxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abarxxxxxxxxxxxxxxxxxxxxxxxxxx=\r\n xxx"
);
}
function testValidateMinimumPropValue() {
$vcard = <<<IN
BEGIN:VCARD
VERSION:4.0
UID:foo
FN:Hi!
N:A
END:VCARD
IN;
$vcard = \Sabre\VObject\Reader::read($vcard);
$this->assertEquals(1, count($vcard->validate()));
$this->assertEquals(1, count($vcard->N->getParts()));
$vcard->validate(\Sabre\VObject\Node::REPAIR);
$this->assertEquals(5, count($vcard->N->getParts()));
}
}

View File

@@ -0,0 +1,245 @@
<?php
namespace Sabre\VObject\Property\VCard;
use
Sabre\VObject,
Sabre\VObject\Reader;
class DateAndOrTimeTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider dates
*/
function testGetJsonValue($input, $output) {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY', $input);
$this->assertEquals(array($output), $prop->getJsonValue());
}
function dates() {
return array(
array(
"19961022T140000",
"1996-10-22T14:00:00",
),
array(
"--1022T1400",
"--10-22T14:00",
),
array(
"---22T14",
"---22T14",
),
array(
"19850412",
"1985-04-12",
),
array(
"1985-04",
"1985-04",
),
array(
"1985",
"1985",
),
array(
"--0412",
"--04-12",
),
array(
"T102200",
"T10:22:00",
),
array(
"T1022",
"T10:22",
),
array(
"T10",
"T10",
),
array(
"T-2200",
"T-22:00",
),
array(
"T102200Z",
"T10:22:00Z",
),
array(
"T102200-0800",
"T10:22:00-0800",
),
array(
"T--00",
"T--00",
),
);
}
public function testSetParts() {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY');
$prop->setParts(array(
new \DateTime('2014-04-02 18:37:00')
));
$this->assertEquals('20140402T183700Z', $prop->getValue());
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetPartsTooMany() {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY');
$prop->setParts(array(
1,
2
));
}
public function testSetPartsString() {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY');
$prop->setParts(array(
"20140402T183700Z"
));
$this->assertEquals('20140402T183700Z', $prop->getValue());
}
public function testSetValueDateTime() {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY');
$prop->setValue(
new \DateTime('2014-04-02 18:37:00')
);
$this->assertEquals('20140402T183700Z', $prop->getValue());
}
public function testSetDateTimeOffset() {
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY');
$prop->setValue(
new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto'))
);
$this->assertEquals('20140402T183700-0400', $prop->getValue());
}
public function testGetDateTime() {
$datetime = new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto'));
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY', $datetime);
$dt = $prop->getDateTime();
$this->assertEquals('2014-04-02T18:37:00-04:00', $dt->format('c'), "For some reason this one failed. Current default timezone is: " . date_default_timezone_get());
}
public function testGetDate() {
$datetime = new \DateTime('2014-04-02');
$vcard = new VObject\Component\VCard();
$prop = $vcard->createProperty('BDAY', $datetime, null, 'DATE');
$this->assertEquals('DATE', $prop->getValueType());
$this->assertEquals('BDAY:20140402', rtrim($prop->serialize()));
}
public function testGetDateIncomplete() {
$datetime = '--0407';
$vcard = new VObject\Component\VCard();
$prop = $vcard->add('BDAY', $datetime);
$dt = $prop->getDateTime();
// Note: if the year changes between the last line and the next line of
// code, this test may fail.
//
// If that happens, head outside and have a drink.
$current = new \DateTime('now');
$year = $current->format('Y');
$this->assertEquals($year . '0407', $dt->format('Ymd'));
}
public function testGetDateIncompleteFromVCard() {
$vcard = <<<VCF
BEGIN:VCARD
VERSION:4.0
BDAY:--0407
END:VCARD
VCF;
$vcard = Reader::read($vcard);
$prop = $vcard->BDAY;
$dt = $prop->getDateTime();
// Note: if the year changes between the last line and the next line of
// code, this test may fail.
//
// If that happens, head outside and have a drink.
$current = new \DateTime('now');
$year = $current->format('Y');
$this->assertEquals($year . '0407', $dt->format('Ymd'));
}
public function testValidate() {
$datetime = '--0407';
$vcard = new VObject\Component\VCard();
$prop = $vcard->add('BDAY', $datetime);
$this->assertEquals(array(), $prop->validate());
}
public function testValidateBroken() {
$datetime = '123';
$vcard = new VObject\Component\VCard();
$prop = $vcard->add('BDAY', $datetime);
$this->assertEquals(array(array(
'level' => 3,
'message' => 'The supplied value (123) is not a correct DATE-AND-OR-TIME property',
'node' => $prop,
)), $prop->validate());
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Sabre\VObject\Property\VCard;
use Sabre\VObject;
class LanguageTagTest extends \PHPUnit_Framework_TestCase {
function testMimeDir() {
$input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n";
$mimeDir = new VObject\Parser\MimeDir($input);
$result = $mimeDir->parse($input);
$this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG);
$this->assertEquals('nl', $result->LANG->getValue());
$this->assertEquals(
$input,
$result->serialize()
);
}
function testChangeAndSerialize() {
$input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n";
$mimeDir = new VObject\Parser\MimeDir($input);
$result = $mimeDir->parse($input);
$this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG);
// This replicates what the vcard converter does and triggered a bug in
// the past.
$result->LANG->setValue(array('de'));
$this->assertEquals('de', $result->LANG->getValue());
$expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:de\r\nEND:VCARD\r\n";
$this->assertEquals(
$expected,
$result->serialize()
);
}
}

View File

@@ -0,0 +1,411 @@
<?php
namespace Sabre\VObject;
use
Sabre\VObject\Component\VCalendar,
Sabre\VObject\Component\VCard;
class PropertyTest extends \PHPUnit_Framework_TestCase {
function testToString() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$this->assertEquals('PROPNAME', $property->name);
$this->assertEquals('propvalue', $property->__toString());
$this->assertEquals('propvalue', (string)$property);
$this->assertEquals('propvalue', $property->getValue());
}
function testCreate() {
$cal = new VCalendar();
$params = array(
'param1' => 'value1',
'param2' => 'value2',
);
$property = $cal->createProperty('propname','propvalue', $params);
$this->assertEquals('value1', $property['param1']->getValue());
$this->assertEquals('value2', $property['param2']->getValue());
}
function testSetValue() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property->setValue('value2');
$this->assertEquals('PROPNAME', $property->name);
$this->assertEquals('value2', $property->__toString());
}
function testParameterExists() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
$this->assertTrue(isset($property['PARAMNAME']));
$this->assertTrue(isset($property['paramname']));
$this->assertFalse(isset($property['foo']));
}
function testParameterGet() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
$this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
}
function testParameterNotExists() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
$this->assertInternalType('null',$property['foo']);
}
function testParameterMultiple() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
$property->add('paramname', 'paramvalue');
$this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
$this->assertEquals(2,count($property['paramname']->getParts()));
}
function testSetParameterAsString() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
$this->assertEquals(1,count($property->parameters()));
$this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']);
$this->assertEquals('PARAMNAME',$property->parameters['PARAMNAME']->name);
$this->assertEquals('paramvalue',$property->parameters['PARAMNAME']->getValue());
}
function testUnsetParameter() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$property['paramname'] = 'paramvalue';
unset($property['PARAMNAME']);
$this->assertEquals(0,count($property->parameters()));
}
function testSerialize() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize());
}
function testSerializeParam() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue', array(
'paramname' => 'paramvalue',
'paramname2' => 'paramvalue2',
));
$this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize());
}
function testSerializeNewLine() {
$cal = new VCalendar();
$property = $cal->createProperty('SUMMARY',"line1\nline2");
$this->assertEquals("SUMMARY:line1\\nline2\r\n",$property->serialize());
}
function testSerializeLongLine() {
$cal = new VCalendar();
$value = str_repeat('!',200);
$property = $cal->createProperty('propname',$value);
$expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n";
$this->assertEquals($expected,$property->serialize());
}
function testSerializeUTF8LineFold() {
$cal = new VCalendar();
$value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a
$property = $cal->createProperty('propname', $value);
$expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n";
$this->assertEquals($expected, $property->serialize());
}
function testGetIterator() {
$cal = new VCalendar();
$it = new ElementList(array());
$property = $cal->createProperty('propname','propvalue');
$property->setIterator($it);
$this->assertEquals($it,$property->getIterator());
}
function testGetIteratorDefault() {
$cal = new VCalendar();
$property = $cal->createProperty('propname','propvalue');
$it = $property->getIterator();
$this->assertTrue($it instanceof ElementList);
$this->assertEquals(1,count($it));
}
function testAddScalar() {
$cal = new VCalendar();
$property = $cal->createProperty('EMAIL');
$property->add('myparam','value');
$this->assertEquals(1, count($property->parameters()));
$this->assertTrue($property->parameters['MYPARAM'] instanceof Parameter);
$this->assertEquals('MYPARAM',$property->parameters['MYPARAM']->name);
$this->assertEquals('value',$property->parameters['MYPARAM']->getValue());
}
function testAddParameter() {
$cal = new VCalendar();
$prop = $cal->createProperty('EMAIL');
$prop->add('MYPARAM','value');
$this->assertEquals(1, count($prop->parameters()));
$this->assertEquals('MYPARAM',$prop['myparam']->name);
}
function testAddParameterTwice() {
$cal = new VCalendar();
$prop = $cal->createProperty('EMAIL');
$prop->add('MYPARAM', 'value1');
$prop->add('MYPARAM', 'value2');
$this->assertEquals(1, count($prop->parameters));
$this->assertEquals(2, count($prop->parameters['MYPARAM']->getParts()));
$this->assertEquals('MYPARAM',$prop['MYPARAM']->name);
}
function testClone() {
$cal = new VCalendar();
$property = $cal->createProperty('EMAIL','value');
$property['FOO'] = 'BAR';
$property2 = clone $property;
$property['FOO'] = 'BAZ';
$this->assertEquals('BAR', (string)$property2['FOO']);
}
function testCreateParams() {
$cal = new VCalendar();
$property = $cal->createProperty('X-PROP','value', array(
'param1' => 'value1',
'param2' => array('value2', 'value3')
));
$this->assertEquals(1, count($property['PARAM1']->getParts()));
$this->assertEquals(2, count($property['PARAM2']->getParts()));
}
function testValidateNonUTF8() {
$calendar = new VCalendar();
$property = $calendar->createProperty('X-PROP', "Bla\x00");
$result = $property->validate(Property::REPAIR);
$this->assertEquals('Property contained a control character (0x00)', $result[0]['message']);
$this->assertEquals('Bla', $property->getValue());
}
function testValidateControlChars() {
$s = "chars[";
foreach (array(
0x7F, 0x5E, 0x5C, 0x3B, 0x3A, 0x2C, 0x22, 0x20,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18,
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
) as $c) {
$s .= sprintf('%02X(%c)', $c, $c);
}
$s .= "]end";
$calendar = new VCalendar();
$property = $calendar->createProperty('X-PROP', $s);
$result = $property->validate(Property::REPAIR);
$this->assertEquals('Property contained a control character (0x7f)', $result[0]['message']);
$this->assertEquals("chars[7F()5E(^)5C(\\\\)3B(\\;)3A(:)2C(\\,)22(\")20( )1F()1E()1D()1C()1B()1A()19()18()17()16()15()14()13()12()11()10()0F()0E()0D()0C()0B()0A(\\n)09( )08()07()06()05()04()03()02()01()00()]end", $property->getRawMimeDirValue());
}
function testValidateBadPropertyName() {
$calendar = new VCalendar();
$property = $calendar->createProperty("X_*&PROP*", "Bla");
$result = $property->validate(Property::REPAIR);
$this->assertEquals($result[0]['message'], 'The propertyname: X_*&PROP* contains invalid characters. Only A-Z, 0-9 and - are allowed');
$this->assertEquals('X-PROP', $property->name);
}
function testGetValue() {
$calendar = new VCalendar();
$property = $calendar->createProperty("SUMMARY", null);
$this->assertEquals(array(), $property->getParts());
$this->assertNull($property->getValue());
$property->setValue(array());
$this->assertEquals(array(), $property->getParts());
$this->assertNull($property->getValue());
$property->setValue(array(1));
$this->assertEquals(array(1), $property->getParts());
$this->assertEquals(1, $property->getValue());
$property->setValue(array(1,2));
$this->assertEquals(array(1,2), $property->getParts());
$this->assertEquals('1,2', $property->getValue());
$property->setValue('str');
$this->assertEquals(array('str'), $property->getParts());
$this->assertEquals('str', $property->getValue());
}
/**
* ElementList should reject this.
*
* @expectedException \LogicException
*/
function testArrayAccessSetInt() {
$calendar = new VCalendar();
$property = $calendar->createProperty("X-PROP", null);
$calendar->add($property);
$calendar->{'X-PROP'}[0] = 'Something!';
}
/**
* ElementList should reject this.
*
* @expectedException \LogicException
*/
function testArrayAccessUnsetInt() {
$calendar = new VCalendar();
$property = $calendar->createProperty("X-PROP", null);
$calendar->add($property);
unset($calendar->{'X-PROP'}[0]);
}
function testValidateBadEncoding() {
$document = new VCalendar();
$property = $document->add('X-FOO','value');
$property['ENCODING'] = 'invalid';
$result = $property->validate();
$this->assertEquals('ENCODING=INVALID is not valid for this document type.', $result[0]['message']);
$this->assertEquals(1, $result[0]['level']);
}
function testValidateBadEncodingVCard4() {
$document = new VCard(array('VERSION' => '4.0'));
$property = $document->add('X-FOO','value');
$property['ENCODING'] = 'BASE64';
$result = $property->validate();
$this->assertEquals('ENCODING parameter is not valid in vCard 4.', $result[0]['message']);
$this->assertEquals(1, $result[0]['level']);
}
function testValidateBadEncodingVCard3() {
$document = new VCard(array('VERSION' => '3.0'));
$property = $document->add('X-FOO','value');
$property['ENCODING'] = 'BASE64';
$result = $property->validate();
$this->assertEquals('ENCODING=BASE64 is not valid for this document type.', $result[0]['message']);
$this->assertEquals(1, $result[0]['level']);
}
function testValidateBadEncodingVCard21() {
$document = new VCard(array('VERSION' => '2.1'));
$property = $document->add('X-FOO','value');
$property['ENCODING'] = 'B';
$result = $property->validate();
$this->assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']);
$this->assertEquals(1, $result[0]['level']);
}
}

View File

@@ -0,0 +1,449 @@
<?php
namespace Sabre\VObject;
class ReaderTest extends \PHPUnit_Framework_TestCase {
function testReadComponent() {
$data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadStream() {
$data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
$stream = fopen('php://memory', 'r+');
fwrite($stream, $data);
rewind($stream);
$result = Reader::read($stream);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadComponentUnixNewLine() {
$data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadComponentLineFold() {
$data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testReadCorruptComponent() {
$data = "BEGIN:VCALENDAR\r\nEND:FOO";
$result = Reader::read($data);
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testReadCorruptSubComponent() {
$data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR";
$result = Reader::read($data);
}
function testReadProperty() {
$data = "BEGIN:VCALENDAR\r\nSUMMARY:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->SUMMARY;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('SUMMARY', $result->name);
$this->assertEquals('propValue', $result->getValue());
}
function testReadPropertyWithNewLine() {
$data = "BEGIN:VCALENDAR\r\nSUMMARY:Line1\\nLine2\\NLine3\\\\Not the 4th line!\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->SUMMARY;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('SUMMARY', $result->name);
$this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue());
}
function testReadMappedProperty() {
$data = "BEGIN:VCALENDAR\r\nDTSTART:20110529\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->DTSTART;
$this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result);
$this->assertEquals('DTSTART', $result->name);
$this->assertEquals('20110529', $result->getValue());
}
function testReadMappedPropertyGrouped() {
$data = "BEGIN:VCALENDAR\r\nfoo.DTSTART:20110529\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->DTSTART;
$this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result);
$this->assertEquals('DTSTART', $result->name);
$this->assertEquals('20110529', $result->getValue());
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testReadBrokenLine() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue";
$result = Reader::read($data);
}
function testReadPropertyInComponent() {
$data = array(
"BEGIN:VCALENDAR",
"PROPNAME:propValue",
"END:VCALENDAR"
);
$result = Reader::read(implode("\r\n",$data));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]);
$this->assertEquals('PROPNAME', $result->children[0]->name);
$this->assertEquals('propValue', $result->children[0]->getValue());
}
function testReadNestedComponent() {
$data = array(
"BEGIN:VCALENDAR",
"BEGIN:VTIMEZONE",
"BEGIN:DAYLIGHT",
"END:DAYLIGHT",
"END:VTIMEZONE",
"END:VCALENDAR"
);
$result = Reader::read(implode("\r\n",$data));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(1, count($result->children()));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]);
$this->assertEquals('VTIMEZONE', $result->children[0]->name);
$this->assertEquals(1, count($result->children[0]->children()));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]);
$this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name);
}
function testReadPropertyParameter() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
}
function testReadPropertyRepeatingParameter() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;N=1;N=2;N=3,4;N=\"5\",6;N=\"7,8\";N=9,10;N=^'11^':propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('N', $result->parameters['N']->name);
$this->assertEquals('1,2,3,4,5,6,7,8,9,10,"11"', $result->parameters['N']->getValue());
$this->assertEquals(array(1,2,3,4,5,6,"7,8",9,10,'"11"'), $result->parameters['N']->getParts());
}
function testReadPropertyRepeatingNamelessGuessedParameter() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;WORK;VOICE;PREF:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('TYPE', $result->parameters['TYPE']->name);
$this->assertEquals('WORK,VOICE,PREF', $result->parameters['TYPE']->getValue());
$this->assertEquals(array('WORK', 'VOICE', 'PREF'), $result->parameters['TYPE']->getParts());
}
function testReadPropertyNoName() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PRODIGY:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('TYPE', $result->parameters['TYPE']->name);
$this->assertTrue($result->parameters['TYPE']->noName);
$this->assertEquals('PRODIGY', $result->parameters['TYPE']);
}
function testReadPropertyParameterExtraColon() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue:anotherrandomstring', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
}
function testReadProperty2Parameters() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(2, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
$this->assertEquals('PARAMNAME2', $result->parameters['PARAMNAME2']->name);
$this->assertEquals('paramvalue2', $result->parameters['PARAMNAME2']->getValue());
}
function testReadPropertyParameterQuoted() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"paramvalue\":propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->PROPNAME;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
}
function testReadPropertyParameterNewLines() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue1^nvalue2^^nvalue3:propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->propname;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals("paramvalue1\nvalue2^nvalue3", $result->parameters['PARAMNAME']->getValue());
}
function testReadPropertyParameterQuotedColon() {
$data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"param:value\":propValue\r\nEND:VCALENDAR";
$result = Reader::read($data);
$result = $result->propname;
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->getValue());
$this->assertEquals(1, count($result->parameters()));
$this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
$this->assertEquals('param:value', $result->parameters['PARAMNAME']->getValue());
}
function testReadForgiving() {
$data = array(
"BEGIN:VCALENDAR",
"X_PROP:propValue",
"END:VCALENDAR"
);
$caught = false;
try {
$result = Reader::read(implode("\r\n",$data));
} catch (ParseException $e) {
$caught = true;
}
$this->assertEquals(true, $caught);
$result = Reader::read(implode("\r\n",$data), Reader::OPTION_FORGIVING);
$expected = implode("\r\n", array(
"BEGIN:VCALENDAR",
"X_PROP:propValue",
"END:VCALENDAR",
""
));
$this->assertEquals($expected, $result->serialize());
}
function testReadWithInvalidLine() {
$data = array(
"BEGIN:VCALENDAR",
"DESCRIPTION:propValue",
"Yes, we've actually seen a file with non-idented property values on multiple lines",
"END:VCALENDAR"
);
$caught = false;
try {
$result = Reader::read(implode("\r\n",$data));
} catch (ParseException $e) {
$caught = true;
}
$this->assertEquals(true, $caught);
$result = Reader::read(implode("\r\n",$data), Reader::OPTION_IGNORE_INVALID_LINES);
$expected = implode("\r\n", array(
"BEGIN:VCALENDAR",
"DESCRIPTION:propValue",
"END:VCALENDAR",
""
));
$this->assertEquals($expected, $result->serialize());
}
/**
* Reported as Issue 32.
*
* @expectedException \Sabre\VObject\ParseException
*/
public function testReadIncompleteFile() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:1.0
BEGIN:VEVENT
X-FUNAMBOL-FOLDER:DEFAULT_FOLDER
X-FUNAMBOL-ALLDAY:0
DTSTART:20111017T110000Z
DTEND:20111017T123000Z
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
CATEGORIES:
LOCATION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:Netviewer Meeting
PRIORITY:1
STATUS:3
X-MICROSOFT-CDO-REPLYTIME:20111017T064200Z
SUMMARY;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:Kopieren: test
CLASS:PUBLIC
AALARM:
RRULE:
X-FUNAMBOL-BILLINGINFO:
X-FUNAMBOL-COMPANIES:
X-FUNAMBOL-MILEAGE:
X-FUNAMBOL-NOAGING:0
ATTENDEE;STATUS=NEEDS ACTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:'Heino' heino@test.com
ATTENDEE;STATUS=NEEDS ACTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:'Markus' test@test.com
ATTENDEE;STATUS=NEEDS AC
ICS;
Reader::read($input);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testReadBrokenInput() {
Reader::read(false);
}
public function testReadBOM() {
$data = chr(0xef) . chr(0xbb) . chr(0xbf) . "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Sabre\VObject\Recur;
use
Sabre\VObject\Reader,
DateTime;
class ByMonthInDailyTest extends \PHPUnit_Framework_TestCase {
/**
* This tests the expansion of dates with DAILY frequency in RRULE with BYMONTH restrictions
*/
function testExpand() {
$ics = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND:20070925T183000Z
UID:uuid
DTSTAMP:19700101T000000Z
LOCATION:
DESCRIPTION:
STATUS:CONFIRMED
SEQUENCE:18
SUMMARY:Stuff
DTSTART:20070925T160000Z
CREATED:20071004T144642Z
RRULE:FREQ=DAILY;BYMONTH=9,10;BYDAY=SU
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($ics);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11'));
foreach ($vcal->VEVENT as $event) {
$dates[] = $event->DTSTART->getValue();
}
$expectedDates = array(
"20130929T160000Z",
"20131006T160000Z",
"20131013T160000Z",
"20131020T160000Z",
"20131027T160000Z",
"20140907T160000Z"
);
$this->assertEquals($expectedDates, $dates, 'Recursed dates are restricted by month');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Sabre\VObject\Recur;
use
Sabre\VObject\Reader,
DateTime;
class BySetPosHangTest extends \PHPUnit_Framework_TestCase {
/**
* Using this iCalendar object, including BYSETPOS=-2 causes the iterator
* to hang, as reported in ticket #212.
*
* See: https://github.com/fruux/sabre-vobject/issues/212
*/
function testExpand() {
$ics = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 3.4.2//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
SUMMARY:Test event 1
DTSTART;TZID=Europe/Copenhagen:20150101T170000
RRULE:FREQ=MONTHLY;BYDAY=TH;BYSETPOS=-2
UID:b4071499-6fe4-418a-83b8-2b8d5ebb38e4
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($ics);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01'));
foreach ($vcal->VEVENT as $event) {
$dates[] = $event->DTSTART->getValue();
}
$expectedDates = array(
"20150101T160000Z",
"20150122T160000Z",
"20150219T160000Z",
"20150319T160000Z",
"20150423T150000Z",
"20150521T150000Z",
"20150618T150000Z",
"20150723T150000Z",
"20150820T150000Z",
"20150917T150000Z",
"20151022T150000Z",
"20151119T160000Z",
"20151224T160000Z",
);
$this->assertEquals($expectedDates, $dates);
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Sabre\VObject\Recur\EventIterator;
use
DateTime,
DateTimeZone,
Sabre\VObject\Reader;
class ExpandFloatingTimesTest extends \PHPUnit_Framework_TestCase {
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20150109T090000
DTEND:20150109T100000
RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20191002T070000Z;BYDAY=FR
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'));
$result = $vcal->serialize();
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20150109T090000Z
DTEND:20150109T100000Z
RECURRENCE-ID:20150109T090000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150116T090000Z
DTEND:20150116T100000Z
RECURRENCE-ID:20150116T090000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150123T090000Z
DTEND:20150123T100000Z
RECURRENCE-ID:20150123T090000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150130T090000Z
DTEND:20150130T100000Z
RECURRENCE-ID:20150130T090000Z
END:VEVENT
END:VCALENDAR
ICS;
$this->assertEquals($output, str_replace("\r", "", $result));
}
function testExpandWithReferenceTimezone() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20150109T090000
DTEND:20150109T100000
RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20191002T070000Z;BYDAY=FR
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'), new \DateTimeZone('Europe/Berlin'));
$result = $vcal->serialize();
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20150109T080000Z
DTEND:20150109T090000Z
RECURRENCE-ID:20150109T080000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150116T080000Z
DTEND:20150116T090000Z
RECURRENCE-ID:20150116T080000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150123T080000Z
DTEND:20150123T090000Z
RECURRENCE-ID:20150123T080000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20150130T080000Z
DTEND:20150130T090000Z
RECURRENCE-ID:20150130T080000Z
END:VEVENT
END:VCALENDAR
ICS;
$this->assertEquals($output, str_replace("\r", "", $result));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Recur;
use Sabre\VObject\Reader;
class FifthTuesdayProblemTest extends \PHPUnit_Framework_TestCase {
/**
* A pretty slow test. Had to be marked as 'medium' for phpunit to not die
* after 1 second. Would be good to optimize later.
*
* @medium
*/
function testGetDTEnd() {
$ics = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND;TZID=America/New_York:20070925T170000
UID:uuid
DTSTAMP:19700101T000000Z
LOCATION:
DESCRIPTION:
STATUS:CONFIRMED
SEQUENCE:18
SUMMARY:Stuff
DTSTART;TZID=America/New_York:20070925T160000
CREATED:20071004T144642Z
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU
END:VEVENT
END:VCALENDAR
ICS;
$vObject = Reader::read($ics);
$it = new Recur\EventIterator($vObject, (string)$vObject->VEVENT->UID);
while($it->valid()) {
$it->next();
}
// If we got here, it means we were successful. The bug that was in the
// system before would fail on the 5th tuesday of the month, if the 5th
// tuesday did not exist.
$this->assertTrue(true);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Sabre\VObject\Recur\EventIterator;
use
DateTime,
DateTimeZone,
Sabre\VObject\Reader;
/**
* This is a unittest for Issue #53.
*/
class RecurrenceIteratorIncorrectExpandTest extends \PHPUnit_Framework_TestCase {
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20130711T050000Z
DTEND:20130711T053000Z
RRULE:FREQ=DAILY;INTERVAL=1;COUNT=2
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20130719T050000Z
DTEND:20130719T053000Z
RECURRENCE-ID:20130712T050000Z
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01'));
$result = $vcal->serialize();
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20130711T050000Z
DTEND:20130711T053000Z
RECURRENCE-ID:20130711T050000Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTSTART:20130719T050000Z
DTEND:20130719T053000Z
RECURRENCE-ID:20130712T050000Z
END:VEVENT
END:VCALENDAR
ICS;
$this->assertEquals($output, str_replace("\r", "", $result));
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Sabre\VObject\Recur\EventIterator;
use
DateTime,
DateTimeZone,
Sabre\VObject\Component\VCalendar,
Sabre\VObject\Recur;
class EventIteratorInfiniteLoopProblemTest extends \PHPUnit_Framework_TestCase {
public function setUp() {
$this->vcal = new VCalendar();
}
/**
* This bug came from a Fruux customer. This would result in a never-ending
* request.
*/
function testFastForwardTooFar() {
$ev = $this->vcal->createComponent('VEVENT');
$ev->UID = 'foobar';
$ev->DTSTART = '20090420T180000Z';
$ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1';
$this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00')));
}
/**
* Different bug, also likely an infinite loop.
*/
function testYearlyByMonthLoop() {
$ev = $this->vcal->createComponent('VEVENT');
$ev->UID = 'uuid';
$ev->DTSTART = '20120101T154500';
$ev->DTSTART['TZID'] = 'Europe/Berlin';
$ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
$ev->DTEND = '20120101T164500';
$ev->DTEND['TZID'] = 'Europe/Berlin';
// This recurrence rule by itself is a yearly rule that should happen
// every february.
//
// The BYDAY part expands this to every day of the month, but the
// BYSETPOS limits this to only the 1st day of the month. Very crazy
// way to specify this, and could have certainly been a lot easier.
$this->vcal->add($ev);
$it = new Recur\EventIterator($this->vcal,'uuid');
$it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
$collect = array();
while($it->valid()) {
$collect[] = $it->getDTSTART();
if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
break;
}
$it->next();
}
$this->assertEquals(
array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))),
$collect
);
}
/**
* Something, somewhere produced an ics with an interval set to 0. Because
* this means we increase the current day (or week, month) by 0, this also
* results in an infinite loop.
*
* @expectedException InvalidArgumentException
* @return void
*/
function testZeroInterval() {
$ev = $this->vcal->createComponent('VEVENT');
$ev->UID = 'uuid';
$ev->DTSTART = '20120824T145700Z';
$ev->RRULE = 'FREQ=YEARLY;INTERVAL=0';
$this->vcal->add($ev);
$it = new Recur\EventIterator($this->vcal,'uuid');
$it->fastForward(new DateTime('2013-01-01 23:00:00', new DateTimeZone('UTC')));
// if we got this far.. it means we are no longer infinitely looping
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Sabre\VObject;
use
DateTime,
DateTimeZone;
class Issue48Test extends \PHPUnit_Framework_TestCase {
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foo
DTEND;TZID=Europe/Moscow:20130710T120000
DTSTART;TZID=Europe/Moscow:20130710T110000
RRULE:FREQ=DAILY;UNTIL=20130712T195959Z
END:VEVENT
BEGIN:VEVENT
UID:foo
DTEND;TZID=Europe/Moscow:20130713T120000
DTSTART;TZID=Europe/Moscow:20130713T110000
RECURRENCE-ID;TZID=Europe/Moscow:20130711T110000
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$it = new Recur\EventIterator($vcal, 'foo');
$result = iterator_to_array($it);
$tz = new DateTimeZone('Europe/Moscow');
$expected = array(
new DateTime('2013-07-10 11:00:00', $tz),
new DateTime('2013-07-12 11:00:00', $tz),
new DateTime('2013-07-13 11:00:00', $tz),
);
$this->assertEquals($expected, $result);
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Sabre\VObject;
use
DateTime,
DateTimeZone;
class Issue50Test extends \PHPUnit_Framework_TestCase {
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Brussels
X-LIC-LOCATION:Europe/Brussels
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130705T142510Z
LAST-MODIFIED:20130715T132556Z
DTSTAMP:20130715T132556Z
UID:1aef0b27-3d92-4581-829a-11999dd36724
SUMMARY:Werken
RRULE:FREQ=DAILY;COUNT=5
DTSTART;TZID=Europe/Brussels:20130715T090000
DTEND;TZID=Europe/Brussels:20130715T170000
LOCATION:Job
DESCRIPTION:Vrij
X-MOZ-GENERATION:9
END:VEVENT
BEGIN:VEVENT
CREATED:20130715T081654Z
LAST-MODIFIED:20130715T110931Z
DTSTAMP:20130715T110931Z
UID:1aef0b27-3d92-4581-829a-11999dd36724
SUMMARY:Werken
RECURRENCE-ID;TZID=Europe/Brussels:20130719T090000
DTSTART;TZID=Europe/Brussels:20130719T070000
DTEND;TZID=Europe/Brussels:20130719T150000
SEQUENCE:1
LOCATION:Job
DESCRIPTION:Vrij
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20130715T111654Z
LAST-MODIFIED:20130715T132556Z
DTSTAMP:20130715T132556Z
UID:1aef0b27-3d92-4581-829a-11999dd36724
SUMMARY:Werken
RECURRENCE-ID;TZID=Europe/Brussels:20130716T090000
DTSTART;TZID=Europe/Brussels:20130716T070000
DTEND;TZID=Europe/Brussels:20130716T150000
SEQUENCE:1
LOCATION:Job
X-MOZ-GENERATION:2
END:VEVENT
BEGIN:VEVENT
CREATED:20130715T125942Z
LAST-MODIFIED:20130715T130023Z
DTSTAMP:20130715T130023Z
UID:1aef0b27-3d92-4581-829a-11999dd36724
SUMMARY:Werken
RECURRENCE-ID;TZID=Europe/Brussels:20130717T090000
DTSTART;TZID=Europe/Brussels:20130717T070000
DTEND;TZID=Europe/Brussels:20130717T150000
SEQUENCE:1
LOCATION:Job
X-MOZ-GENERATION:3
END:VEVENT
BEGIN:VEVENT
CREATED:20130715T130024Z
LAST-MODIFIED:20130715T130034Z
DTSTAMP:20130715T130034Z
UID:1aef0b27-3d92-4581-829a-11999dd36724
SUMMARY:Werken
RECURRENCE-ID;TZID=Europe/Brussels:20130718T090000
DTSTART;TZID=Europe/Brussels:20130718T090000
DTEND;TZID=Europe/Brussels:20130718T170000
LOCATION:Job
X-MOZ-GENERATION:5
DESCRIPTION:Vrij
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724');
$result = array();
foreach($it as $instance) {
$result[] = $instance;
}
$tz = new DateTimeZone('Europe/Brussels');
$this->assertEquals(array(
new DateTime('2013-07-15 09:00:00', $tz),
new DateTime('2013-07-16 07:00:00', $tz),
new DateTime('2013-07-17 07:00:00', $tz),
new DateTime('2013-07-18 09:00:00', $tz),
new DateTime('2013-07-19 07:00:00', $tz),
), $result);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
<?php
namespace Sabre\VObject\Recur\EventIterator;
use
DateTime,
DateTimeZone,
Sabre\VObject\Reader;
class RecurrenceIteratorMissingOverriddenTest extends \PHPUnit_Framework_TestCase {
function testExpand() {
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20130727T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=2
SUMMARY:A
END:VEVENT
BEGIN:VEVENT
RECURRENCE-ID:20130728T120000Z
UID:foo
DTSTART:20140101T120000Z
DURATION:PT1H
SUMMARY:B
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01'));
$result = $vcal->serialize();
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foo
DTSTART:20130727T120000Z
DURATION:PT1H
SUMMARY:A
RECURRENCE-ID:20130727T120000Z
END:VEVENT
BEGIN:VEVENT
RECURRENCE-ID:20130728T120000Z
UID:foo
DTSTART:20140101T120000Z
DURATION:PT1H
SUMMARY:B
END:VEVENT
END:VCALENDAR
ICS;
$this->assertEquals($output, str_replace("\r","",$result));
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Sabre\VObject\Recur;
use
Sabre\VObject\Reader;
class IssueEXDATETest extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \Sabre\VObject\Recur\NoInstancesException
*/
function testRecurrence() {
$input = <<<ICS
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
BEGIN:VEVENT
DTSTART;TZID=Europe/Berlin:20130329T140000
DTEND;TZID=Europe/Berlin:20130329T153000
RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20130412T115959Z
EXDATE;TZID=Europe/Berlin:20130405T140000
EXDATE;TZID=Europe/Berlin:20130329T140000
DTSTAMP:20140916T201215Z
UID:foo
SEQUENCE:1
SUMMARY:foo
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
$it = new EventIterator($vcal, 'foo');
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Sabre\VObject\RecurrenceIterator;
use Sabre\VObject\Reader;
use DateTime;
class OverrideFirstEventTest extends \PHPUnit_Framework_TestCase {
function testOverrideFirstEvent() {
$input = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20140803T120000Z
RRULE:FREQ=WEEKLY
SUMMARY:Original
END:VEVENT
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140803T120000Z
DTSTART:20140803T120000Z
SUMMARY:Overridden
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$vcal->expand(new DateTime('2014-08-01'), new DateTime('2014-09-01'));
$expected = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
RECURRENCE-ID:20140803T120000Z
DTSTART:20140803T120000Z
SUMMARY:Overridden
END:VEVENT
BEGIN:VEVENT
UID:foobar
DTSTART:20140810T120000Z
SUMMARY:Original
RECURRENCE-ID:20140810T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
DTSTART:20140817T120000Z
SUMMARY:Original
RECURRENCE-ID:20140817T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
DTSTART:20140824T120000Z
SUMMARY:Original
RECURRENCE-ID:20140824T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
DTSTART:20140831T120000Z
SUMMARY:Original
RECURRENCE-ID:20140831T120000Z
END:VEVENT
END:VCALENDAR
ICS;
$newIcs = $vcal->serialize();
$newIcs = str_replace("\r\n","\n", $newIcs);
$this->assertEquals(
$expected,
$newIcs
);
}
function testRemoveFirstEvent() {
$input = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20140803T120000Z
RRULE:FREQ=WEEKLY
EXDATE:20140803T120000Z
SUMMARY:Original
END:VEVENT
END:VCALENDAR
ICS;
$vcal = Reader::read($input);
$vcal->expand(new DateTime('2014-08-01'), new DateTime('2014-08-19'));
$expected = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20140810T120000Z
SUMMARY:Original
RECURRENCE-ID:20140810T120000Z
END:VEVENT
BEGIN:VEVENT
UID:foobar
DTSTART:20140817T120000Z
SUMMARY:Original
RECURRENCE-ID:20140817T120000Z
END:VEVENT
END:VCALENDAR
ICS;
$newIcs = $vcal->serialize();
$newIcs = str_replace("\r\n","\n", $newIcs);
$this->assertEquals(
$expected,
$newIcs
);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Sabre\VObject\Recur;
use Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Reader;
/**
* Testing case when overridden recurring events have same start date.
*
* Class SameDateForRecurringEventsTest
*/
class SameDateForRecurringEventsTest extends \PHPUnit_Framework_TestCase
{
/**
* Checking is all events iterated by EventIterator.
*/
public function testAllEventsArePresentInIterator()
{
$ics = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:1
DTSTART;TZID=Europe/Kiev:20160713T110000
DTEND;TZID=Europe/Kiev:20160713T113000
RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3
END:VEVENT
BEGIN:VEVENT
UID:2
DTSTART;TZID=Europe/Kiev:20160713T110000
DTEND;TZID=Europe/Kiev:20160713T113000
RECURRENCE-ID;TZID=Europe/Kiev:20160714T110000
END:VEVENT
BEGIN:VEVENT
UID:3
DTSTART;TZID=Europe/Kiev:20160713T110000
DTEND;TZID=Europe/Kiev:20160713T113000
RECURRENCE-ID;TZID=Europe/Kiev:20160715T110000
END:VEVENT
BEGIN:VEVENT
UID:4
DTSTART;TZID=Europe/Kiev:20160713T110000
DTEND;TZID=Europe/Kiev:20160713T113000
RECURRENCE-ID;TZID=Europe/Kiev:20160716T110000
END:VEVENT
END:VCALENDAR
ICS;
$vCalendar = Reader::read($ics);
$eventIterator = new EventIterator($vCalendar->getComponents());
$this->assertEquals(4, iterator_count($eventIterator), 'in ICS 4 events');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Sabre\VObject\Recur;
use DateTime;
use DateTimeZone;
class RDateIteratorTest extends \PHPUnit_Framework_TestCase {
function testSimple() {
$utc = new DateTimeZone('UTC');
$it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc));
$expected = array(
new DateTime('2014-08-01 00:00:00', $utc),
new DateTime('2014-09-01 00:00:00', $utc),
new DateTime('2014-10-01 00:00:00', $utc),
);
$this->assertEquals(
$expected,
iterator_to_array($it)
);
$this->assertFalse($it->isInfinite());
}
function testFastForward() {
$utc = new DateTimeZone('UTC');
$it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc));
$it->fastForward(new DateTime('2014-08-15 00:00:00'));
$result = array();
while($it->valid()) {
$result[] = $it->current();
$it->next();
}
$expected = array(
new DateTime('2014-09-01 00:00:00', $utc),
new DateTime('2014-10-01 00:00:00', $utc),
);
$this->assertEquals(
$expected,
$result
);
$this->assertFalse($it->isInfinite());
}
}

View File

@@ -0,0 +1,713 @@
<?php
namespace Sabre\VObject\Recur;
use DateTime;
use DateTimeZone;
class RRuleIteratorTest extends \PHPUnit_Framework_TestCase {
function testHourly() {
$this->parse(
'FREQ=HOURLY;INTERVAL=3;COUNT=12',
'2011-10-07 12:00:00',
array(
'2011-10-07 12:00:00',
'2011-10-07 15:00:00',
'2011-10-07 18:00:00',
'2011-10-07 21:00:00',
'2011-10-08 00:00:00',
'2011-10-08 03:00:00',
'2011-10-08 06:00:00',
'2011-10-08 09:00:00',
'2011-10-08 12:00:00',
'2011-10-08 15:00:00',
'2011-10-08 18:00:00',
'2011-10-08 21:00:00',
)
);
}
function testDaily() {
$this->parse(
'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z',
'2011-10-07',
array(
'2011-10-07 00:00:00',
'2011-10-10 00:00:00',
'2011-10-13 00:00:00',
'2011-10-16 00:00:00',
'2011-10-19 00:00:00',
'2011-10-22 00:00:00',
'2011-10-25 00:00:00',
)
);
}
function testDailyByDayByHour() {
$this->parse(
'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7',
'2011-10-08 06:00:00',
array(
'2011-10-08 06:00:00',
'2011-10-08 07:00:00',
'2011-10-09 06:00:00',
'2011-10-09 07:00:00',
'2011-10-15 06:00:00',
'2011-10-15 07:00:00',
'2011-10-16 06:00:00',
'2011-10-16 07:00:00',
'2011-10-22 06:00:00',
'2011-10-22 07:00:00',
'2011-10-23 06:00:00',
'2011-10-23 07:00:00',
)
);
}
function testDailyByHour() {
$this->parse(
'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15',
'2012-10-11 12:00:00',
array(
'2012-10-11 12:00:00',
'2012-10-11 13:00:00',
'2012-10-11 14:00:00',
'2012-10-11 15:00:00',
'2012-10-13 10:00:00',
'2012-10-13 11:00:00',
'2012-10-13 12:00:00',
'2012-10-13 13:00:00',
'2012-10-13 14:00:00',
'2012-10-13 15:00:00',
'2012-10-15 10:00:00',
'2012-10-15 11:00:00',
)
);
}
function testDailyByDay() {
$this->parse(
'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR',
'2011-10-07 12:00:00',
array(
'2011-10-07 12:00:00',
'2011-10-11 12:00:00',
'2011-10-19 12:00:00',
'2011-10-21 12:00:00',
'2011-10-25 12:00:00',
'2011-11-02 12:00:00',
'2011-11-04 12:00:00',
'2011-11-08 12:00:00',
'2011-11-16 12:00:00',
'2011-11-18 12:00:00',
'2011-11-22 12:00:00',
'2011-11-30 12:00:00',
)
);
}
function testDailyCount() {
$this->parse(
'FREQ=DAILY;COUNT=5',
'2014-08-01 18:03:00',
array(
'2014-08-01 18:03:00',
'2014-08-02 18:03:00',
'2014-08-03 18:03:00',
'2014-08-04 18:03:00',
'2014-08-05 18:03:00',
)
);
}
function testDailyByMonth() {
$this->parse(
'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU',
'2007-10-04 16:00:00',
array(
"2013-09-29 16:00:00",
"2013-10-06 16:00:00",
"2013-10-13 16:00:00",
"2013-10-20 16:00:00",
"2013-10-27 16:00:00",
"2014-09-07 16:00:00"
),
'2013-09-28'
);
}
function testWeekly() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=2;COUNT=10',
'2011-10-07 00:00:00',
array(
'2011-10-07 00:00:00',
'2011-10-21 00:00:00',
'2011-11-04 00:00:00',
'2011-11-18 00:00:00',
'2011-12-02 00:00:00',
'2011-12-16 00:00:00',
'2011-12-30 00:00:00',
'2012-01-13 00:00:00',
'2012-01-27 00:00:00',
'2012-02-10 00:00:00',
)
);
}
function testWeeklyByDay() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA',
'2014-08-01 00:00:00',
array(
'2014-08-01 00:00:00',
'2014-08-04 00:00:00',
'2014-08-11 00:00:00',
'2014-08-18 00:00:00',
)
);
}
function testWeeklyByDay2() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU',
'2011-10-07 00:00:00',
array(
'2011-10-07 00:00:00',
'2011-10-18 00:00:00',
'2011-10-19 00:00:00',
'2011-10-21 00:00:00',
'2011-11-01 00:00:00',
'2011-11-02 00:00:00',
'2011-11-04 00:00:00',
'2011-11-15 00:00:00',
'2011-11-16 00:00:00',
'2011-11-18 00:00:00',
'2011-11-29 00:00:00',
'2011-11-30 00:00:00',
)
);
}
function testWeeklyByDayByHour() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10',
'2011-10-07 08:00:00',
array(
'2011-10-07 08:00:00',
'2011-10-07 09:00:00',
'2011-10-07 10:00:00',
'2011-10-18 08:00:00',
'2011-10-18 09:00:00',
'2011-10-18 10:00:00',
'2011-10-19 08:00:00',
'2011-10-19 09:00:00',
'2011-10-19 10:00:00',
'2011-10-21 08:00:00',
'2011-10-21 09:00:00',
'2011-10-21 10:00:00',
'2011-11-01 08:00:00',
'2011-11-01 09:00:00',
'2011-11-01 10:00:00',
)
);
}
function testWeeklyByDaySpecificHour() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU',
'2011-10-07 18:00:00',
array(
'2011-10-07 18:00:00',
'2011-10-18 18:00:00',
'2011-10-19 18:00:00',
'2011-10-21 18:00:00',
'2011-11-01 18:00:00',
'2011-11-02 18:00:00',
'2011-11-04 18:00:00',
'2011-11-15 18:00:00',
'2011-11-16 18:00:00',
'2011-11-18 18:00:00',
'2011-11-29 18:00:00',
'2011-11-30 18:00:00',
)
);
}
function testMonthly() {
$this->parse(
'FREQ=MONTHLY;INTERVAL=3;COUNT=5',
'2011-12-05 00:00:00',
array(
'2011-12-05 00:00:00',
'2012-03-05 00:00:00',
'2012-06-05 00:00:00',
'2012-09-05 00:00:00',
'2012-12-05 00:00:00',
)
);
}
function testMonlthyEndOfMonth() {
$this->parse(
'FREQ=MONTHLY;INTERVAL=2;COUNT=12',
'2011-12-31 00:00:00',
array(
'2011-12-31 00:00:00',
'2012-08-31 00:00:00',
'2012-10-31 00:00:00',
'2012-12-31 00:00:00',
'2013-08-31 00:00:00',
'2013-10-31 00:00:00',
'2013-12-31 00:00:00',
'2014-08-31 00:00:00',
'2014-10-31 00:00:00',
'2014-12-31 00:00:00',
'2015-08-31 00:00:00',
'2015-10-31 00:00:00',
)
);
}
function testMonthlyByMonthDay() {
$this->parse(
'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7',
'2011-01-01 00:00:00',
array(
'2011-01-01 00:00:00',
'2011-01-25 00:00:00',
'2011-01-31 00:00:00',
'2011-06-01 00:00:00',
'2011-06-24 00:00:00',
'2011-11-01 00:00:00',
'2011-11-24 00:00:00',
'2012-04-01 00:00:00',
'2012-04-24 00:00:00',
)
);
}
function testMonthlyByDay() {
$this->parse(
'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH',
'2011-01-03 00:00:00',
array(
'2011-01-03 00:00:00',
'2011-01-05 00:00:00',
'2011-01-10 00:00:00',
'2011-01-17 00:00:00',
'2011-01-18 00:00:00',
'2011-01-20 00:00:00',
'2011-01-24 00:00:00',
'2011-01-31 00:00:00',
'2011-03-02 00:00:00',
'2011-03-07 00:00:00',
'2011-03-14 00:00:00',
'2011-03-17 00:00:00',
'2011-03-21 00:00:00',
'2011-03-22 00:00:00',
'2011-03-28 00:00:00',
'2011-05-02 00:00:00',
)
);
}
function testMonthlyByDayByMonthDay() {
$this->parse(
'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1',
'2011-08-01 00:00:00',
array(
'2011-08-01 00:00:00',
'2012-10-01 00:00:00',
'2013-04-01 00:00:00',
'2013-07-01 00:00:00',
'2014-09-01 00:00:00',
'2014-12-01 00:00:00',
'2015-06-01 00:00:00',
'2016-02-01 00:00:00',
'2016-08-01 00:00:00',
'2017-05-01 00:00:00',
)
);
}
function testMonthlyByDayBySetPos() {
$this->parse(
'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1',
'2011-01-03 00:00:00',
array(
'2011-01-03 00:00:00',
'2011-01-31 00:00:00',
'2011-02-01 00:00:00',
'2011-02-28 00:00:00',
'2011-03-01 00:00:00',
'2011-03-31 00:00:00',
'2011-04-01 00:00:00',
'2011-04-29 00:00:00',
'2011-05-02 00:00:00',
'2011-05-31 00:00:00',
)
);
}
function testYearly() {
$this->parse(
'FREQ=YEARLY;COUNT=10;INTERVAL=3',
'2011-01-01 00:00:00',
array(
'2011-01-01 00:00:00',
'2014-01-01 00:00:00',
'2017-01-01 00:00:00',
'2020-01-01 00:00:00',
'2023-01-01 00:00:00',
'2026-01-01 00:00:00',
'2029-01-01 00:00:00',
'2032-01-01 00:00:00',
'2035-01-01 00:00:00',
'2038-01-01 00:00:00',
)
);
}
function testYearlyLeapYear() {
$this->parse(
'FREQ=YEARLY;COUNT=3',
'2012-02-29 00:00:00',
array(
'2012-02-29 00:00:00',
'2016-02-29 00:00:00',
'2020-02-29 00:00:00',
)
);
}
function testYearlyByMonth() {
$this->parse(
'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10',
'2011-04-07 00:00:00',
array(
'2011-04-07 00:00:00',
'2011-10-07 00:00:00',
'2015-04-07 00:00:00',
'2015-10-07 00:00:00',
'2019-04-07 00:00:00',
'2019-10-07 00:00:00',
'2023-04-07 00:00:00',
'2023-10-07 00:00:00',
)
);
}
function testYearlyByMonthByDay() {
$this->parse(
'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU',
'2011-04-04 00:00:00',
array(
'2011-04-04 00:00:00',
'2011-04-24 00:00:00',
'2011-10-03 00:00:00',
'2011-10-30 00:00:00',
'2016-04-04 00:00:00',
'2016-04-24 00:00:00',
'2016-10-03 00:00:00',
'2016-10-30 00:00:00',
)
);
}
function testFastForward() {
// The idea is that we're fast-forwarding too far in the future, so
// there will be no results left.
$this->parse(
'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU',
'2011-04-04 00:00:00',
array(),
'2020-05-05 00:00:00'
);
}
/**
* The bug that was in the
* system before would fail on the 5th tuesday of the month, if the 5th
* tuesday did not exist.
*
* A pretty slow test. Had to be marked as 'medium' for phpunit to not die
* after 1 second. Would be good to optimize later.
*
* @medium
*/
function testFifthTuesdayProblem() {
$this->parse(
'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU',
'2007-10-04 14:46:42',
array(
"2007-10-04 14:46:42",
)
);
}
/**
* This bug came from a Fruux customer. This would result in a never-ending
* request.
*/
function testFastFowardTooFar() {
$this->parse(
'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1',
'2009-04-20 18:00:00',
array(
'2009-04-20 18:00:00',
'2009-04-27 18:00:00',
'2009-05-04 18:00:00',
'2009-05-11 18:00:00',
'2009-05-18 18:00:00',
'2009-05-25 18:00:00',
'2009-06-01 18:00:00',
'2009-06-08 18:00:00',
'2009-06-15 18:00:00',
'2009-06-22 18:00:00',
'2009-06-29 18:00:00',
)
);
}
/**
* This also at one point caused an infinite loop. We're keeping the test.
*/
function testYearlyByMonthLoop() {
$this->parse(
'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA',
'2012-01-01 15:45:00',
array(
'2012-02-01 15:45:00',
),
'2012-01-29 23:00:00'
);
}
/**
* Something, somewhere produced an ics with an interval set to 0. Because
* this means we increase the current day (or week, month) by 0, this also
* results in an infinite loop.
*
* @expectedException InvalidArgumentException
*/
function testZeroInterval() {
$this->parse(
'FREQ=YEARLY;INTERVAL=0',
'2012-08-24 14:57:00',
array(),
'2013-01-01 23:00:00'
);
}
/**
* @expectedException InvalidArgumentException
*/
function testInvalidFreq() {
$this->parse(
'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z',
'2011-10-07',
array()
);
}
/**
* @expectedException InvalidArgumentException
*/
function testByDayBadOffset() {
$this->parse(
'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA',
'2014-08-01 00:00:00',
array()
);
}
function testUntilBeginHAsTimezone() {
$this->parse(
'FREQ=WEEKLY;UNTIL=20131118T183000',
'2013-09-23 18:30:00',
array(
'2013-09-23 18:30:00',
'2013-09-30 18:30:00',
'2013-10-07 18:30:00',
'2013-10-14 18:30:00',
'2013-10-21 18:30:00',
'2013-10-28 18:30:00',
'2013-11-04 18:30:00',
'2013-11-11 18:30:00',
'2013-11-18 18:30:00',
),
null,
'America/New_York'
);
}
function testUntilBeforeDtStart() {
$this->parse(
'FREQ=DAILY;UNTIL=20140101T000000Z',
'2014-08-02 00:15:00',
array(
'2014-08-02 00:15:00',
)
);
}
function testIgnoredStuff() {
$this->parse(
'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2',
'2014-08-02 00:15:00',
array(
'2014-08-02 00:15:00',
'2014-08-03 00:15:00',
)
);
}
function testMinusFifthThursday() {
$this->parse(
'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4',
'2015-01-01 00:15:00',
array(
'2015-01-01 00:15:00',
'2015-01-08 00:15:00',
'2015-02-05 00:15:00',
'2015-03-05 00:15:00'
)
);
}
/**
* @expectedException InvalidArgumentException
*/
function testUnsupportedPart() {
$this->parse(
'FREQ=DAILY;BYWODAN=1',
'2014-08-02 00:15:00',
array()
);
}
function testIteratorFunctions() {
$parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13'));
$parser->next();
$this->assertEquals(
new DateTime('2014-08-03 00:00:13'),
$parser->current()
);
$this->assertEquals(
1,
$parser->key()
);
$parser->rewind();
$this->assertEquals(
new DateTime('2014-08-02 00:00:13'),
$parser->current()
);
$this->assertEquals(
0,
$parser->key()
);
}
function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') {
$dt = new DateTime($start, new DateTimeZone($tz));
$parser = new RRuleIterator($rule, $dt);
if ($fastForward) {
$parser->fastForward(new DateTime($fastForward));
}
$result = array();
while($parser->valid()) {
$item = $parser->current();
$result[] = $item->format('Y-m-d H:i:s');
if ($parser->isInfinite() && count($result) >= count($expected)) {
break;
}
$parser->next();
}
$this->assertEquals(
$expected,
$result
);
}
}

View File

@@ -0,0 +1,39 @@
BEGIN:VCALENDAR
VERSION:2.0
X-WR-TIMEZONE:America/New_York
PRODID:-//www.churchcommunitybuilder.com//Church Community Builder//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Test Event
BEGIN:VTIMEZONE
TZID:America/New_York
X-LIC-LOCATION:America/New_York
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:10621-1440@ccbchurch.com
DTSTART;TZID=America/New_York:20130923T183000
DTEND;TZID=America/New_York:20130923T203000
DTSTAMP:20131216T170211
RRULE:FREQ=WEEKLY;UNTIL=20131118T183000
CREATED:20130423T161111
DESCRIPTION:Test Event ending November 11, 2013
LAST-MODIFIED:20131126T163428
SEQUENCE:1387231331
SUMMARY:Test
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR

View File

@@ -0,0 +1,20 @@
<?php
namespace Sabre\VObject;
/**
* This issue was pointed out in Issue 55. \r should be stripped completely
* when encoding property values.
*/
class SlashRTest extends \PHPUnit_Framework_TestCase {
function testEncode() {
$vcal = new Component\VCalendar();
$prop = $vcal->add('test', "abc\r\ndef");
$this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize());
}
}

View File

@@ -0,0 +1,325 @@
<?php
namespace Sabre\VObject\Splitter;
use Sabre\VObject;
class ICalendarTest extends \PHPUnit_Framework_TestCase {
protected $version;
function setUp() {
$this->version = VObject\Version::VERSION;
}
function createStream($data) {
$stream = fopen('php://memory','r+');
fwrite($stream, $data);
rewind($stream);
return $stream;
}
function testICalendarImportValidEvent() {
$data = <<<EOT
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foo
DTSTAMP:20140122T233226Z
DTSTART:20140101T070000Z
END:VEVENT
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
while($object=$objects->getNext()) {
$return .= $object->serialize();
}
$this->assertEquals(array(), VObject\Reader::read($return)->validate());
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testICalendarImportWrongType() {
$data = <<<EOT
BEGIN:VCARD
UID:foo1
END:VCARD
BEGIN:VCARD
UID:foo2
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
}
function testICalendarImportEndOfData() {
$data = <<<EOT
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foo
DTSTAMP:20140122T233226Z
END:VEVENT
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
while($object=$objects->getNext()) {
$return .= $object->serialize();
}
$this->assertNull($object=$objects->getNext());
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testICalendarImportInvalidEvent() {
$data = <<<EOT
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
}
function testICalendarImportMultipleValidEvents() {
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo1
DTSTAMP:20140122T233226Z
DTSTART:20140101T050000Z
END:VEVENT
EOT;
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo2
DTSTAMP:20140122T233226Z
DTSTART:20140101T060000Z
END:VEVENT
EOT;
$data = <<<EOT
BEGIN:VCALENDAR
$event[0]
$event[1]
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
$i = 0;
while($object=$objects->getNext()) {
$expected = <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $this->version//EN
CALSCALE:GREGORIAN
$event[$i]
END:VCALENDAR
EOT;
$return .= $object->serialize();
$expected = str_replace("\n", "\r\n", $expected);
$this->assertEquals($expected, $object->serialize());
$i++;
}
$this->assertEquals(array(), VObject\Reader::read($return)->validate());
}
function testICalendarImportEventWithoutUID() {
$data = <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $this->version//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART:20140101T040000Z
DTSTAMP:20140122T233226Z
END:VEVENT
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
while($object=$objects->getNext()) {
$return .= $object->serialize();
}
$messages = VObject\Reader::read($return)->validate();
if ($messages) {
$messages = array_map(
function($item) { return $item['message']; },
$messages
);
$this->fail('Validation errors: ' . implode("\n", $messages));
} else {
$this->assertEquals(array(), $messages);
}
}
function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents() {
$timezones = <<<EOT
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
DTSTART:19810329T020000
TZNAME:MESZ
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
DTSTART:19961027T030000
TZNAME:MEZ
TZOFFSETTO:+0100
END:STANDARD
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:Europe/London
BEGIN:DAYLIGHT
TZOFFSETFROM:+0000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
DTSTART:19810329T010000
TZNAME:GMT+01:00
TZOFFSETTO:+0100
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
DTSTART:19961027T020000
TZNAME:GMT
TZOFFSETTO:+0000
END:STANDARD
END:VTIMEZONE
EOT;
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo1
DTSTAMP:20140122T232710Z
DTSTART:20140101T010000Z
END:VEVENT
EOT;
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo2
DTSTAMP:20140122T232710Z
DTSTART:20140101T020000Z
END:VEVENT
EOT;
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo3
DTSTAMP:20140122T232710Z
DTSTART:20140101T030000Z
END:VEVENT
EOT;
$data = <<<EOT
BEGIN:VCALENDAR
$timezones
$event[0]
$event[1]
$event[2]
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
$i = 0;
while($object=$objects->getNext()) {
$expected = <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $this->version//EN
CALSCALE:GREGORIAN
$timezones
$event[$i]
END:VCALENDAR
EOT;
$expected = str_replace("\n", "\r\n", $expected);
$this->assertEquals($expected, $object->serialize());
$return .= $object->serialize();
$i++;
}
$this->assertEquals(array(), VObject\Reader::read($return)->validate());
}
function testICalendarImportWithOutVTIMEZONES() {
$data = <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.8//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20120605T072109Z
UID:D6716295-C10F-4B20-82F9-E1A3026C7DCF
DTEND;VALUE=DATE:20120717
TRANSP:TRANSPARENT
SUMMARY:Start Vorbereitung
DTSTART;VALUE=DATE:20120716
DTSTAMP:20120605T072115Z
SEQUENCE:2
BEGIN:VALARM
X-WR-ALARMUID:A99EDA6A-35EB-4446-B8BC-CDA3C60C627D
UID:A99EDA6A-35EB-4446-B8BC-CDA3C60C627D
TRIGGER:-PT15H
X-APPLE-DEFAULT-ALARM:TRUE
ATTACH;VALUE=URI:Basso
ACTION:AUDIO
END:VALARM
END:VEVENT
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$objects = new ICalendar($tempFile);
$return = "";
while($object=$objects->getNext()) {
$return .= $object->serialize();
}
$messages = VObject\Reader::read($return)->validate();
$this->assertEquals(array(), $messages);
}
}

View File

@@ -0,0 +1,195 @@
<?php
namespace Sabre\VObject\Splitter;
use Sabre\VObject;
class VCardTest extends \PHPUnit_Framework_TestCase {
function createStream($data) {
$stream = fopen('php://memory','r+');
fwrite($stream, $data);
rewind($stream);
return $stream;
}
function testVCardImportValidVCard() {
$data = <<<EOT
BEGIN:VCARD
UID:foo
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
$count = 0;
while($objects->getNext()) {
$count++;
}
$this->assertEquals(1, $count);
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testVCardImportWrongType() {
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo1
DTSTAMP:20140122T233226Z
DTSTART:20140101T050000Z
END:VEVENT
EOT;
$event[] = <<<EOT
BEGIN:VEVENT
UID:foo2
DTSTAMP:20140122T233226Z
DTSTART:20140101T060000Z
END:VEVENT
EOT;
$data = <<<EOT
BEGIN:VCALENDAR
$event[0]
$event[1]
END:VCALENDAR
EOT;
$tempFile = $this->createStream($data);
$splitter = new VCard($tempFile);
while($object=$splitter->getNext()) {
}
}
function testVCardImportValidVCardsWithCategories() {
$data = <<<EOT
BEGIN:VCARD
UID:card-in-foo1-and-foo2
CATEGORIES:foo1,foo2
END:VCARD
BEGIN:VCARD
UID:card-in-foo1
CATEGORIES:foo1
END:VCARD
BEGIN:VCARD
UID:card-in-foo3
CATEGORIES:foo3
END:VCARD
BEGIN:VCARD
UID:card-in-foo1-and-foo3
CATEGORIES:foo1\,foo3
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$splitter = new VCard($tempFile);
$count = 0;
while($object=$splitter->getNext()) {
$count++;
}
$this->assertEquals(4, $count);
}
function testVCardImportEndOfData() {
$data = <<<EOT
BEGIN:VCARD
UID:foo
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
$object=$objects->getNext();
$this->assertNull($objects->getNext());
}
/**
* @expectedException \Sabre\VObject\ParseException
*/
function testVCardImportCheckInvalidArgumentException() {
$data = <<<EOT
BEGIN:FOO
END:FOO
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
while($objects->getNext()) { }
}
function testVCardImportMultipleValidVCards() {
$data = <<<EOT
BEGIN:VCARD
UID:foo
END:VCARD
BEGIN:VCARD
UID:foo
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
$count = 0;
while($objects->getNext()) {
$count++;
}
$this->assertEquals(2, $count);
}
function testImportMultipleSeparatedWithNewLines() {
$data = <<<EOT
BEGIN:VCARD
UID:foo
END:VCARD
BEGIN:VCARD
UID:foo
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
$count = 0;
while ($objects->getNext()) {
$count++;
}
$this->assertEquals(2, $count);
}
function testVCardImportVCardWithoutUID() {
$data = <<<EOT
BEGIN:VCARD
END:VCARD
EOT;
$tempFile = $this->createStream($data);
$objects = new VCard($tempFile);
$count = 0;
while($objects->getNext()) {
$count++;
}
$this->assertEquals(1, $count);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Sabre\VObject;
class StringUtilTest extends \PHPUnit_Framework_TestCase {
function testNonUTF8() {
$string = StringUtil::isUTF8(chr(0xbf));
$this->assertEquals(false, $string);
}
function testIsUTF8() {
$string = StringUtil::isUTF8('I 💚 SabreDAV');
$this->assertEquals(true, $string);
}
function testUTF8ControlChar() {
$string = StringUtil::isUTF8(chr(0x00));
$this->assertEquals(false, $string);
}
function testConvertToUTF8nonUTF8() {
$string = StringUtil::convertToUTF8(chr(0xbf));
$this->assertEquals(utf8_encode(chr(0xbf)), $string);
}
function testConvertToUTF8IsUTF8() {
$string = StringUtil::convertToUTF8('I 💚 SabreDAV');
$this->assertEquals('I 💚 SabreDAV', $string);
}
function testConvertToUTF8ControlChar() {
$string = StringUtil::convertToUTF8(chr(0x00));
$this->assertEquals('', $string);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Sabre\VObject;
class TestCase extends \PHPUnit_Framework_TestCase {
/**
* This method tests wether two vcards or icalendar objects are
* semantically identical.
*
* It supports objects being supplied as strings, streams or
* Sabre\VObject\Component instances.
*
* PRODID is removed from both objects as this is often variable.
*
* @param resource|string|Component $expected
* @param resource|string|Component $actual
* @param string $message
*/
function assertVObjEquals($expected, $actual, $message = '') {
$self = $this;
$getObj = function($input) use ($self) {
if (is_resource($input)) {
$input = stream_get_contents($input);
}
if (is_string($input)) {
$input = Reader::read($input);
}
if (!$input instanceof Component) {
$this->fail('Input must be a string, stream or VObject component');
}
unset($input->PRODID);
return $input;
};
$expected = $getObj($expected);
$actual = $getObj($actual);
$this->assertEquals(
$expected->serialize(),
$actual->serialize(),
$message
);
}
}

View File

@@ -0,0 +1,369 @@
<?php
namespace Sabre\VObject;
class TimezoneUtilTest extends \PHPUnit_Framework_TestCase {
function setUp() {
// clearning the tz cache
TimezoneUtil::$map = null;
}
/**
* @dataProvider getMapping
*/
function testCorrectTZ($timezoneName) {
try {
$tz = new \DateTimeZone($timezoneName);
$this->assertInstanceOf('DateTimeZone', $tz);
} catch (\Exception $e) {
if (strpos($e->getMessage(), "Unknown or bad timezone")!==false) {
$this->markTestSkipped($timezoneName . ' is not (yet) supported in this PHP version. Update pecl/timezonedb');
} else {
throw $e;
}
}
}
function getMapping() {
TimeZoneUtil::loadTzMaps();
// PHPUNit requires an array of arrays
return array_map(
function($value) {
return array($value);
},
TimeZoneUtil::$map
);
}
function testExchangeMap() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
X-MICROSOFT-CDO-TZID:2
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$ex = new \DateTimeZone('Europe/Lisbon');
$this->assertEquals($ex->getName(), $tz->getName());
}
function testWetherMicrosoftIsStillInsane() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:(GMT+01.00) Sarajevo/Warsaw/Zagreb
X-MICROSOFT-CDO-TZID:2
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('(GMT+01.00) Sarajevo/Warsaw/Zagreb', Reader::read($vobj));
$ex = new \DateTimeZone('Europe/Sarajevo');
$this->assertEquals($ex->getName(), $tz->getName());
}
function testUnknownExchangeId() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
X-MICROSOFT-CDO-TZID:2000
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$ex = new \DateTimeZone(date_default_timezone_get());
$this->assertEquals($ex->getName(), $tz->getName());
}
function testWindowsTimeZone() {
$tz = TimeZoneUtil::getTimeZone('Eastern Standard Time');
$ex = new \DateTimeZone('America/New_York');
$this->assertEquals($ex->getName(), $tz->getName());
}
/**
* @dataProvider getPHPTimeZoneIdentifiers
*/
function testTimeZoneIdentifiers($tzid) {
$tz = TimeZoneUtil::getTimeZone($tzid);
$ex = new \DateTimeZone($tzid);
$this->assertEquals($ex->getName(), $tz->getName());
}
/**
* @dataProvider getPHPTimeZoneBCIdentifiers
*/
function testTimeZoneBCIdentifiers($tzid) {
$tz = TimeZoneUtil::getTimeZone($tzid);
$ex = new \DateTimeZone($tzid);
$this->assertEquals($ex->getName(), $tz->getName());
}
function getPHPTimeZoneIdentifiers() {
// PHPUNit requires an array of arrays
return array_map(
function($value) {
return array($value);
},
\DateTimeZone::listIdentifiers()
);
}
function getPHPTimeZoneBCIdentifiers() {
// PHPUNit requires an array of arrays
return array_map(
function($value) {
return array($value);
},
TimeZoneUtil::getIdentifiersBC()
);
}
function testTimezoneOffset() {
$tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true);
if (version_compare(PHP_VERSION, '5.5.10', '>=') && !defined('HHVM_VERSION')) {
$ex = new \DateTimeZone('-04:00');
} else {
$ex = new \DateTimeZone('Etc/GMT-4');
}
$this->assertEquals($ex->getName(), $tz->getName());
}
/**
* @expectedException InvalidArgumentException
*/
function testTimezoneFail() {
$tz = TimeZoneUtil::getTimeZone('FooBar', null, true);
}
function testFallBack() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$ex = new \DateTimeZone(date_default_timezone_get());
$this->assertEquals($ex->getName(), $tz->getName());
}
function testLjubljanaBug() {
$vobj = <<<HI
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana
X-LIC-LOCATION:Europe/Ljubljana
BEGIN:STANDARD
TZNAME:CET
DTSTART:19701028T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19700325T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:foo
DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana:
20121003T080000
DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana:
20121003T083000
TRANSP:OPAQUE
SEQUENCE:2
SUMMARY:testing
CREATED:20121002T172613Z
LAST-MODIFIED:20121002T172613Z
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana', Reader::read($vobj));
$ex = new \DateTimeZone('Europe/Ljubljana');
$this->assertEquals($ex->getName(), $tz->getName());
}
function testWeirdSystemVLICs() {
$vobj = <<<HI
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT
X-LIC-LOCATION:SystemV/EST5EDT
BEGIN:STANDARD
TZNAME:EST
DTSTART:19701104T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:EDT
DTSTART:19700311T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:20121026T021107Z-6301-1000-1-0@chAir
DTSTAMP:20120905T172126Z
DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT:
20121026T153000
DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT:
20121026T160000
TRANSP:OPAQUE
SEQUENCE:5
SUMMARY:pick up Ibby
CLASS:PUBLIC
CREATED:20121026T021108Z
LAST-MODIFIED:20121026T021118Z
X-EVOLUTION-MOVE-CALENDAR:1
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT', Reader::read($vobj), true);
$ex = new \DateTimeZone('America/New_York');
$this->assertEquals($ex->getName(), $tz->getName());
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Sabre\VObject;
class UUIDUtilTest extends \PHPUnit_Framework_TestCase {
function testValidateUUID() {
$this->assertTrue(
UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555')
);
$this->assertFalse(
UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555')
);
$this->assertTrue(
UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555')
);
$this->assertFalse(
UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555')
);
}
/**
* @depends testValidateUUID
*/
function testGetUUID() {
$this->assertTrue(
UUIDUtil::validateUUID(
UUIDUtil::getUUID()
)
);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Sabre\VObject;
/**
* Assorted vcard 2.1 tests.
*/
class VCard21Test extends \PHPUnit_Framework_TestCase {
function testPropertyWithNoName() {
$input = <<<VCF
BEGIN:VCARD\r
VERSION:2.1\r
EMAIL;HOME;WORK:evert@fruux.com\r
END:VCARD\r
VCF;
$vobj = Reader::read($input);
$output = $vobj->serialize($input);
$this->assertEquals($input, $output);
}
function testPropertyPadValueCount() {
$input = <<<VCF
BEGIN:VCARD
VERSION:2.1
N:Foo
END:VCARD
VCF;
$vobj = Reader::read($input);
$output = $vobj->serialize($input);
$expected = <<<VCF
BEGIN:VCARD\r
VERSION:2.1\r
N:Foo;;;;\r
END:VCARD\r
VCF;
$this->assertEquals($expected, $output);
}
}

View File

@@ -0,0 +1,531 @@
<?php
namespace Sabre\VObject;
class VCardConverterTest extends TestCase {
function testConvert30to40() {
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
PRODID:foo
FN;CHARSET=UTF-8:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
ITEM1.TEL:+1 444 555 666
ITEM1.X-ABLABEL:CustomLabel
PHOTO;ENCODING=b;TYPE=JPEG,HOME:Zm9v
PHOTO;ENCODING=b;TYPE=GIF:Zm9v
PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
X-ABShowAs:COMPANY
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
ITEM1.TEL:+1 444 555 666
ITEM1.X-ABLABEL:CustomLabel
PHOTO;TYPE=HOME:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif;base64,Zm9v
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
KIND:ORG
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testConvert40to40() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
PHOTO:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif;base64,Zm9v
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
PHOTO:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif;base64,Zm9v
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testConvert21to40() {
$input = <<<IN
BEGIN:VCARD
VERSION:2.1
N:Family;Johnson
FN:Johnson Family
TEL;HOME;VOICE:555-12345-345
ADR;HOME:;;100 Street Lane;Saubel Beach;ON;H0H0H0
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:100 Street Lane=0D=0ASaubel Beach,
ON H0H0H0
REV:20110731T040251Z
UID:12345678
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
N:Family;Johnson;;;
FN:Johnson Family
TEL;TYPE=HOME,VOICE:555-12345-345
ADR;TYPE=HOME:;;100 Street Lane;Saubel Beach;ON;H0H0H0;
REV:20110731T040251Z
UID:12345678
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testConvert30to30() {
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
PRODID:foo
FN;CHARSET=UTF-8:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
PHOTO;ENCODING=b;TYPE=JPEG:Zm9v
PHOTO;ENCODING=b;TYPE=GIF:Zm9v
PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
PRODID:foo
FN;CHARSET=UTF-8:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
PHOTO;ENCODING=b;TYPE=JPEG:Zm9v
PHOTO;ENCODING=b;TYPE=GIF:Zm9v
PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testConvert40to30() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
PRODID:foo
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
PHOTO:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif,foo
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
KIND:ORG
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
FN:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
PHOTO;ENCODING=b;TYPE=JPEG:Zm9v
PHOTO;ENCODING=b;TYPE=GIF:Zm9v
PHOTO;ENCODING=b;TYPE=PNG;X-PARAM=FOO:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
X-ABSHOWAS:COMPANY
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testConvertGroupCard() {
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
PRODID:foo
X-ADDRESSBOOKSERVER-KIND:GROUP
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
KIND:GROUP
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
X-ADDRESSBOOKSERVER-KIND:GROUP
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testBDAYConversion() {
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
PRODID:foo
BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-16
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
BDAY:--04-16
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-16
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
}
/**
* @expectedException InvalidArgumentException
*/
function testUnknownSourceVCardVersion() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.2
PRODID:foo
FN;CHARSET=UTF-8:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
ITEM1.TEL:+1 444 555 666
ITEM1.X-ABLABEL:CustomLabel
PHOTO;ENCODING=b;TYPE=JPEG,HOME:Zm9v
PHOTO;ENCODING=b;TYPE=GIF:Zm9v
PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
X-ABShowAs:COMPANY
END:VCARD
IN;
$vcard = Reader::read($input);
$vcard->convert(Document::VCARD40);
}
/**
* @expectedException InvalidArgumentException
*/
function testUnknownTargetVCardVersion() {
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
PRODID:foo
END:VCARD
IN;
$vcard = Reader::read($input);
$vcard->convert(Document::VCARD21);
}
function testConvertIndividualCard() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
PRODID:foo
KIND:INDIVIDUAL
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testAnniversary() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
ITEM1.ANNIVERSARY:20081210
END:VCARD
IN;
$output = <<<'OUT'
BEGIN:VCARD
VERSION:3.0
ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210
ITEM1.X-ABLABEL:_$!<Anniversary>!$_
ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
// Swapping input and output
list(
$input,
$output
) = array(
$output,
$input
);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testMultipleAnniversaries() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
ITEM1.ANNIVERSARY:20081210
ITEM2.ANNIVERSARY:20091210
ITEM3.ANNIVERSARY:20101210
END:VCARD
IN;
$output = <<<'OUT'
BEGIN:VCARD
VERSION:3.0
ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210
ITEM1.X-ABLABEL:_$!<Anniversary>!$_
ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210
ITEM2.X-ABLABEL:_$!<Anniversary>!$_
ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210
ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210
ITEM3.X-ABLABEL:_$!<Anniversary>!$_
ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
// Swapping input and output
list(
$input,
$output
) = array(
$output,
$input
);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testNoLabel() {
$input = <<<VCF
BEGIN:VCARD
VERSION:3.0
UID:foo
N:Doe;John;;;
FN:John Doe
item1.X-ABDATE;type=pref:2008-12-11
END:VCARD
VCF;
$vcard = Reader::read($input);
$this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
$vcard = $vcard->convert(Document::VCARD40);
$vcard = $vcard->serialize();
$converted = Reader::read($vcard);
$converted->validate();
$version = Version::VERSION;
$expected = <<<VCF
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject $version//EN
UID:foo
N:Doe;John;;;
FN:John Doe
ITEM1.X-ABDATE;PREF=1:2008-12-11
END:VCARD
VCF;
$this->assertEquals($expected, str_replace("\r","", $vcard));
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Sabre\VObject;
class VersionTest extends \PHPUnit_Framework_TestCase {
function testString() {
$v = Version::VERSION;
$this->assertEquals(-1, version_compare('2.0.0',$v));
}
}

View File

@@ -0,0 +1,352 @@
BEGIN:VCARD
VERSION:3.0
N:Benutzer;Test;;;
FN:Test Benutzer
PHOTO;BASE64:
/9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau
m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H
a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii
KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ
BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW
u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn
bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4
g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci
QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh
UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9
CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc
u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku
Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP
j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP
OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro
/nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU
LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy
9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl
G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW
QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb
94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD
5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+
dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV
4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0
sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW
rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K
rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk
HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD
xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC
yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY
itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN
AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh
dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V
DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A
RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun
8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg
QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt
pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS
nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu
lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V
5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF
tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3
Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs
uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+
1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx
sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r
VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP
X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY
2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm
P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi
yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N
t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk
OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4
V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish
yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46
ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW
KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX
e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO
lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY
MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21
MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy
WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d
6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ
HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs
HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw
ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa
KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9
iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8
Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5
z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33
yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4
NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/
BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3
evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP
4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8
nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+
RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi
JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0
xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA
GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS
P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw
WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+
6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6
1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf
rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c
VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z
nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m
PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3
En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4
wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7
3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP
7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3
wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G
00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE
rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg
B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA
6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw
cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb
juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r
PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t
7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr
nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD
aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq
/qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg
C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA
iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F
h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb
d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC
UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk
XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR
79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF
jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA
MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA
Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA
+SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W
qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE
DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM
jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR
jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI
do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze
MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S
KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn
cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ
JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz
R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR
kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd
0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb
zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/
Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf
Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa
AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht
X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp
UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO
3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK
QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH
HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/
McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka
6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi
Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy
MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u
1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up
YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH
0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB
159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA
7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG
0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm
gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS
24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l
GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd
g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34
x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9
8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I
NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ
GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe
DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey
jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN
VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP
uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU
6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9
jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt
XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0
/wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr
qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM
4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM
XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw
NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx
2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X
2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU
65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn
h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+
OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd
xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh
aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw
o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH
1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP
O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb
lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ
dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy
7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi
anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2
Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y
ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ
LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8
g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld
x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar
u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV
RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe
3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz
xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg
eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ
fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6
XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2
ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF
c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K
iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU
CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c
54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc
ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c
OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4
AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8
zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn
Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4
eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9
cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW
KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21
1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi
qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ
q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N
ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG
CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e
lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt
MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6
qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh
h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv
S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL
KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w
dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z
mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb
AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww
eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC
L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm
xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C
KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG
OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY
gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7
qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP
mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA
zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR
mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg
pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF
+T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu
mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND
bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V
2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE
9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9
QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4
QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki
RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP
xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW
ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA
bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml
jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk
1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub
c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr
co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI
gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI
iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG
WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw
tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG
7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC
SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1
R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b
AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG
31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx
obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy
Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA
GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr
csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg
0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx
bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1
oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71
LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j
TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP
HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX
bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x
0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl
PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC
s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT
LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc
FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09
9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW
56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw
2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH
wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj
pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I
/fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW
UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5
vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ
bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm
AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5
7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW
DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX
TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p
wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws
HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6
VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt
6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH
X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ
7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8
QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P
BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG
R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6
zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe
poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD
4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D
N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG
XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t
yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK
yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb
qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44
5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX
+9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA
5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC
CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye
3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w
EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg
CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68
d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE
bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC
UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH
qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF
pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H
G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX
cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/
AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw
aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG
W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa
fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw
vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p
V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma
IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw
EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G
9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2
Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6
ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+
U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH
14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr
bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt
0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw
zbVbk4/OrNpefLsnyyg5UUAf/9k=
END:VCARD

View File

@@ -0,0 +1,351 @@
BEGIN:VCARD
VERSION:2.1
PHOTO;ENCODING=BASE64;JPEG:
/9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
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
zbVbk4/OrNpefLsnyyg5UUAf/9k=
END:VCARD