pax_global_header00006660000000000000000000000064126447703030014520gustar00rootroot0000000000000052 comment=061dd47ce40074bf63da8e3d6dbe7f18b5a4f3a4 sabre-vobject-3.5.0/000077500000000000000000000000001264477030300142535ustar00rootroot00000000000000sabre-vobject-3.5.0/.gitignore000066400000000000000000000002341264477030300162420ustar00rootroot00000000000000# Composer stuff vendor/ composer.lock tests/cov/ tests/temp #vim .*.swp #binaries bin/phpunit bin/phpcs # Development stuff testdata/ # OS X .DS_Store sabre-vobject-3.5.0/.travis.yml000066400000000000000000000004011264477030300163570ustar00rootroot00000000000000language: php php: - 5.3 - 5.4 - 5.5 - 5.6 - 7 - hhvm matrix: allow_failures: - php: hhvm script: - phpunit --configuration tests/phpunit.xml - ./bin/phpcs -p --standard=tests/phpcs/ruleset.xml lib/ before_script: composer install sabre-vobject-3.5.0/ChangeLog.md000066400000000000000000000524331264477030300164330ustar00rootroot00000000000000ChangeLog ========= 3.5.0 (2016-01-11) ----------------- * This release supports PHP 7, contrary to 3.4.x versions. * BC Break: `Sabre\VObject\Property\Float` has been renamed to `Sabre\VObject\Property\FloatValue`. * BC Break: `Sabre\VObject\Property\Integer` has been renamed to `Sabre\VObject\Property\IntegerValue`. 3.4.9 (2016-01-11) ------------------ * This package now specifies in composer.json that it does not support PHP 7. For PHP 7, use version 3.5.x or 4.x. 3.4.8 (2016-01-04) ------------------ * #284: When generating `CANCEL` iTip messages, we now include `DTEND`. (@kewisch). 3.4.7 (2015-09-05) ------------------ * #253: Handle `isInTimeRange` for recurring events that have 0 valid instances. (@DominikTo, @migrax). 3.4.6 (2015-08-06) ------------------ * #250: Recurring all-day events are incorrectly included in time range requests when not using UTC in the time range. (@armin-hackmann) 3.4.5 (2015-06-02) ------------------ * #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property would throw an error. 3.4.4 (2015-05-27) ------------------ * #228: Fixed a 'party crasher' bug in the iTip broker. This would break scheduling in some cases. 3.4.3 (2015-05-19) ------------------ * #219: Corrected validation of `EXDATE` properties with more than one value. * #212: `BYSETPOS` with values below `-1` was broken and could cause infinite loops. * #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist) * #216: `ENCODING` parameter is now validated for all document types. * #217: Initializing vCard `DATE` objects with a PHP DateTime object will now work correctly. (@thomascube) 3.4.2 (2015-02-25) ------------------ * #210: iTip: Replying to an event without a master event was broken. 3.4.1 (2015-02-24) ------------------ * A minor change to ensure that unittests work correctly in the sabre/dav test-suite. 3.4.0 (2015-02-23) ------------------ * #196: Made parsing recurrence rules a lot faster on big calendars. * Updated windows timezone mappings to latest unicode version. * #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan) * #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan) * #205: Improving handling of multiple `EXDATE` when processing iTip changes. (@armin-hackmann) * #187: Fixed validator rules for `LAST-MODIFIED` properties. * #188: Retain floating times when generating instances using `Recur\EventIterator`. * #203: Skip tests for timezones that are not supported on older PHP versions, instead of a hard fail. * #204: Dealing a bit better with vCard date-time values that contained milliseconds. (which is normally invalid). (@armin-hackmann) 3.3.5 (2015-01-09) ------------------ * #168: Expanding calendars now removes objects with recurrence rules that don't have a valid recurrence instance. * #177: SCHEDULE-STATUS should not contain a reason phrase, only a status code. * #175: Parser can now read and skip the UTF-8 BOM. * #179: Added `isFloating` to `DATE-TIME` properties. * #179: Fixed jCal serialization of floating `DATE-TIME` properties. * #173: vCard converter failed for `X-ABDATE` properties that had no `X-ABLABEL`. * #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules specific for CalDAV/CardDAV servers. * #176: A missing `UID` is no longer an error, but a warning for the vCard validator, unless `PROFILE_CARDDAV` is specified. 3.3.4 (2014-11-19) ------------------ * #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and vice-versa when converting to/from vCard 4. * #154: It's now possible to easily select all vCard properties belonging to a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann) * #156: Simpler way to check if a string is UTF-8. (@Hywan) * Unittest improvements. * #159: The recurrence iterator, freebusy generator and iCalendar DATE and DATE-TIME properties can now all accept a reference timezone when working floating times or all-day events. * #159: Master events will no longer get a `RECURRENCE-ID` when expanding. * #159: `RECURRENCE-ID` for all-day events will now be correct when expanding. * #163: Added a `getTimeZone()` method to `VTIMEZONE` components. 3.3.3 (2014-10-09) ------------------ * #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the original event. * #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected. * #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is set to support the inbox feature of iOS. * #147: Bugs related to scheduling all-day events. * #148: Ignore events that have attendees but no organizer. * #149: Avoiding logging errors during timezone detection. This is a workaround for a PHP bug. * Support for "Line Islands Standard Time" windows timezone. * #154: Correctly work around vCard parameters that have a value but no name. 3.3.2 (2014-09-19) ------------------ * Changed: iTip broker now sets RSVP status to false when replies are received. * #118: iTip Message now has a `getScheduleStatus()` method. * #119: Support for detecting 'significant changes'. * #120: Support for `SCHEDULE-FORCE-SEND`. * #121: iCal demands parameters containing the + sign to be quoted. * #122: Don't generate REPLY messages for events that have been cancelled. * #123: Added `SUMMARY` to iTip messages. * #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`). * #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`. * #131: RRULE that doesn't provide a single valid instance now throws an exception. * #136: Validator rejects *all* control characters. We were missing a few. * #133: Splitter objects will throw exceptions when receiving incompatible objects. * #127: Attendees who delete recurring event instances events they had already declined earlier will no longer generate another reply. * #125: Send CANCEL messages when ORGANIZER property gets deleted. 3.3.1 (2014-08-18) ------------------ * Changed: It's now possible to pass DateTime objects when using the magic setters on properties. (`$event->DTSTART = new DateTime('now')`). * #111: iTip Broker does not process attendee adding events to EXDATE. * #112: EventIterator now sets TZID on RECURRENCE-ID. * #113: Timezone support during creation of iTip REPLY messages. * #114: VTIMEZONE is retained when generating new REQUEST objects. * #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip broker. This improves evolution support. * #115: Using REQUEST-STATUS from REPLY messages and now propegating that into SCHEDULE-STATUS. 3.3.0 (2014-08-07) ------------------ * We now use PSR-4 for the directory structure. This means that everything that was used to be in the `lib/Sabre/VObject` directory is now moved to `lib/`. If you use composer to load this library, you shouldn't have to do anything about that though. * VEVENT now get populated with a DTSTAMP and UID property by default. * BC Break: Removed the 'includes.php' file. Use composer instead. * #103: Added support for processing [iTip][iTip] messages. This allows a user to parse incoming iTip messages and apply the result on existing calendars, or automatically generate invites/replies/cancellations based on changes that a user made on objects. * #75, #58, #18: Fixes related to overriding the first event in recurrences. * Added: VCalendar::getBaseComponent to find the 'master' component in a calendar. * #51: Support for iterating RDATE properties. * Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are excluded events with wrong time 3.2.4 (2014-07-14) ------------------ * Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into consideration when converting between vCard 3 and 4. * Fixed: Issue #96. Some support for Yahoo's broken vcards. * Fixed: PHP 5.3 support was broken in the cli tool. 3.2.3 (2014-06-12) ------------------ * Validator now checks if DUE and DTSTART are of the same type in VTODO, and ensures that DUE is always after DTSTART. * Removed documentation from source repository, to http://sabre.io/vobject/ * Expanded the vobject cli tool validation output to make it easier to find issues. * Fixed: vobject repair. It was not working for iCalendar objects. 3.2.2 (2014-05-07) ------------------ * Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying slightly changed which caused the test to fail. 3.2.1 (2014-05-03) ------------------ * Minor tweak to make the unittests run with the latest hhvm on travis. * Updated timezone definitions. * Updated copyright links to point to http://sabre.io/ 3.2.0 (2014-04-02) ------------------ * Now hhvm compatible! * The validator can now detect a _lot_ more problems. Many rules for both iCalendar and vCard were added. * Added: bin/generate_vcards, a utility to generate random vcards for testing purposes. Patches are welcome to add more data. * Updated: Windows timezone mapping to latest version from unicode.org * Changed: The timezone maps are now loaded in from external files, in lib/Sabre/VObject/timezonedata. * Added: Fixing badly encoded URL's from google contacts vcards. * Fixed: Issue #68. Couldn't decode properties ending in a colon. * Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL clause. * Fixed: Issue #67. BYMONTH limit on DAILY recurrences. * Fixed: Issue #26. Return a more descriptive error when coming across broken BYDAY rules. * Fixed: Issue #28. Incorrect timezone detection for some timezones. * Fixed: Issue #70. Casting a parameter with a null value to string would fail. * Added: Support for rfc6715 and rfc6474. * Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property. * Added: UUIDUtil, for easily creating unique identifiers. * Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime. * Fixed: Issue #86. Don't go into an infinite loop when php errors are disabled and an invalid file is read. 3.1.4 (2014-03-30) ------------------ * Fixed: Issue #87: Several compatibility fixes related to timezone handling changes in PHP 5.5.10. 3.1.3 (2013-10-02) ------------------ * Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue #56. * Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than one newline. Thanks @Vedmak for the patch. * Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to be inserted. * Added: VCardConverter removed properties that are no longer supported in vCard 4.0. * Added: vCards with a minimum number of values (such as N), but don't have that many, are now automatically padded with empty components. * Added: The vCard validator now also checks for a minimum number of components, and has the ability to repair these. * Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard 3.0 or 4.0. * Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level VCalendar and VCard components. * Fixed: Issue 62: Parsing iCalendar parameters with no value. * Added: --forgiving option to vobject utility. * Fixed: Compound properties such as ADR were not correctly split up in vCard 2.1 quoted printable-encoded properties. * Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks @DominikTo for the patch. 3.1.2 (2013-08-13) ------------------ * Fixed: Setting correct property group on VCard conversion 3.1.1 (2013-08-02) ------------------ * Fixed: Issue #53. A regression in RecurrenceIterator. 3.1.0 (2013-07-27) ------------------ * Added: bad-ass new cli debugging utility (in bin/vobject). * Added: jCal and jCard parser. * Fixed: URI properties should not escape ; and ,. * Fixed: VCard 4 documents now correctly use URI as a default value-type for PHOTO and others. BINARY no longer exists in vCard 4. * Added: Utility to convert between 2.1, 3.0 and 4.0 vCards. * Added: You can now add() multiple parameters to a property in one call. * Added: Parameter::has() for easily checking if a parameter value exists. * Added: VCard::preferred() to find a preferred email, phone number, etc for a contact. * Changed: All $duration properties are now public. * Added: A few validators for iCalendar documents. * Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception events are out of order in the iCalendar file. * Fixed: Issue #48. Overridden events in the recurrence iterator that were past the UNTIL date were ignored. * Added: getDuration for DURATION values such as TRIGGER. Thanks to @SimonSimCity. * Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's more than 1. Thanks to @Vedmak. 3.0.0 (2013-06-21) ------------------ * Fixed: includes.php file was still broken. Our tool to generate it had some bugs. 3.0.0-beta4 (2013-06-21) ------------------------ * Fixed: includes.php was no longer up to date. 3.0.0-beta3 (2013-06-17) ------------------------ * Added: OPTION_FORGIVING now also allows slashes in property names. * Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999 * Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with a missing base event. * Fixed: jCard encoding of TIME properties. * Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values. 3.0.0-beta2 (2013-06-10) ------------------------ * Fixed: Corrected includes.php file. * Fixed: vCard date-time parser supported extended-format dates as well. * Changed: Properties have been moved to an ICalendar or VCard directory. * Fixed: Couldn't parse vCard 3 extended format dates and times. * Fixed: Couldn't export jCard DATE values correctly. * Fixed: Recursive loop in ICalendar\DateTime property. 3.0.0-beta1 (2013-06-07) ------------------------ * Added: jsonSerialize() for creating jCal and jCard documents. * Added: helper method to parse vCard dates and times. * Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP, DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties. * Removed: CommaSeparatedText property. Now included into Text. * Fixed: Multiple parameters with the same name are now correctly encoded. * Fixed: Parameter values containing a comma are now enclosed in double-quotes. * Fixed: Iterating parameter values should now fully work as expected. * Fixed: Support for vCard 2.1 nameless parameters. * Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified class names, so they are actually overridable. * Fixed: Updating DATE-TIME to DATE values now behaves like expected. 3.0.0-alpha4 (2013-05-31) ------------------------- * Added: It's now possible to send parser options to the splitter classes. * Added: A few tweaks to improve component and property creation. 3.0.0-alpha3 (2013-05-13) ------------------------- * Changed: propertyMap, valueMap and componentMap are now static properties. * Changed: Component::remove() will throw an exception when trying to a node that's not a child of said component. * Added: Splitter objects are now faster, line numbers are accurately reported and use less memory. * Added: MimeDir parser can now continue parsing with the same stream buffer. * Fixed: vobjectvalidate.php is operational again. * Fixed: \r is properly stripped in text values. * Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for vCards 2.1. * Fixed: Parser assumes vCard 2.1, if no version was supplied. 3.0.0-alpha2 (2013-05-22) ------------------------- * Fixed: vCard URL properties were referencing a non-existant class. 3.0.0-alpha1 (2013-05-21) ------------------------- * Fixed: Now correctly dealing with escaping of properties. This solves the problem with double-backslashes where they don't belong. * Added: Easy support for properties with more than one value, using setParts and getParts. * Added: Support for broken 2.1 vCards produced by microsoft. * Added: Automatically decoding quoted-printable values. * Added: Automatically decoding base64 values. * Added: Decoding RFC6868 parameter values (uses ^ as an escape character). * Added: Fancy new MimeDir parser that can also parse streams. * Added: Automatically mapping many, many properties to a property-class with specialized API's. * Added: remove() method for easily removing properties and sub-components components. * Changed: Components, Properties and Parameters can no longer be created with Component::create, Property::create and Parameter::create. They must instead be created through the root component. (A VCalendar or VCard object). * Changed: API for DateTime properties has slightly changed. * Changed: the ->value property is now protected everywhere. Use getParts() and getValue() instead. * BC Break: No support for mac newlines (\r). Never came across these anyway. * Added: add() method to the Property class. * Added: It's now possible to easy set multi-value properties as arrays. * Added: When setting date-time properties you can just pass PHP's DateTime object. * Added: New components automatically get a bunch of default properties, such as VERSION and CALSCALE. * Added: You can add new sub-components much quicker with the magic setters, and add() method. 2.1.7 (2015-01-21) ------------------ * Fixed: Issue #94, a workaround for bad escaping of ; and , in compound properties. It's not a full solution, but it's an improvement for those stuck in the 2.1 versions. 2.1.6 (2014-12-10) ------------------ * Fixed: Minor change to make sure that unittests succeed on every PHP version. 2.1.5 (2014-06-03) ------------------ * Fixed: #94: Better parameter escaping. * Changed: Documentation cleanups. 2.1.4 (2014-03-30) ------------------ * Fixed: Issue #87: Several compatibility fixes related to timezone handling changes in PHP 5.5.10. 2.1.3 (2013-10-02) ------------------ * Fixed: Issue #55. \r must be stripped from property values. * Fixed: Issue #65. Putting quotes around parameter values that contain a colon. 2.1.2 (2013-08-02) ------------------ * Fixed: Issue #53. A regression in RecurrenceIterator. 2.1.1 (2013-07-27) ------------------ * Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception events are out of order in the iCalendar file. * Fixed: Issue #48. Overridden events in the recurrence iterator that were past the UNTIL date were ignored. 2.1.0 (2013-06-17) ------------------ * This version is fully backwards compatible with 2.0.\*. However, it contains a few new API's that mimic the VObject 3 API. This allows it to be used a 'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and 1.8 can run with both the 2 and 3 versions of this library. * Added: Property\DateTime::hasTime(). * Added: Property\MultiDateTime::hasTime(). * Added: Property::getValue(). * Added: Document class. * Added: Document::createComponent and Document::createProperty. * Added: Parameter::getValue(). 2.0.7 (2013-03-05) ------------------ * Fixed: Microsoft re-uses their magic numbers for different timezones, specifically id 2 for both Sarajevo and Lisbon). A workaround was added to deal with this. 2.0.6 (2013-02-17) ------------------ * Fixed: The reader now properly parses parameters without a value. 2.0.5 (2012-11-05) ------------------ * Fixed: The FreeBusyGenerator is now properly using the factory methods for creation of components and properties. 2.0.4 (2012-11-02) ------------------ * Added: Known Lotus Notes / Domino timezone id's. 2.0.3 (2012-10-29) ------------------ * Added: Support for 'GMT+????' format in TZID's. * Added: Support for formats like SystemV/EST5EDT in TZID's. * Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART. * Added: Support for BYHOUR in FREQ=DAILY (@hollodk). * Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY. 2.0.2 (2012-10-06) ------------------ * Added: includes.php file, to load the entire library in one go. * Fixed: A problem with determining alarm triggers for TODO's. 2.0.1 (2012-09-22) ------------------ * Removed: Element class. It wasn't used. * Added: Basic validation and repair methods for broken input data. * Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was specified. * Added: A cli script that can validate and automatically repair vcards and iCalendar objects. * Added: A new 'Compound' property, that can automatically split up parts for properties such as N, ADR, ORG and CATEGORIES. * Added: Splitter classes, that can split up large objects (such as exports) into individual objects (thanks @DominikTO and @armin-hackmann). * Added: VFREEBUSY component, which allows easily checking wether timeslots are available. * Added: The Reader class now has a 'FORGIVING' option, which allows it to parse properties with incorrect characters in the name (at this time, it just allows underscores). * Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard any invalid lines. * Fixed: A bug in Windows timezone-id mappings for times created in Greenlands timezone (sorry Greenlanders! I do care!). * Fixed: DTEND was not generated correctly for VFREEBUSY reports. * Fixed: Parser is at least 25% faster with real-world data. 2.0.0 (2012-08-08) ------------------ * VObject is now a separate project from SabreDAV. See the SabreDAV changelog for version information before 2.0. * New: VObject library now uses PHP 5.3 namespaces. * New: It's possible to specify lists of parameters when constructing properties. * New: made it easier to construct the FreeBusyGenerator. [iTip]: http://tools.ietf.org/html/rfc5546 sabre-vobject-3.5.0/LICENSE000066400000000000000000000030411264477030300152560ustar00rootroot00000000000000Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Sabre nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sabre-vobject-3.5.0/README.md000066400000000000000000000035651264477030300155430ustar00rootroot00000000000000sabre/vobject ============= The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. The goal of the VObject library is to create a very complete library, with an easy to use API. Build status ------------ | branch | status | | ------ | ------ | | master | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=master)](https://travis-ci.org/fruux/sabre-vobject) | | 3.4 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.4)](https://travis-ci.org/fruux/sabre-vobject) | | 3.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.1)](https://travis-ci.org/fruux/sabre-vobject) | | 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-vobject) | | 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-vobject) | Installation ------------ VObject requires PHP 5.3, and should be installed using composer. The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website). After that, just declare the vobject dependency as follows: "require" : { "sabre/vobject" : "~3.4" } Then, run `composer.phar update` and you should be good. Usage ----- * [3.x documentation](http://sabre.io/vobject/usage/) * [2.x documentation](http://sabre.io/vobject/usage_2/) * [Migrating from 2.x to 3.x](http://sabre.io/vobject/upgrade/) Support ------- Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. Made at fruux ------------- This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. sabre-vobject-3.5.0/bin/000077500000000000000000000000001264477030300150235ustar00rootroot00000000000000sabre-vobject-3.5.0/bin/bench.php000077500000000000000000000003451264477030300166200ustar00rootroot00000000000000#!/usr/bin/env php xpath('//mapZone') as $mapZone) { $from = (string)$mapZone['other']; $to = (string)$mapZone['type']; list($to) = explode(' ', $to, 2); if (!isset($map[$from])) { $map[$from] = $to; } } ksort($map); echo "Writing to: $outputFile\n"; $f = fopen($outputFile,'w'); fwrite($f, " testdata.vcf HI; fwrite(STDERR, $help); exit(2); } $count = (int)$argv[1]; if ($count < 1) { fwrite(STDERR, "Count must be at least 1\n"); exit(2); } fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n"); fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n"); /** * The following list is just some random data we compiled from various * sources online. * * Very little thought went into compiling this list, and certainly nothing * political or ethical. * * We would _love_ more additions to this to add more variation to this list. * * Send us PR's and don't be shy adding your own first and last name for fun. */ $sets = array( "nl" => array( "country" => "Netherlands", "boys" => array( "Anno", "Bram", "Daan", "Evert", "Finn", "Jayden", "Jens", "Jesse", "Levi", "Lucas", "Luuk", "Milan", "René", "Sem", "Sibrand", "Willem", ), "girls" => array( "Celia", "Emma", "Fenna", "Geke", "Inge", "Julia", "Lisa", "Lotte", "Mila", "Sara", "Sophie", "Tess", "Zoë", ), "last" => array( "Bakker", "Bos", "De Boer", "De Groot", "De Jong", "De Vries", "Jansen", "Janssen", "Meyer", "Mulder", "Peters", "Smit", "Van Dijk", "Van den Berg", "Visser", "Vos", ), ), "us" => array( "country" => "United States", "boys" => array( "Aiden", "Alexander", "Charles", "David", "Ethan", "Jacob", "James", "Jayden", "John", "Joseph", "Liam", "Mason", "Michael", "Noah", "Richard", "Robert", "Thomas", "William", ), "girls" => array( "Ava", "Barbara", "Chloe", "Dorothy", "Elizabeth", "Emily", "Emma", "Isabella", "Jennifer", "Lily", "Linda", "Margaret", "Maria", "Mary", "Mia", "Olivia", "Patricia", "Roxy", "Sophia", "Susan", "Zoe", ), "last" => array( "Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", "Miller", "Wilson", "Moore", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", ), ), ); $current = 0; $r = function($arr) { return $arr[mt_rand(0,count($arr)-1)]; }; $bdayStart = strtotime('-85 years'); $bdayEnd = strtotime('-20 years'); while($current < $count) { $current++; fwrite(STDERR, "\033[100D$current/$count"); $country = array_rand($sets); $gender = mt_rand(0,1)?'girls':'boys'; $vcard = new Component\VCard(array( 'VERSION' => '4.0', 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']), 'UID' => UUIDUtil::getUUID(), )); $bdayRatio = mt_rand(0,9); if($bdayRatio < 2) { // 20% has a birthday property with a full date $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); $vcard->add('BDAY', $dt->format('Ymd')); } elseif ($bdayRatio < 3) { // 10% we only know the month and date of $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); $vcard->add('BDAY', '--' . $dt->format('md')); } if ($result = $vcard->validate()) { ob_start(); echo "\nWe produced an invalid vcard somehow!\n"; foreach($result as $message) { echo " " . $message['message'] . "\n"; } fwrite(STDERR, ob_get_clean()); } echo $vcard->serialize(); } fwrite(STDERR,"\nDone.\n"); sabre-vobject-3.5.0/bin/generateicalendardata.php000077500000000000000000000042121264477030300220250ustar00rootroot00000000000000#!/usr/bin/env php version = '2.0'; $calendar->calscale = 'GREGORIAN'; $ii=0; while($ii < $events) { $ii++; $event = VObject\Component::create('VEVENT'); $event->DTSTART = 'bla'; $event->SUMMARY = 'Event #' . $ii; $event->UID = md5(microtime(true)); $doctorRandom = mt_rand(1,1000); switch($doctorRandom) { // All-day event case 1 : $event->DTEND = 'bla'; $dtStart = clone $currentDate; $dtEnd = clone $currentDate; $dtEnd->modify('+' . mt_rand(1,3) . ' days'); $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::DATE); $event->DTEND->setDateTime($dtEnd, VObject\Property\DateTime::DATE); break; case 2 : $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1,10); // No break intentional default : $dtStart = clone $currentDate; $dtStart->setTime(mt_rand(1,23), mt_rand(0,59), mt_rand(0,59)); $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::UTC); $event->DURATION = 'PT'.mt_rand(1,3).'H'; break; } $calendar->add($event); $currentDate->modify('+ ' . mt_rand(0,3) . ' days'); } fwrite(STDERR, "Validating\n"); $result = $calendar->validate(); if ($result) { fwrite(STDERR, "Errors!\n"); fwrite(STDERR, print_r($result,true)); die(-1); } fwrite(STDERR, "Serializing this beast\n"); echo $calendar->serialize(); fwrite(STDERR, "done.\n"); sabre-vobject-3.5.0/bin/rrulebench.php000066400000000000000000000013451264477030300176700ustar00rootroot00000000000000parse->start(); echo "Parsing.\n"; $vobj = Sabre\VObject\Reader::read(fopen($inputFile,'r')); $bench->parse->stop(); echo "Expanding.\n"; $bench->expand->start(); $vobj->expand(new DateTime($startDate), new DateTime($endDate)); $bench->expand->stop(); echo $bench,"\n"; sabre-vobject-3.5.0/bin/vobject000077500000000000000000000012131264477030300164020ustar00rootroot00000000000000#!/usr/bin/env php main($argv)); sabre-vobject-3.5.0/composer.json000066400000000000000000000025161264477030300170010ustar00rootroot00000000000000{ "name": "sabre/vobject", "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", "keywords" : [ "VObject", "iCalendar", "vCard", "jCard", "jCal" ], "homepage" : "http://sabre.io/vobject/", "license" : "BSD-3-Clause", "require" : { "php" : ">=5.3.1", "ext-mbstring" : "*" }, "require-dev" : { "phpunit/phpunit" : "*", "squizlabs/php_codesniffer": "*" }, "authors" : [ { "name" : "Evert Pot", "email" : "me@evertpot.com", "homepage" : "http://evertpot.com/", "role" : "Developer" }, { "name" : "Dominik Tobschall", "email" : "dominik@fruux.com", "homepage" : "http://tobschall.de/", "role" : "Developer" } ], "support" : { "forum" : "https://groups.google.com/group/sabredav-discuss", "source" : "https://github.com/fruux/sabre-vobject" }, "autoload" : { "psr-4" : { "Sabre\\VObject\\" : "lib/" } }, "bin" : [ "bin/vobject", "bin/generate_vcards" ], "extra" : { "branch-alias" : { "dev-master" : "3.2.x-dev" } }, "config" : { "bin-dir" : "bin" } } sabre-vobject-3.5.0/lib/000077500000000000000000000000001264477030300150215ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/Cli.php000066400000000000000000000513671264477030300162550ustar00rootroot00000000000000stderr) { $this->stderr = fopen('php://stderr', 'w'); } if (!$this->stdout) { $this->stdout = fopen('php://stdout', 'w'); } if (!$this->stdin) { $this->stdin = fopen('php://stdin', 'r'); } // @codeCoverageIgnoreEnd try { list($options, $positional) = $this->parseArguments($argv); if (isset($options['q'])) { $this->quiet = true; } $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION)); foreach($options as $name=>$value) { switch($name) { case 'q' : // Already handled earlier. break; case 'h' : case 'help' : $this->showHelp(); return 0; break; case 'format' : switch($value) { // jcard/jcal documents case 'jcard' : case 'jcal' : // specific document versions case 'vcard21' : case 'vcard30' : case 'vcard40' : case 'icalendar20' : // specific formats case 'json' : case 'mimedir' : // icalendar/vcad case 'icalendar' : case 'vcard' : $this->format = $value; break; default : throw new InvalidArgumentException('Unknown format: ' . $value); } break; case 'pretty' : if (version_compare(PHP_VERSION, '5.4.0') >= 0) { $this->pretty = true; } break; case 'forgiving' : $this->forgiving = true; break; case 'inputformat' : switch($value) { // json formats case 'jcard' : case 'jcal' : case 'json' : $this->inputFormat = 'json'; break; // mimedir formats case 'mimedir' : case 'icalendar' : case 'vcard' : case 'vcard21' : case 'vcard30' : case 'vcard40' : case 'icalendar20' : $this->inputFormat = 'mimedir'; break; default : throw new InvalidArgumentException('Unknown format: ' . $value); } break; default : throw new InvalidArgumentException('Unknown option: ' . $name); } } if (count($positional) === 0) { $this->showHelp(); return 1; } if (count($positional) === 1) { throw new InvalidArgumentException('Inputfile is a required argument'); } if (count($positional) > 3) { throw new InvalidArgumentException('Too many arguments'); } if (!in_array($positional[0], array('validate','repair','convert','color'))) { throw new InvalidArgumentException('Uknown command: ' . $positional[0]); } } catch (InvalidArgumentException $e) { $this->showHelp(); $this->log('Error: ' . $e->getMessage(), 'red'); return 1; } $command = $positional[0]; $this->inputPath = $positional[1]; $this->outputPath = isset($positional[2])?$positional[2]:'-'; if ($this->outputPath !== '-') { $this->stdout = fopen($this->outputPath, 'w'); } if (!$this->inputFormat) { if (substr($this->inputPath, -5)==='.json') { $this->inputFormat = 'json'; } else { $this->inputFormat = 'mimedir'; } } if (!$this->format) { if (substr($this->outputPath,-5)==='.json') { $this->format = 'json'; } else { $this->format = 'mimedir'; } } $realCode = 0; try { while($input = $this->readInput()) { $returnCode = $this->$command($input); if ($returnCode!==0) $realCode = $returnCode; } } catch (EofException $e) { // end of file } catch (\Exception $e) { $this->log('Error: ' . $e->getMessage(),'red'); return 2; } return $realCode; } /** * Shows the help message. * * @return void */ protected function showHelp() { $this->log('Usage:', 'yellow'); $this->log(" vobject [options] command [arguments]"); $this->log(''); $this->log('Options:', 'yellow'); $this->log($this->colorize('green', ' -q ') . "Don't output anything."); $this->log($this->colorize('green', ' -help -h ') . "Display this help message."); $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,"); $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict."); $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir."); $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it"); $this->log(" must be specified here."); // Only PHP 5.4 and up if (version_compare(PHP_VERSION, '5.4.0') >= 0) { $this->log($this->colorize('green', ' --pretty ') . "json pretty-print."); } $this->log(''); $this->log('Commands:', 'yellow'); $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.'); $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.'); $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.'); $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.'); $this->log( <<log('Examples:', 'yellow'); $this->log(' vobject convert contact.vcf contact.json'); $this->log(' vobject convert --format=vcard40 old.vcf new.vcf'); $this->log(' vobject convert --inputformat=json --format=mimedir - -'); $this->log(' vobject color calendar.ics'); $this->log(''); $this->log('https://github.com/fruux/sabre-vobject','purple'); } /** * Validates a VObject file * * @param Component $vObj * @return int */ protected function validate($vObj) { $returnCode = 0; switch($vObj->name) { case 'VCALENDAR' : $this->log("iCalendar: " . (string)$vObj->VERSION); break; case 'VCARD' : $this->log("vCard: " . (string)$vObj->VERSION); break; } $warnings = $vObj->validate(); if (!count($warnings)) { $this->log(" No warnings!"); } else { $levels = array( 1 => 'REPAIRED', 2 => 'WARNING', 3 => 'ERROR', ); $returnCode = 2; foreach($warnings as $warn) { $extra = ''; if ($warn['node'] instanceof Property) { $extra = ' (property: "' . $warn['node']->name . '")'; } $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); } } return $returnCode; } /** * Repairs a VObject file * * @param Component $vObj * @return int */ protected function repair($vObj) { $returnCode = 0; switch($vObj->name) { case 'VCALENDAR' : $this->log("iCalendar: " . (string)$vObj->VERSION); break; case 'VCARD' : $this->log("vCard: " . (string)$vObj->VERSION); break; } $warnings = $vObj->validate(Node::REPAIR); if (!count($warnings)) { $this->log(" No warnings!"); } else { $levels = array( 1 => 'REPAIRED', 2 => 'WARNING', 3 => 'ERROR', ); $returnCode = 2; foreach($warnings as $warn) { $extra = ''; if ($warn['node'] instanceof Property) { $extra = ' (property: "' . $warn['node']->name . '")'; } $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); } } fwrite($this->stdout, $vObj->serialize()); return $returnCode; } /** * Converts a vObject file to a new format. * * @param Component $vObj * @return int */ protected function convert($vObj) { $json = false; $convertVersion = null; $forceInput = null; switch($this->format) { case 'json' : $json = true; if ($vObj->name === 'VCARD') { $convertVersion = Document::VCARD40; } break; case 'jcard' : $json = true; $forceInput = 'VCARD'; $convertVersion = Document::VCARD40; break; case 'jcal' : $json = true; $forceInput = 'VCALENDAR'; break; case 'mimedir' : case 'icalendar' : case 'icalendar20' : case 'vcard' : break; case 'vcard21' : $convertVersion = Document::VCARD21; break; case 'vcard30' : $convertVersion = Document::VCARD30; break; case 'vcard40' : $convertVersion = Document::VCARD40; break; } if ($forceInput && $vObj->name !== $forceInput) { throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format); } if ($convertVersion) { $vObj = $vObj->convert($convertVersion); } if ($json) { $jsonOptions = 0; if ($this->pretty) { $jsonOptions = JSON_PRETTY_PRINT; } fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions)); } else { fwrite($this->stdout, $vObj->serialize()); } return 0; } /** * Colorizes a file * * @param Component $vObj * @return int */ protected function color($vObj) { fwrite($this->stdout, $this->serializeComponent($vObj)); } /** * Returns an ansi color string for a color name. * * @param string $color * @return string */ protected function colorize($color, $str, $resetTo = 'default') { $colors = array( 'cyan' => '1;36', 'red' => '1;31', 'yellow' => '1;33', 'blue' => '0;34', 'green' => '0;32', 'default' => '0', 'purple' => '0;35', ); return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m"; } /** * Writes out a string in specific color. * * @param string $color * @param string $str * @return void */ protected function cWrite($color, $str) { fwrite($this->stdout, $this->colorize($color, $str)); } protected function serializeComponent(Component $vObj) { $this->cWrite('cyan', 'BEGIN'); $this->cWrite('red', ':'); $this->cWrite('yellow', $vObj->name . "\n"); /** * Gives a component a 'score' for sorting purposes. * * This is solely used by the childrenSort method. * * A higher score means the item will be lower in the list. * To avoid score collisions, each "score category" has a reasonable * space to accomodate elements. The $key is added to the $score to * preserve the original relative order of elements. * * @param int $key * @param array $array * @return int */ $sortScore = function($key, $array) { if ($array[$key] instanceof Component) { // We want to encode VTIMEZONE first, this is a personal // preference. if ($array[$key]->name === 'VTIMEZONE') { $score=300000000; return $score+$key; } else { $score=400000000; return $score+$key; } } else { // Properties get encoded first // VCARD version 4.0 wants the VERSION property to appear first if ($array[$key] instanceof Property) { if ($array[$key]->name === 'VERSION') { $score=100000000; return $score+$key; } else { // All other properties $score=200000000; return $score+$key; } } } }; $tmp = $vObj->children; uksort( $vObj->children, function($a, $b) use ($sortScore, $tmp) { $sA = $sortScore($a, $tmp); $sB = $sortScore($b, $tmp); return $sA - $sB; } ); foreach($vObj->children as $child) { if ($child instanceof Component) { $this->serializeComponent($child); } else { $this->serializeProperty($child); } } $this->cWrite('cyan', 'END'); $this->cWrite('red', ':'); $this->cWrite('yellow', $vObj->name . "\n"); } /** * Colorizes a property. * * @param Property $property * @return void */ protected function serializeProperty(Property $property) { if ($property->group) { $this->cWrite('default', $property->group); $this->cWrite('red', '.'); } $str = ''; $this->cWrite('yellow', $property->name); foreach($property->parameters as $param) { $this->cWrite('red',';'); $this->cWrite('blue', $param->serialize()); } $this->cWrite('red',':'); if ($property instanceof Property\Binary) { $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)'); } else { $parts = $property->getParts(); $first1 = true; // Looping through property values foreach($parts as $part) { if ($first1) { $first1 = false; } else { $this->cWrite('red', $property->delimiter); } $first2 = true; // Looping through property sub-values foreach((array)$part as $subPart) { if ($first2) { $first2 = false; } else { // The sub-value delimiter is always comma $this->cWrite('red', ','); } $subPart = strtr( $subPart, array( '\\' => $this->colorize('purple', '\\\\', 'green'), ';' => $this->colorize('purple', '\;', 'green'), ',' => $this->colorize('purple', '\,', 'green'), "\n" => $this->colorize('purple', "\\n\n\t", 'green'), "\r" => "", ) ); $this->cWrite('green', $subPart); } } } $this->cWrite("default", "\n"); } /** * Parses the list of arguments. * * @param array $argv * @return void */ protected function parseArguments(array $argv) { $positional = array(); $options = array(); for($ii=0; $ii < count($argv); $ii++) { // Skipping the first argument. if ($ii===0) continue; $v = $argv[$ii]; if (substr($v,0,2)==='--') { // This is a long-form option. $optionName = substr($v,2); $optionValue = true; if (strpos($optionName,'=')) { list($optionName, $optionValue) = explode('=', $optionName); } $options[$optionName] = $optionValue; } elseif (substr($v,0,1) === '-' && strlen($v)>1) { // This is a short-form option. foreach(str_split(substr($v,1)) as $option) { $options[$option] = true; } } else { $positional[] = $v; } } return array($options, $positional); } protected $parser; /** * Reads the input file * * @return Component */ protected function readInput() { if (!$this->parser) { if ($this->inputPath!=='-') { $this->stdin = fopen($this->inputPath,'r'); } if ($this->inputFormat === 'mimedir') { $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0)); } else { $this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0)); } } return $this->parser->parse(); } /** * Sends a message to STDERR. * * @param string $msg * @return void */ protected function log($msg, $color = 'default') { if (!$this->quiet) { if ($color!=='default') { $msg = $this->colorize($color, $msg); } fwrite($this->stderr, $msg . "\n"); } } } sabre-vobject-3.5.0/lib/Component.php000066400000000000000000000416641264477030300175070ustar00rootroot00000000000000value syntax, in which case * properties will automatically be created, or you can just pass a list of * Component and Property object. * * By default, a set of sensible values will be added to the component. For * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To * ensure that this does not happen, set $defaults to false. * * @param Document $root * @param string $name such as VCALENDAR, VEVENT. * @param array $children * @param bool $defaults * @return void */ function __construct(Document $root, $name, array $children = array(), $defaults = true) { $this->name = strtoupper($name); $this->root = $root; if ($defaults) { // This is a terribly convoluted way to do this, but this ensures // that the order of properties as they are specified in both // defaults and the childrens list, are inserted in the object in a // natural way. $list = $this->getDefaults(); $nodes = array(); foreach($children as $key=>$value) { if ($value instanceof Node) { if (isset($list[$value->name])) { unset($list[$value->name]); } $nodes[] = $value; } else { $list[$key] = $value; } } foreach($list as $key=>$value) { $this->add($key, $value); } foreach($nodes as $node) { $this->add($node); } } else { foreach($children as $k=>$child) { if ($child instanceof Node) { // Component or Property $this->add($child); } else { // Property key=>value $this->add($k, $child); } } } } /** * Adds a new property or component, and returns the new item. * * This method has 3 possible signatures: * * add(Component $comp) // Adds a new component * add(Property $prop) // Adds a new property * add($name, $value, array $parameters = array()) // Adds a new property * add($name, array $children = array()) // Adds a new component * by name. * * @return Node */ function add($a1, $a2 = null, $a3 = null) { if ($a1 instanceof Node) { if (!is_null($a2)) { throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node'); } $a1->parent = $this; $this->children[] = $a1; return $a1; } elseif(is_string($a1)) { $item = $this->root->create($a1, $a2, $a3); $item->parent = $this; $this->children[] = $item; return $item; } else { throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string'); } } /** * This method removes a component or property from this component. * * You can either specify the item by name (like DTSTART), in which case * all properties/components with that name will be removed, or you can * pass an instance of a property or component, in which case only that * exact item will be removed. * * The removed item will be returned. In case there were more than 1 items * removed, only the last one will be returned. * * @param mixed $item * @return void */ function remove($item) { if (is_string($item)) { $children = $this->select($item); foreach($children as $k=>$child) { unset($this->children[$k]); } return $child; } else { foreach($this->children as $k => $child) { if ($child===$item) { unset($this->children[$k]); return $child; } } throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); } } /** * Returns an iterable list of children * * @return array */ function children() { return $this->children; } /** * This method only returns a list of sub-components. Properties are * ignored. * * @return array */ function getComponents() { $result = array(); foreach($this->children as $child) { if ($child instanceof Component) { $result[] = $child; } } return $result; } /** * Returns an array with elements that match the specified name. * * This function is also aware of MIME-Directory groups (as they appear in * vcards). This means that if a property is grouped as "HOME.EMAIL", it * will also be returned when searching for just "EMAIL". If you want to * search for a property in a specific group, you can select on the entire * string ("HOME.EMAIL"). If you want to search on a specific property that * has not been assigned a group, specify ".EMAIL". * * Keys are retained from the 'children' array, which may be confusing in * certain cases. * * @param string $name * @return array */ function select($name) { $group = null; $name = strtoupper($name); if (strpos($name,'.')!==false) { list($group,$name) = explode('.', $name, 2); } $result = array(); foreach($this->children as $key=>$child) { if ( ( strtoupper($child->name) === $name && (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group)) ) || ( $name === '' && $child instanceof Property && strtoupper($child->group) === $group ) ) { $result[$key] = $child; } } reset($result); return $result; } /** * Turns the object back into a serialized blob. * * @return string */ function serialize() { $str = "BEGIN:" . $this->name . "\r\n"; /** * Gives a component a 'score' for sorting purposes. * * This is solely used by the childrenSort method. * * A higher score means the item will be lower in the list. * To avoid score collisions, each "score category" has a reasonable * space to accomodate elements. The $key is added to the $score to * preserve the original relative order of elements. * * @param int $key * @param array $array * @return int */ $sortScore = function($key, $array) { if ($array[$key] instanceof Component) { // We want to encode VTIMEZONE first, this is a personal // preference. if ($array[$key]->name === 'VTIMEZONE') { $score=300000000; return $score+$key; } else { $score=400000000; return $score+$key; } } else { // Properties get encoded first // VCARD version 4.0 wants the VERSION property to appear first if ($array[$key] instanceof Property) { if ($array[$key]->name === 'VERSION') { $score=100000000; return $score+$key; } else { // All other properties $score=200000000; return $score+$key; } } } }; $tmp = $this->children; uksort( $this->children, function($a, $b) use ($sortScore, $tmp) { $sA = $sortScore($a, $tmp); $sB = $sortScore($b, $tmp); return $sA - $sB; } ); foreach($this->children as $child) $str.=$child->serialize(); $str.= "END:" . $this->name . "\r\n"; return $str; } /** * This method returns an array, with the representation as it should be * encoded in json. This is used to create jCard or jCal documents. * * @return array */ function jsonSerialize() { $components = array(); $properties = array(); foreach($this->children as $child) { if ($child instanceof Component) { $components[] = $child->jsonSerialize(); } else { $properties[] = $child->jsonSerialize(); } } return array( strtolower($this->name), $properties, $components ); } /** * This method should return a list of default property values. * * @return array */ protected function getDefaults() { return array(); } /* Magic property accessors {{{ */ /** * Using 'get' you will either get a property or component. * * If there were no child-elements found with the specified name, * null is returned. * * To use this, this may look something like this: * * $event = $calendar->VEVENT; * * @param string $name * @return Property */ function __get($name) { $matches = $this->select($name); if (count($matches)===0) { return null; } else { $firstMatch = current($matches); /** @var $firstMatch Property */ $firstMatch->setIterator(new ElementList(array_values($matches))); return $firstMatch; } } /** * This method checks if a sub-element with the specified name exists. * * @param string $name * @return bool */ function __isset($name) { $matches = $this->select($name); return count($matches)>0; } /** * Using the setter method you can add properties or subcomponents * * You can either pass a Component, Property * object, or a string to automatically create a Property. * * If the item already exists, it will be removed. If you want to add * a new item with the same name, always use the add() method. * * @param string $name * @param mixed $value * @return void */ function __set($name, $value) { $matches = $this->select($name); $overWrite = count($matches)?key($matches):null; if ($value instanceof Component || $value instanceof Property) { $value->parent = $this; if (!is_null($overWrite)) { $this->children[$overWrite] = $value; } else { $this->children[] = $value; } } else { $property = $this->root->create($name,$value); $property->parent = $this; if (!is_null($overWrite)) { $this->children[$overWrite] = $property; } else { $this->children[] = $property; } } } /** * Removes all properties and components within this component with the * specified name. * * @param string $name * @return void */ function __unset($name) { $matches = $this->select($name); foreach($matches as $k=>$child) { unset($this->children[$k]); $child->parent = null; } } /* }}} */ /** * This method is automatically called when the object is cloned. * Specifically, this will ensure all child elements are also cloned. * * @return void */ function __clone() { foreach($this->children as $key=>$child) { $this->children[$key] = clone $child; $this->children[$key]->parent = $this; } } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * It is also possible to specify defaults and severity levels for * violating the rule. * * See the VEVENT implementation for getValidationRules for a more complex * example. * * @var array */ function getValidationRules() { return array(); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on). * 2 - A warning. * 3 - An error. * * @param int $options * @return array */ function validate($options = 0) { $rules = $this->getValidationRules(); $defaults = $this->getDefaults(); $propertyCounters = array(); $messages = array(); foreach($this->children as $child) { $name = strtoupper($child->name); if (!isset($propertyCounters[$name])) { $propertyCounters[$name] = 1; } else { $propertyCounters[$name]++; } $messages = array_merge($messages, $child->validate($options)); } foreach($rules as $propName => $rule) { switch($rule) { case '0' : if (isset($propertyCounters[$propName])) { $messages[] = array( 'level' => 3, 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component', 'node' => $this, ); } break; case '1' : if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName]!==1) { $repaired = false; if ($options & self::REPAIR && isset($defaults[$propName])) { $this->add($propName, $defaults[$propName]); } $messages[] = array( 'level' => $repaired?1:3, 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component', 'node' => $this, ); } break; case '+' : if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) { $messages[] = array( 'level' => 3, 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component', 'node' => $this, ); } break; case '*' : break; case '?' : if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) { $messages[] = array( 'level' => 3, 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component', 'node' => $this, ); } break; } } return $messages; } } sabre-vobject-3.5.0/lib/Component/000077500000000000000000000000001264477030300167635ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/Component/Available.php000066400000000000000000000055101264477030300213550ustar00rootroot00000000000000 1, 'DTSTART' => 1, 'DTSTAMP' => 1, 'DTEND' => '?', 'DURATION' => '?', 'CREATED' => '?', 'DESCRIPTION' => '?', 'LAST-MODIFIED' => '?', 'RECURRENCE-ID' => '?', 'RRULE' => '?', 'SUMMARY' => '?', 'CATEGORIES' => '*', 'COMMENT' => '*', 'CONTACT' => '*', 'EXDATE' => '*', 'RDATE' => '*', 'AVAILABLE' => '*', ); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on). * 2 - A warning. * 3 - An error. * * @param int $options * @return array */ function validate($options = 0) { $result = parent::validate($options); if (isset($this->DTEND) && isset($this->DURATION)) { $result[] = array( 'level' => 3, 'message' => 'DTEND and DURATION cannot both be present', 'node' => $this ); } if (isset($this->DURATION) && !isset($this->DTSTART)) { $result[] = array( 'level' => 3, 'message' => 'DURATION must be declared with a DTSTART.', 'node' => $this ); } return $result; } } sabre-vobject-3.5.0/lib/Component/VAlarm.php000066400000000000000000000106501264477030300206600ustar00rootroot00000000000000TRIGGER; if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') { $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START'; $parentComponent = $this->parent; if ($related === 'START') { if ($parentComponent->name === 'VTODO') { $propName = 'DUE'; } else { $propName = 'DTSTART'; } $effectiveTrigger = clone $parentComponent->$propName->getDateTime(); $effectiveTrigger->add($triggerDuration); } else { if ($parentComponent->name === 'VTODO') { $endProp = 'DUE'; } elseif ($parentComponent->name === 'VEVENT') { $endProp = 'DTEND'; } else { throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); } if (isset($parentComponent->$endProp)) { $effectiveTrigger = clone $parentComponent->$endProp->getDateTime(); $effectiveTrigger->add($triggerDuration); } elseif (isset($parentComponent->DURATION)) { $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION); $effectiveTrigger->add($duration); $effectiveTrigger->add($triggerDuration); } else { $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); $effectiveTrigger->add($triggerDuration); } } } else { $effectiveTrigger = $trigger->getDateTime(); } return $effectiveTrigger; } /** * Returns true or false depending on if the event falls in the specified * time-range. This is used for filtering purposes. * * The rules used to determine if an event falls within the specified * time-range is based on the CalDAV specification. * * @param \DateTime $start * @param \DateTime $end * @return bool */ public function isInTimeRange(\DateTime $start, \DateTime $end) { $effectiveTrigger = $this->getEffectiveTriggerTime(); if (isset($this->DURATION)) { $duration = VObject\DateTimeParser::parseDuration($this->DURATION); $repeat = (string)$this->repeat; if (!$repeat) { $repeat = 1; } $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat); foreach($period as $occurrence) { if ($start <= $occurrence && $end > $occurrence) { return true; } } return false; } else { return ($start <= $effectiveTrigger && $end > $effectiveTrigger); } } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ public function getValidationRules() { return array( 'ACTION' => 1, 'TRIGGER' => 1, 'DURATION' => '?', 'REPEAT' => '?', 'ATTACH' => '?', ); } } sabre-vobject-3.5.0/lib/Component/VAvailability.php000066400000000000000000000051511264477030300222360ustar00rootroot00000000000000 1, 'DTSTAMP' => 1, 'BUSYTYPE' => '?', 'CLASS' => '?', 'CREATED' => '?', 'DESCRIPTION' => '?', 'DTSTART' => '?', 'LAST-MODIFIED' => '?', 'ORGANIZER' => '?', 'PRIORITY' => '?', 'SEQUENCE' => '?', 'SUMMARY' => '?', 'URL' => '?', 'DTEND' => '?', 'DURATION' => '?', 'CATEGORIES' => '*', 'COMMENT' => '*', 'CONTACT' => '*', ); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on). * 2 - A warning. * 3 - An error. * * @param int $options * @return array */ function validate($options = 0) { $result = parent::validate($options); if (isset($this->DTEND) && isset($this->DURATION)) { $result[] = array( 'level' => 3, 'message' => 'DTEND and DURATION cannot both be present', 'node' => $this ); } return $result; } } sabre-vobject-3.5.0/lib/Component/VCalendar.php000066400000000000000000000431741264477030300213440ustar00rootroot00000000000000 'Sabre\\VObject\\Component\\VAlarm', 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability', 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available', 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', 'VTODO' => 'Sabre\\VObject\\Component\\VTodo', ); /** * List of value-types, and which classes they map to. * * @var array */ static $valueMap = array( 'BINARY' => 'Sabre\\VObject\\Property\\Binary', 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', 'TEXT' => 'Sabre\\VObject\\Property\\Text', 'TIME' => 'Sabre\\VObject\\Property\\Time', 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. 'URI' => 'Sabre\\VObject\\Property\\Uri', 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', ); /** * List of properties, and which classes they map to. * * @var array */ static $propertyMap = array( // Calendar properties 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', 'METHOD' => 'Sabre\\VObject\\Property\\FlatText', 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', // Component properties 'ATTACH' => 'Sabre\\VObject\\Property\\Uri', 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', 'GEO' => 'Sabre\\VObject\\Property\\FloatValue', 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue', 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue', 'RESOURCES' => 'Sabre\\VObject\\Property\\Text', 'STATUS' => 'Sabre\\VObject\\Property\\FlatText', 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', // Date and Time Component Properties 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', // Time Zone Component Properties 'TZID' => 'Sabre\\VObject\\Property\\FlatText', 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', 'TZURL' => 'Sabre\\VObject\\Property\\Uri', // Relationship Component Properties 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', 'URL' => 'Sabre\\VObject\\Property\\Uri', 'UID' => 'Sabre\\VObject\\Property\\FlatText', // Recurrence Component Properties 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 // Alarm Component Properties 'ACTION' => 'Sabre\\VObject\\Property\\FlatText', 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue', 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', // Change Management Component Properties 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue', // Request Status 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', // Additions from draft-daboo-valarm-extensions-04 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', // Additions from draft-daboo-calendar-availability-05 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text', ); /** * Returns the current document type. * * @return void */ function getDocumentType() { return self::ICALENDAR20; } /** * Returns a list of all 'base components'. For instance, if an Event has * a recurrence rule, and one instance is overridden, the overridden event * will have the same UID, but will be excluded from this list. * * VTIMEZONE components will always be excluded. * * @param string $componentName filter by component name * @return VObject\Component[] */ function getBaseComponents($componentName = null) { $components = array(); foreach($this->children as $component) { if (!$component instanceof VObject\Component) continue; if (isset($component->{'RECURRENCE-ID'})) continue; if ($componentName && $component->name !== strtoupper($componentName)) continue; if ($component->name === 'VTIMEZONE') continue; $components[] = $component; } return $components; } /** * Returns the first component that is not a VTIMEZONE, and does not have * an RECURRENCE-ID. * * If there is no such component, null will be returned. * * @param string $componentName filter by component name * @return VObject\Component|null */ function getBaseComponent($componentName = null) { foreach($this->children as $component) { if (!$component instanceof VObject\Component) continue; if (isset($component->{'RECURRENCE-ID'})) continue; if ($componentName && $component->name !== strtoupper($componentName)) continue; if ($component->name === 'VTIMEZONE') continue; return $component; } } /** * If this calendar object, has events with recurrence rules, this method * can be used to expand the event into multiple sub-events. * * Each event will be stripped from it's recurrence information, and only * the instances of the event in the specified timerange will be left * alone. * * In addition, this method will cause timezone information to be stripped, * and normalized to UTC. * * This method will alter the VCalendar. This cannot be reversed. * * This functionality is specifically used by the CalDAV standard. It is * possible for clients to request expand events, if they are rather simple * clients and do not have the possibility to calculate recurrences. * * @param DateTime $start * @param DateTime $end * @param DateTimeZone $timeZone reference timezone for floating dates and * times. * @return void */ function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) { $newEvents = array(); if (!$timeZone) { $timeZone = new DateTimeZone('UTC'); } // An array of events. Events are indexed by UID. Each item in this // array is a list of one or more events that match the UID. $recurringEvents = array(); foreach($this->select('VEVENT') as $key=>$vevent) { $uid = (string)$vevent->UID; if (!$uid) { throw new \LogicException('Event did not have a UID!'); } if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) { if (isset($recurringEvents[$uid])) { $recurringEvents[$uid][] = $vevent; } else { $recurringEvents[$uid] = array($vevent); } continue; } if (!isset($vevent->RRULE)) { if ($vevent->isInTimeRange($start, $end)) { $newEvents[] = $vevent; } continue; } } foreach($recurringEvents as $events) { try { $it = new EventIterator($events, $timeZone); } catch (NoInstancesException $e) { // This event is recurring, but it doesn't have a single // instance. We are skipping this event from the output // entirely. continue; } $it->fastForward($start); while($it->valid() && $it->getDTStart() < $end) { if ($it->getDTEnd() > $start) { $newEvents[] = $it->getEventObject(); } $it->next(); } } // Wiping out all old VEVENT objects unset($this->VEVENT); // Setting all properties to UTC time. foreach($newEvents as $newEvent) { foreach($newEvent->children as $child) { if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) { $dt = $child->getDateTimes($timeZone); // We only need to update the first timezone, because // setDateTimes will match all other timezones to the // first. $dt[0]->setTimeZone(new DateTimeZone('UTC')); $child->setDateTimes($dt); } } $this->add($newEvent); } // Removing all VTIMEZONE components unset($this->VTIMEZONE); } /** * This method should return a list of default property values. * * @return array */ protected function getDefaults() { return array( 'VERSION' => '2.0', 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', 'CALSCALE' => 'GREGORIAN', ); } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ function getValidationRules() { return array( 'PRODID' => 1, 'VERSION' => 1, 'CALSCALE' => '?', 'METHOD' => '?', ); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on). * 2 - A warning. * 3 - An error. * * @param int $options * @return array */ function validate($options = 0) { $warnings = parent::validate($options); if ($ver = $this->VERSION) { if ((string)$ver !== '2.0') { $warnings[] = array( 'level' => 3, 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', 'node' => $this, ); } } $uidList = array(); $componentsFound = 0; $componentTypes = array(); foreach($this->children as $child) { if($child instanceof Component) { $componentsFound++; if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) { continue; } $componentTypes[] = $child->name; $uid = (string)$child->UID; $isMaster = isset($child->{'RECURRENCE-ID'})?0:1; if (isset($uidList[$uid])) { $uidList[$uid]['count']++; if ($isMaster && $uidList[$uid]['hasMaster']) { $warnings[] = array( 'level' => 3, 'message' => 'More than one master object was found for the object with UID ' . $uid, 'node' => $this, ); } $uidList[$uid]['hasMaster']+=$isMaster; } else { $uidList[$uid] = array( 'count' => 1, 'hasMaster' => $isMaster, ); } } } if ($componentsFound===0) { $warnings[] = array( 'level' => 3, 'message' => 'An iCalendar object must have at least 1 component.', 'node' => $this, ); } if ($options & self::PROFILE_CALDAV) { if (count($uidList)>1) { $warnings[] = array( 'level' => 3, 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', 'node' => $this, ); } if (count(array_unique($componentTypes))===0) { $warnings[] = array( 'level' => 3, 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', 'node' => $this, ); } if (count(array_unique($componentTypes))>1) { $warnings[] = array( 'level' => 3, 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', 'node' => $this, ); } if (isset($this->METHOD)) { $warnings[] = array( 'level' => 3, 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', 'node' => $this, ); } } return $warnings; } /** * Returns all components with a specific UID value. * * @return array */ function getByUID($uid) { return array_filter($this->children, function($item) use ($uid) { if (!$item instanceof Component) { return false; } if (!$itemUid = $item->select('UID')) { return false; } $itemUid = current($itemUid)->getValue(); return $uid === $itemUid; }); } } sabre-vobject-3.5.0/lib/Component/VCard.php000066400000000000000000000354121264477030300205000ustar00rootroot00000000000000 'Sabre\\VObject\\Property\\Binary', 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date', 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime', 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', 'TEXT' => 'Sabre\\VObject\\Property\\Text', 'TIME' => 'Sabre\\VObject\\Property\\Time', 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. 'URI' => 'Sabre\\VObject\\Property\\Uri', 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', ); /** * List of properties, and which classes they map to. * * @var array */ static $propertyMap = array( // vCard 2.1 properties and up 'N' => 'Sabre\\VObject\\Property\\Text', 'FN' => 'Sabre\\VObject\\Property\\FlatText', 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values. 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', 'ADR' => 'Sabre\\VObject\\Property\\Text', 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 'TEL' => 'Sabre\\VObject\\Property\\FlatText', 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText', 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 'GEO' => 'Sabre\\VObject\\Property\\FlatText', 'TITLE' => 'Sabre\\VObject\\Property\\FlatText', 'ROLE' => 'Sabre\\VObject\\Property\\FlatText', 'LOGO' => 'Sabre\\VObject\\Property\\Binary', // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so // not supported at the moment 'ORG' => 'Sabre\\VObject\\Property\\Text', 'NOTE' => 'Sabre\\VObject\\Property\\FlatText', 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', 'SOUND' => 'Sabre\\VObject\\Property\\FlatText', 'URL' => 'Sabre\\VObject\\Property\\Uri', 'UID' => 'Sabre\\VObject\\Property\\FlatText', 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', 'KEY' => 'Sabre\\VObject\\Property\\FlatText', 'TZ' => 'Sabre\\VObject\\Property\\Text', // vCard 3.0 properties 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText', 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', 'NICKNAME' => 'Sabre\\VObject\\Property\\Text', 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 // rfc2739 properties 'FBURL' => 'Sabre\\VObject\\Property\\Uri', 'CAPURI' => 'Sabre\\VObject\\Property\\Uri', 'CALURI' => 'Sabre\\VObject\\Property\\Uri', // rfc4770 properties 'IMPP' => 'Sabre\\VObject\\Property\\Uri', // vCard 4.0 properties 'XML' => 'Sabre\\VObject\\Property\\FlatText', 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text', 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', 'GENDER' => 'Sabre\\VObject\\Property\\Text', 'KIND' => 'Sabre\\VObject\\Property\\FlatText', // rfc6474 properties 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText', 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText', 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // rfc6715 properties 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText', 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText', 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText', 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText', ); /** * Returns the current document type. * * @return void */ function getDocumentType() { if (!$this->version) { $version = (string)$this->VERSION; switch($version) { case '2.1' : $this->version = self::VCARD21; break; case '3.0' : $this->version = self::VCARD30; break; case '4.0' : $this->version = self::VCARD40; break; default : $this->version = self::UNKNOWN; break; } } return $this->version; } /** * Converts the document to a different vcard version. * * Use one of the VCARD constants for the target. This method will return * a copy of the vcard in the new version. * * At the moment the only supported conversion is from 3.0 to 4.0. * * If input and output version are identical, a clone is returned. * * @param int $target * @return VCard */ function convert($target) { $converter = new VObject\VCardConverter(); return $converter->convert($this, $target); } /** * VCards with version 2.1, 3.0 and 4.0 are found. * * If the VCARD doesn't know its version, 2.1 is assumed. */ const DEFAULT_VERSION = self::VCARD21; /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ function validate($options = 0) { $warnings = array(); $versionMap = array( self::VCARD21 => '2.1', self::VCARD30 => '3.0', self::VCARD40 => '4.0', ); $version = $this->select('VERSION'); if (count($version)===1) { $version = (string)$this->VERSION; if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') { $warnings[] = array( 'level' => 3, 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', 'node' => $this, ); if ($options & self::REPAIR) { $this->VERSION = $versionMap[self::DEFAULT_VERSION]; } } if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) { $warnings[] = array( 'level' => 3, 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.', 'node' => $this, ); } } $uid = $this->select('UID'); if (count($uid) === 0) { if ($options & self::PROFILE_CARDDAV) { // Required for CardDAV $warningLevel = 3; $message = 'vCards on CardDAV servers MUST have a UID property.'; } else { // Not required for regular vcards $warningLevel = 2; $message = 'Adding a UID to a vCard property is recommended.'; } if ($options & self::REPAIR) { $this->UID = VObject\UUIDUtil::getUUID(); $warningLevel = 1; } $warnings[] = array( 'level' => $warningLevel, 'message' => $message, 'node' => $this, ); } $fn = $this->select('FN'); if (count($fn)!==1) { $repaired = false; if (($options & self::REPAIR) && count($fn) === 0) { // We're going to try to see if we can use the contents of the // N property. if (isset($this->N)) { $value = explode(';', (string)$this->N); if (isset($value[1]) && $value[1]) { $this->FN = $value[1] . ' ' . $value[0]; } else { $this->FN = $value[0]; } $repaired = true; // Otherwise, the ORG property may work } elseif (isset($this->ORG)) { $this->FN = (string)$this->ORG; $repaired = true; } } $warnings[] = array( 'level' => $repaired?1:3, 'message' => 'The FN property must appear in the VCARD component exactly 1 time', 'node' => $this, ); } return array_merge( parent::validate($options), $warnings ); } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ function getValidationRules() { return array( 'ADR' => '*', 'ANNIVERSARY' => '?', 'BDAY' => '?', 'CALADRURI' => '*', 'CALURI' => '*', 'CATEGORIES' => '*', 'CLIENTPIDMAP' => '*', 'EMAIL' => '*', 'FBURL' => '*', 'IMPP' => '*', 'GENDER' => '?', 'GEO' => '*', 'KEY' => '*', 'KIND' => '?', 'LANG' => '*', 'LOGO' => '*', 'MEMBER' => '*', 'N' => '?', 'NICKNAME' => '*', 'NOTE' => '*', 'ORG' => '*', 'PHOTO' => '*', 'PRODID' => '?', 'RELATED' => '*', 'REV' => '?', 'ROLE' => '*', 'SOUND' => '*', 'SOURCE' => '*', 'TEL' => '*', 'TITLE' => '*', 'TZ' => '*', 'URL' => '*', 'VERSION' => '1', 'XML' => '*', // FN is commented out, because it's already handled by the // validate function, which may also try to repair it. // 'FN' => '+', 'UID' => '?', ); } /** * Returns a preferred field. * * VCards can indicate wether a field such as ADR, TEL or EMAIL is * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x * being a number between 1 and 100). * * If neither of those parameters are specified, the first is returned, if * a field with that name does not exist, null is returned. * * @param string $fieldName * @return VObject\Property|null */ function preferred($propertyName) { $preferred = null; $lastPref = 101; foreach($this->select($propertyName) as $field) { $pref = 101; if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) { $pref = 1; } elseif (isset($field['PREF'])) { $pref = $field['PREF']->getValue(); } if ($pref < $lastPref || is_null($preferred)) { $preferred = $field; $lastPref = $pref; } } return $preferred; } /** * This method should return a list of default property values. * * @return array */ protected function getDefaults() { return array( 'VERSION' => '3.0', 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', ); } /** * This method returns an array, with the representation as it should be * encoded in json. This is used to create jCard or jCal documents. * * @return array */ function jsonSerialize() { // A vcard does not have sub-components, so we're overriding this // method to remove that array element. $properties = array(); foreach($this->children as $child) { $properties[] = $child->jsonSerialize(); } return array( strtolower($this->name), $properties, ); } /** * Returns the default class for a property name. * * @param string $propertyName * @return string */ function getClassNameForPropertyName($propertyName) { $className = parent::getClassNameForPropertyName($propertyName); // In vCard 4, BINARY no longer exists, and we need URI instead. if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) { return 'Sabre\\VObject\\Property\\Uri'; } return $className; } } sabre-vobject-3.5.0/lib/Component/VEvent.php000066400000000000000000000106701264477030300207070ustar00rootroot00000000000000RRULE) { try { $it = new EventIterator($this, null, $start->getTimezone()); } catch (NoInstancesException $e) { // If we've catched this exception, there are no instances // for the event that fall into the specified time-range. return false; } $it->fastForward($start); // We fast-forwarded to a spot where the end-time of the // recurrence instance exceeded the start of the requested // time-range. // // If the starttime of the recurrence did not exceed the // end of the time range as well, we have a match. return ($it->getDTStart() < $end && $it->getDTEnd() > $start); } $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); if (isset($this->DTEND)) { // The DTEND property is considered non inclusive. So for a 3 day // event in july, dtstart and dtend would have to be July 1st and // July 4th respectively. // // See: // http://tools.ietf.org/html/rfc5545#page-54 $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); } elseif (isset($this->DURATION)) { $effectiveEnd = clone $effectiveStart; $effectiveEnd->add(VObject\DateTimeParser::parseDuration($this->DURATION)); } elseif (!$this->DTSTART->hasTime()) { $effectiveEnd = clone $effectiveStart; $effectiveEnd->modify('+1 day'); } else { $effectiveEnd = clone $effectiveStart; } return ( ($start < $effectiveEnd) && ($end > $effectiveStart) ); } /** * This method should return a list of default property values. * * @return array */ protected function getDefaults() { return array( 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), 'DTSTAMP' => date('Ymd\\THis\\Z'), ); } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ public function getValidationRules() { $hasMethod = isset($this->parent->METHOD); return array( 'UID' => 1, 'DTSTAMP' => 1, 'DTSTART' => $hasMethod?'?':'1', 'CLASS' => '?', 'CREATED' => '?', 'DESCRIPTION' => '?', 'GEO' => '?', 'LAST-MODIFIED' => '?', 'LOCATION' => '?', 'ORGANIZER' => '?', 'PRIORITY' => '?', 'SEQUENCE' => '?', 'STATUS' => '?', 'SUMMARY' => '?', 'TRANSP' => '?', 'URL' => '?', 'RECURRENCE-ID' => '?', 'RRULE' => '?', 'DTEND' => '?', 'DURATION' => '?', 'ATTACH' => '*', 'ATTENDEE' => '*', 'CATEGORIES' => '*', 'COMMENT' => '*', 'CONTACT' => '*', 'EXDATE' => '*', 'REQUEST-STATUS' => '*', 'RELATED-TO' => '*', 'RESOURCES' => '*', 'RDATE' => '*', ); } } sabre-vobject-3.5.0/lib/Component/VFreeBusy.php000066400000000000000000000054511264477030300213530ustar00rootroot00000000000000select('FREEBUSY') as $freebusy) { // We are only interested in FBTYPE=BUSY (the default), // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') { continue; } // The freebusy component can hold more than 1 value, separated by // commas. $periods = explode(',', (string)$freebusy); foreach($periods as $period) { // Every period is formatted as [start]/[end]. The start is an // absolute UTC time, the end may be an absolute UTC time, or // duration (relative) value. list($busyStart, $busyEnd) = explode('/', $period); $busyStart = VObject\DateTimeParser::parse($busyStart); $busyEnd = VObject\DateTimeParser::parse($busyEnd); if ($busyEnd instanceof \DateInterval) { $tmp = clone $busyStart; $tmp->add($busyEnd); $busyEnd = $tmp; } if($start < $busyEnd && $end > $busyStart) { return false; } } } return true; } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ public function getValidationRules() { return array( 'UID' => 1, 'DTSTAMP' => 1, 'CONTACT' => '?', 'DTSTART' => '?', 'DTEND' => '?', 'ORGANIZER' => '?', 'URL' => '?', 'ATTENDEE' => '*', 'COMMENT' => '*', 'FREEBUSY' => '*', 'REQUEST-STATUS' => '*', ); } } sabre-vobject-3.5.0/lib/Component/VJournal.php000066400000000000000000000045361264477030300212440ustar00rootroot00000000000000DTSTART)?$this->DTSTART->getDateTime():null; if ($dtstart) { $effectiveEnd = clone $dtstart; if (!$this->DTSTART->hasTime()) { $effectiveEnd->modify('+1 day'); } return ($start <= $effectiveEnd && $end > $dtstart); } return false; } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ public function getValidationRules() { return array( 'UID' => 1, 'DTSTAMP' => 1, 'CLASS' => '?', 'CREATED' => '?', 'DTSTART' => '?', 'LAST-MODIFIED' => '?', 'ORGANIZER' => '?', 'RECURRENCE-ID' => '?', 'SEQUENCE' => '?', 'STATUS' => '?', 'SUMMARY' => '?', 'URL' => '?', 'RRULE' => '?', 'ATTACH' => '*', 'ATTENDEE' => '*', 'CATEGORIES' => '*', 'COMMENT' => '*', 'CONTACT' => '*', 'DESCRIPTION' => '*', 'EXDATE' => '*', 'RELATED-TO' => '*', 'RDATE' => '*', ); } } sabre-vobject-3.5.0/lib/Component/VTimeZone.php000066400000000000000000000032231264477030300213540ustar00rootroot00000000000000TZID, $this->root); } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ function getValidationRules() { return array( 'TZID' => 1, 'LAST-MODIFIED' => '?', 'TZURL' => '?', // At least 1 STANDARD or DAYLIGHT must appear, or more. But both // cannot appear in the same VTIMEZONE. // // The validator is not specific yet to pick this up, so these // rules are too loose. 'STANDARD' => '*', 'DAYLIGHT' => '*', ); } } sabre-vobject-3.5.0/lib/Component/VTodo.php000066400000000000000000000121371264477030300205330ustar00rootroot00000000000000DTSTART)?$this->DTSTART->getDateTime():null; $duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null; $due = isset($this->DUE)?$this->DUE->getDateTime():null; $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null; $created = isset($this->CREATED)?$this->CREATED->getDateTime():null; if ($dtstart) { if ($duration) { $effectiveEnd = clone $dtstart; $effectiveEnd->add($duration); return $start <= $effectiveEnd && $end > $dtstart; } elseif ($due) { return ($start < $due || $start <= $dtstart) && ($end > $dtstart || $end >= $due); } else { return $start <= $dtstart && $end > $dtstart; } } if ($due) { return ($start < $due && $end >= $due); } if ($completed && $created) { return ($start <= $created || $start <= $completed) && ($end >= $created || $end >= $completed); } if ($completed) { return ($start <= $completed && $end >= $completed); } if ($created) { return ($end > $created); } return true; } /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either * must or must not appear. * * Possible values per property: * * 0 - Must not appear. * * 1 - Must appear exactly once. * * + - Must appear at least once. * * * - Can appear any number of times. * * ? - May appear, but not more than once. * * @var array */ public function getValidationRules() { return array( 'UID' => 1, 'DTSTAMP' => 1, 'CLASS' => '?', 'COMPLETED' => '?', 'CREATED' => '?', 'DESCRIPTION' => '?', 'DTSTART' => '?', 'GEO' => '?', 'LAST-MODIFIED' => '?', 'LOCATION' => '?', 'ORGANIZER' => '?', 'PERCENT' => '?', 'PRIORITY' => '?', 'RECURRENCE-ID' => '?', 'SEQUENCE' => '?', 'STATUS' => '?', 'SUMMARY' => '?', 'URL' => '?', 'RRULE' => '?', 'DUE' => '?', 'DURATION' => '?', 'ATTACH' => '*', 'ATTENDEE' => '*', 'CATEGORIES' => '*', 'COMMENT' => '*', 'CONTACT' => '*', 'EXDATE' => '*', 'REQUEST-STATUS' => '*', 'RELATED-TO' => '*', 'RESOURCES' => '*', 'RDATE' => '*', ); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ public function validate($options = 0) { $result = parent::validate($options); if (isset($this->DUE) && isset($this->DTSTART)) { $due = $this->DUE; $dtStart = $this->DTSTART; if ($due->getValueType() !== $dtStart->getValueType()) { $result[] = array( 'level' => 3, 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', 'node' => $due, ); } elseif ($due->getDateTime() < $dtStart->getDateTime()) { $result[] = array( 'level' => 3, 'message' => 'DUE must occur after DTSTART', 'node' => $due, ); } } return $result; } } sabre-vobject-3.5.0/lib/DateTimeParser.php000066400000000000000000000266061264477030300204150ustar00rootroot00000000000000setTimeZone(new \DateTimeZone('UTC')); return $date; } /** * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object. * * @param string $date * @param DateTimeZone $tz * @return DateTime */ static public function parseDate($date, DateTimeZone $tz = null) { // Format is YYYYMMDD $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches); if (!$result) { throw new LogicException('The supplied iCalendar date value is incorrect: ' . $date); } if (is_null($tz)) { $tz = new DateTimeZone('UTC'); } $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz); return $date; } /** * Parses an iCalendar (RFC5545) formatted duration value. * * This method will either return a DateTimeInterval object, or a string * suitable for strtotime or DateTime::modify. * * @param string $duration * @param bool $asString * @return DateInterval|string */ static public function parseDuration($duration, $asString = false) { $result = preg_match('/^(?P\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$/', $duration, $matches); if (!$result) { throw new LogicException('The supplied iCalendar duration value is incorrect: ' . $duration); } if (!$asString) { $invert = false; if ($matches['plusminus']==='-') { $invert = true; } $parts = array( 'week', 'day', 'hour', 'minute', 'second', ); foreach($parts as $part) { $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0; } // We need to re-construct the $duration string, because weeks and // days are not supported by DateInterval in the same string. $duration = 'P'; $days = $matches['day']; if ($matches['week']) { $days+=$matches['week']*7; } if ($days) $duration.=$days . 'D'; if ($matches['minute'] || $matches['second'] || $matches['hour']) { $duration.='T'; if ($matches['hour']) $duration.=$matches['hour'].'H'; if ($matches['minute']) $duration.=$matches['minute'].'M'; if ($matches['second']) $duration.=$matches['second'].'S'; } if ($duration==='P') { $duration = 'PT0S'; } $iv = new DateInterval($duration); if ($invert) $iv->invert = true; return $iv; } $parts = array( 'week', 'day', 'hour', 'minute', 'second', ); $newDur = ''; foreach($parts as $part) { if (isset($matches[$part]) && $matches[$part]) { $newDur.=' '.$matches[$part] . ' ' . $part . 's'; } } $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur); if ($newDur === '+') { $newDur = '+0 seconds'; }; return $newDur; } /** * Parses either a Date or DateTime, or Duration value. * * @param string $date * @param DateTimeZone|string $referenceTz * @return DateTime|DateInterval */ static public function parse($date, $referenceTz = null) { if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) { return self::parseDuration($date); } elseif (strlen($date)===8) { return self::parseDate($date, $referenceTz); } else { return self::parseDateTime($date, $referenceTz); } } /** * This method parses a vCard date and or time value. * * This can be used for the DATE, DATE-TIME, TIMESTAMP and * DATE-AND-OR-TIME value. * * This method returns an array, not a DateTime value. * * The elements in the array are in the following order: * year, month, date, hour, minute, second, timezone * * Almost any part of the string may be omitted. It's for example legal to * just specify seconds, leave out the year, etc. * * Timezone is either returned as 'Z' or as '+08:00' * * For any non-specified values null is returned. * * List of date formats that are supported: * YYYY * YYYY-MM * YYYYMMDD * --MMDD * ---DD * * YYYY-MM-DD * --MM-DD * ---DD * * List of supported time formats: * * HH * HHMM * HHMMSS * -MMSS * --SS * * HH * HH:MM * HH:MM:SS * -MM:SS * --SS * * A full basic-format date-time string looks like : * 20130603T133901 * * A full extended-format date-time string looks like : * 2013-06-03T13:39:01 * * Times may be postfixed by a timezone offset. This can be either 'Z' for * UTC, or a string like -0500 or +1100. * * @param string $date * @return array */ static public function parseVCardDateTime($date) { $regex = '/^ (?: # date part (?: (?: (?P [0-9]{4}) (?: -)?| --) (?P [0-9]{2})? |---) (?P [0-9]{2})? )? (?:T # time part (?P [0-9]{2} | -) (?P [0-9]{2} | -)? (?P [0-9]{2})? (?: \.[0-9]{3})? # milliseconds (?P # timezone offset Z | (?: \+|-)(?: [0-9]{4}) )? )? $/x'; if (!preg_match($regex, $date, $matches)) { // Attempting to parse the extended format. $regex = '/^ (?: # date part (?: (?P [0-9]{4}) - | -- ) (?P [0-9]{2}) - (?P [0-9]{2}) )? (?:T # time part (?: (?P [0-9]{2}) : | -) (?: (?P [0-9]{2}) : | -)? (?P [0-9]{2})? (?: \.[0-9]{3})? # milliseconds (?P # timezone offset Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) )? )? $/x'; if (!preg_match($regex, $date, $matches)) { throw new InvalidArgumentException('Invalid vCard date-time string: ' . $date); } } $parts = array( 'year', 'month', 'date', 'hour', 'minute', 'second', 'timezone' ); $result = array(); foreach($parts as $part) { if (empty($matches[$part])) { $result[$part] = null; } elseif ($matches[$part] === '-' || $matches[$part] === '--') { $result[$part] = null; } else { $result[$part] = $matches[$part]; } } return $result; } /** * This method parses a vCard TIME value. * * This method returns an array, not a DateTime value. * * The elements in the array are in the following order: * hour, minute, second, timezone * * Almost any part of the string may be omitted. It's for example legal to * just specify seconds, leave out the hour etc. * * Timezone is either returned as 'Z' or as '+08:00' * * For any non-specified values null is returned. * * List of supported time formats: * * HH * HHMM * HHMMSS * -MMSS * --SS * * HH * HH:MM * HH:MM:SS * -MM:SS * --SS * * A full basic-format time string looks like : * 133901 * * A full extended-format time string looks like : * 13:39:01 * * Times may be postfixed by a timezone offset. This can be either 'Z' for * UTC, or a string like -0500 or +11:00. * * @param string $date * @return array */ static public function parseVCardTime($date) { $regex = '/^ (?P [0-9]{2} | -) (?P [0-9]{2} | -)? (?P [0-9]{2})? (?: \.[0-9]{3})? # milliseconds (?P # timezone offset Z | (?: \+|-)(?: [0-9]{4}) )? $/x'; if (!preg_match($regex, $date, $matches)) { // Attempting to parse the extended format. $regex = '/^ (?: (?P [0-9]{2}) : | -) (?: (?P [0-9]{2}) : | -)? (?P [0-9]{2})? (?: \.[0-9]{3})? # milliseconds (?P # timezone offset Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) )? $/x'; if (!preg_match($regex, $date, $matches)) { throw new InvalidArgumentException('Invalid vCard time string: ' . $date); } } $parts = array( 'hour', 'minute', 'second', 'timezone' ); $result = array(); foreach($parts as $part) { if (empty($matches[$part])) { $result[$part] = null; } elseif ($matches[$part] === '-') { $result[$part] = null; } else { $result[$part] = $matches[$part]; } } return $result; } } sabre-vobject-3.5.0/lib/Document.php000066400000000000000000000155321264477030300173160ustar00rootroot00000000000000value syntax, in which case * properties will automatically be created, or you can just pass a list of * Component and Property object. * * By default, a set of sensible values will be added to the component. For * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To * ensure that this does not happen, set $defaults to false. * * @param string $name * @param array $children * @param bool $defaults * @return Component */ public function createComponent($name, array $children = null, $defaults = true) { $name = strtoupper($name); $class = 'Sabre\\VObject\\Component'; if (isset(static::$componentMap[$name])) { $class=static::$componentMap[$name]; } if (is_null($children)) $children = array(); return new $class($this, $name, $children, $defaults); } /** * Factory method for creating new properties * * This method automatically searches for the correct property class, based * on its name. * * You can specify the parameters either in key=>value syntax, in which case * parameters will automatically be created, or you can just pass a list of * Parameter objects. * * @param string $name * @param mixed $value * @param array $parameters * @param string $valueType Force a specific valuetype, such as URI or TEXT * @return Property */ public function createProperty($name, $value = null, array $parameters = null, $valueType = null) { // If there's a . in the name, it means it's prefixed by a groupname. if (($i=strpos($name,'.'))!==false) { $group = substr($name, 0, $i); $name = strtoupper(substr($name, $i+1)); } else { $name = strtoupper($name); $group = null; } $class = null; if ($valueType) { // The valueType argument comes first to figure out the correct // class. $class = $this->getClassNameForPropertyValue($valueType); } if (is_null($class) && isset($parameters['VALUE'])) { // If a VALUE parameter is supplied, we should use that. $class = $this->getClassNameForPropertyValue($parameters['VALUE']); } if (is_null($class)) { $class = $this->getClassNameForPropertyName($name); } if (is_null($parameters)) $parameters = array(); return new $class($this, $name, $value, $parameters, $group); } /** * This method returns a full class-name for a value parameter. * * For instance, DTSTART may have VALUE=DATE. In that case we will look in * our valueMap table and return the appropriate class name. * * This method returns null if we don't have a specialized class. * * @param string $valueParam * @return void */ public function getClassNameForPropertyValue($valueParam) { $valueParam = strtoupper($valueParam); if (isset(static::$valueMap[$valueParam])) { return static::$valueMap[$valueParam]; } } /** * Returns the default class for a property name. * * @param string $propertyName * @return string */ public function getClassNameForPropertyName($propertyName) { if (isset(static::$propertyMap[$propertyName])) { return static::$propertyMap[$propertyName]; } else { return 'Sabre\\VObject\\Property\\Unknown'; } } } sabre-vobject-3.5.0/lib/ElementList.php000066400000000000000000000056221264477030300177640ustar00rootroot00000000000000vevent where there's multiple VEVENT objects. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class ElementList implements \Iterator, \Countable, \ArrayAccess { /** * Inner elements * * @var array */ protected $elements = array(); /** * Creates the element list. * * @param array $elements */ public function __construct(array $elements) { $this->elements = $elements; } /* {{{ Iterator interface */ /** * Current position * * @var int */ private $key = 0; /** * Returns current item in iteration * * @return Element */ public function current() { return $this->elements[$this->key]; } /** * To the next item in the iterator * * @return void */ public function next() { $this->key++; } /** * Returns the current iterator key * * @return int */ public function key() { return $this->key; } /** * Returns true if the current position in the iterator is a valid one * * @return bool */ public function valid() { return isset($this->elements[$this->key]); } /** * Rewinds the iterator * * @return void */ public function rewind() { $this->key = 0; } /* }}} */ /* {{{ Countable interface */ /** * Returns the number of elements * * @return int */ public function count() { return count($this->elements); } /* }}} */ /* {{{ ArrayAccess Interface */ /** * Checks if an item exists through ArrayAccess. * * @param int $offset * @return bool */ public function offsetExists($offset) { return isset($this->elements[$offset]); } /** * Gets an item through ArrayAccess. * * @param int $offset * @return mixed */ public function offsetGet($offset) { return $this->elements[$offset]; } /** * Sets an item through ArrayAccess. * * @param int $offset * @param mixed $value * @return void */ public function offsetSet($offset, $value) { throw new \LogicException('You can not add new objects to an ElementList'); } /** * Sets an item through ArrayAccess. * * This method just forwards the request to the inner iterator * * @param int $offset * @return void */ public function offsetUnset($offset) { throw new \LogicException('You can not remove objects from an ElementList'); } /* }}} */ } sabre-vobject-3.5.0/lib/EofException.php000066400000000000000000000005451264477030300201260ustar00rootroot00000000000000setTimeRange($start, $end); } if ($objects) { $this->setObjects($objects); } if (is_null($timeZone)) { $timeZone = new DateTimeZone('UTC'); } $this->setTimeZone($timeZone); } /** * Sets the VCALENDAR object. * * If this is set, it will not be generated for you. You are responsible * for setting things like the METHOD, CALSCALE, VERSION, etc.. * * The VFREEBUSY object will be automatically added though. * * @param Component $vcalendar * @return void */ public function setBaseObject(Component $vcalendar) { $this->baseObject = $vcalendar; } /** * Sets the input objects * * You must either specify a valendar object as a strong, or as the parse * Component. * It's also possible to specify multiple objects as an array. * * @param mixed $objects * @return void */ public function setObjects($objects) { if (!is_array($objects)) { $objects = array($objects); } $this->objects = array(); foreach($objects as $object) { if (is_string($object)) { $this->objects[] = Reader::read($object); } elseif ($object instanceof Component) { $this->objects[] = $object; } else { throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); } } } /** * Sets the time range * * Any freebusy object falling outside of this time range will be ignored. * * @param DateTime $start * @param DateTime $end * @return void */ public function setTimeRange(\DateTime $start = null, \DateTime $end = null) { $this->start = $start; $this->end = $end; } /** * Sets the reference timezone for floating times. * * @param DateTimeZone $timeZone * @return void */ public function setTimeZone(DateTimeZone $timeZone) { $this->timeZone = $timeZone; } /** * Parses the input data and returns a correct VFREEBUSY object, wrapped in * a VCALENDAR. * * @return Component */ public function getResult() { $busyTimes = array(); foreach($this->objects as $key=>$object) { foreach($object->getBaseComponents() as $component) { switch($component->name) { case 'VEVENT' : $FBTYPE = 'BUSY'; if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { break; } if (isset($component->STATUS)) { $status = strtoupper($component->STATUS); if ($status==='CANCELLED') { break; } if ($status==='TENTATIVE') { $FBTYPE = 'BUSY-TENTATIVE'; } } $times = array(); if ($component->RRULE) { try { $iterator = new EventIterator($object, (string)$component->uid, $this->timeZone); } catch (NoInstancesException $e) { // This event is recurring, but it doesn't have a single // instance. We are skipping this event from the output // entirely. unset($this->objects[$key]); continue; } if ($this->start) { $iterator->fastForward($this->start); } $maxRecurrences = 200; while($iterator->valid() && --$maxRecurrences) { $startTime = $iterator->getDTStart(); if ($this->end && $startTime > $this->end) { break; } $times[] = array( $iterator->getDTStart(), $iterator->getDTEnd(), ); $iterator->next(); } } else { $startTime = $component->DTSTART->getDateTime($this->timeZone); if ($this->end && $startTime > $this->end) { break; } $endTime = null; if (isset($component->DTEND)) { $endTime = $component->DTEND->getDateTime($this->timeZone); } elseif (isset($component->DURATION)) { $duration = DateTimeParser::parseDuration((string)$component->DURATION); $endTime = clone $startTime; $endTime->add($duration); } elseif (!$component->DTSTART->hasTime()) { $endTime = clone $startTime; $endTime->modify('+1 day'); } else { // The event had no duration (0 seconds) break; } $times[] = array($startTime, $endTime); } foreach($times as $time) { if ($this->end && $time[0] > $this->end) break; if ($this->start && $time[1] < $this->start) break; $busyTimes[] = array( $time[0], $time[1], $FBTYPE, ); } break; case 'VFREEBUSY' : foreach($component->FREEBUSY as $freebusy) { $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY'; // Skipping intervals marked as 'free' if ($fbType==='FREE') continue; $values = explode(',', $freebusy); foreach($values as $value) { list($startTime, $endTime) = explode('/', $value); $startTime = DateTimeParser::parseDateTime($startTime); if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') { $duration = DateTimeParser::parseDuration($endTime); $endTime = clone $startTime; $endTime->add($duration); } else { $endTime = DateTimeParser::parseDateTime($endTime); } if($this->start && $this->start > $endTime) continue; if($this->end && $this->end < $startTime) continue; $busyTimes[] = array( $startTime, $endTime, $fbType ); } } break; } } } if ($this->baseObject) { $calendar = $this->baseObject; } else { $calendar = new VCalendar(); } $vfreebusy = $calendar->createComponent('VFREEBUSY'); $calendar->add($vfreebusy); if ($this->start) { $dtstart = $calendar->createProperty('DTSTART'); $dtstart->setDateTime($this->start); $vfreebusy->add($dtstart); } if ($this->end) { $dtend = $calendar->createProperty('DTEND'); $dtend->setDateTime($this->end); $vfreebusy->add($dtend); } $dtstamp = $calendar->createProperty('DTSTAMP'); $dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC'))); $vfreebusy->add($dtstamp); foreach($busyTimes as $busyTime) { $busyTime[0]->setTimeZone(new \DateTimeZone('UTC')); $busyTime[1]->setTimeZone(new \DateTimeZone('UTC')); $prop = $calendar->createProperty( 'FREEBUSY', $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') ); $prop['FBTYPE'] = $busyTime[2]; $vfreebusy->add($prop); } return $calendar; } } sabre-vobject-3.5.0/lib/ITip/000077500000000000000000000000001264477030300156665ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/ITip/Broker.php000066400000000000000000001104231264477030300176240ustar00rootroot00000000000000component !== 'VEVENT') { return false; } switch($itipMessage->method) { case 'REQUEST' : return $this->processMessageRequest($itipMessage, $existingObject); case 'CANCEL' : return $this->processMessageCancel($itipMessage, $existingObject); case 'REPLY' : return $this->processMessageReply($itipMessage, $existingObject); default : // Unsupported iTip message return null; } return $existingObject; } /** * This function parses a VCALENDAR object and figure out if any messages * need to be sent. * * A VCALENDAR object will be created from the perspective of either an * attendee, or an organizer. You must pass a string identifying the * current user, so we can figure out who in the list of attendees or the * organizer we are sending this message on behalf of. * * It's possible to specify the current user as an array, in case the user * has more than one identifying href (such as multiple emails). * * It $oldCalendar is specified, it is assumed that the operation is * updating an existing event, which means that we need to look at the * differences between events, and potentially send old attendees * cancellations, and current attendees updates. * * If $calendar is null, but $oldCalendar is specified, we treat the * operation as if the user has deleted an event. If the user was an * organizer, this means that we need to send cancellation notices to * people. If the user was an attendee, we need to make sure that the * organizer gets the 'declined' message. * * @param VCalendar|string $calendar * @param string|array $userHref * @param VCalendar|string $oldCalendar * @return array */ public function parseEvent($calendar = null, $userHref, $oldCalendar = null) { if ($oldCalendar) { if (is_string($oldCalendar)) { $oldCalendar = Reader::read($oldCalendar); } if (!isset($oldCalendar->VEVENT)) { // We only support events at the moment return array(); } $oldEventInfo = $this->parseEventInfo($oldCalendar); } else { $oldEventInfo = array( 'organizer' => null, 'significantChangeHash' => '', 'attendees' => array(), ); } $userHref = (array)$userHref; if (!is_null($calendar)) { if (is_string($calendar)) { $calendar = Reader::read($calendar); } if (!isset($calendar->VEVENT)) { // We only support events at the moment return array(); } $eventInfo = $this->parseEventInfo($calendar); if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) { // If there were no attendees on either side of the equation, // we don't need to do anything. return array(); } if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) { // There was no organizer before or after the change. return array(); } $baseCalendar = $calendar; // If the new object didn't have an organizer, the organizer // changed the object from a scheduling object to a non-scheduling // object. We just copy the info from the old object. if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) { $eventInfo['organizer'] = $oldEventInfo['organizer']; $eventInfo['organizerName'] = $oldEventInfo['organizerName']; } } else { // The calendar object got deleted, we need to process this as a // cancellation / decline. if (!$oldCalendar) { // No old and no new calendar, there's no thing to do. return array(); } $eventInfo = $oldEventInfo; if (in_array($eventInfo['organizer'], $userHref)) { // This is an organizer deleting the event. $eventInfo['attendees'] = array(); // Increasing the sequence, but only if the organizer deleted // the event. $eventInfo['sequence']++; } else { // This is an attendee deleting the event. foreach($eventInfo['attendees'] as $key=>$attendee) { if (in_array($attendee['href'], $userHref)) { $eventInfo['attendees'][$key]['instances'] = array('master' => array('id'=>'master', 'partstat' => 'DECLINED') ); } } } $baseCalendar = $oldCalendar; } if (in_array($eventInfo['organizer'], $userHref)) { return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); } elseif ($oldCalendar) { // We need to figure out if the user is an attendee, but we're only // doing so if there's an oldCalendar, because we only want to // process updates, not creation of new events. foreach($eventInfo['attendees'] as $attendee) { if (in_array($attendee['href'], $userHref)) { return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); } } } return array(); } /** * Processes incoming REQUEST messages. * * This is message from an organizer, and is either a new event * invite, or an update to an existing one. * * * @param Message $itipMessage * @param VCalendar $existingObject * @return VCalendar|null */ protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) { if (!$existingObject) { // This is a new invite, and we're just going to copy over // all the components from the invite. $existingObject = new VCalendar(); foreach($itipMessage->message->getComponents() as $component) { $existingObject->add(clone $component); } } else { // We need to update an existing object with all the new // information. We can just remove all existing components // and create new ones. foreach($existingObject->getComponents() as $component) { $existingObject->remove($component); } foreach($itipMessage->message->getComponents() as $component) { $existingObject->add(clone $component); } } return $existingObject; } /** * Processes incoming CANCEL messages. * * This is a message from an organizer, and means that either an * attendee got removed from an event, or an event got cancelled * altogether. * * @param Message $itipMessage * @param VCalendar $existingObject * @return VCalendar|null */ protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) { if (!$existingObject) { // The event didn't exist in the first place, so we're just // ignoring this message. } else { foreach($existingObject->VEVENT as $vevent) { $vevent->STATUS = 'CANCELLED'; $vevent->SEQUENCE = $itipMessage->sequence; } } return $existingObject; } /** * Processes incoming REPLY messages. * * The message is a reply. This is for example an attendee telling * an organizer he accepted the invite, or declined it. * * @param Message $itipMessage * @param VCalendar $existingObject * @return VCalendar|null */ protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) { // A reply can only be processed based on an existing object. // If the object is not available, the reply is ignored. if (!$existingObject) { return null; } $instances = array(); $requestStatus = '2.0'; // Finding all the instances the attendee replied to. foreach($itipMessage->message->VEVENT as $vevent) { $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; $attendee = $vevent->ATTENDEE; $instances[$recurId] = $attendee['PARTSTAT']->getValue(); if (isset($vevent->{'REQUEST-STATUS'})) { $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); list($requestStatus) = explode(';', $requestStatus); } } // Now we need to loop through the original organizer event, to find // all the instances where we have a reply for. $masterObject = null; foreach($existingObject->VEVENT as $vevent) { $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; if ($recurId==='master') { $masterObject = $vevent; } if (isset($instances[$recurId])) { $attendeeFound = false; if (isset($vevent->ATTENDEE)) { foreach($vevent->ATTENDEE as $attendee) { if ($attendee->getValue() === $itipMessage->sender) { $attendeeFound = true; $attendee['PARTSTAT'] = $instances[$recurId]; $attendee['SCHEDULE-STATUS'] = $requestStatus; // Un-setting the RSVP status, because we now know // that the attende already replied. unset($attendee['RSVP']); break; } } } if (!$attendeeFound) { // Adding a new attendee. The iTip documentation calls this // a party crasher. $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array( 'PARTSTAT' => $instances[$recurId] )); if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName; } unset($instances[$recurId]); } } if(!$masterObject) { // No master object, we can't add new instances. return null; } // If we got replies to instances that did not exist in the // original list, it means that new exceptions must be created. foreach($instances as $recurId=>$partstat) { $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); $found = false; $iterations = 1000; do { $newObject = $recurrenceIterator->getEventObject(); $recurrenceIterator->next(); if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) { $found = true; } $iterations--; } while($recurrenceIterator->valid() && !$found && $iterations); // Invalid recurrence id. Skipping this object. if (!$found) continue; unset( $newObject->RRULE, $newObject->EXDATE, $newObject->RDATE ); $attendeeFound = false; if (isset($newObject->ATTENDEE)) { foreach($newObject->ATTENDEE as $attendee) { if ($attendee->getValue() === $itipMessage->sender) { $attendeeFound = true; $attendee['PARTSTAT'] = $partstat; break; } } } if (!$attendeeFound) { // Adding a new attendee $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array( 'PARTSTAT' => $partstat )); if ($itipMessage->senderName) { $attendee['CN'] = $itipMessage->senderName; } } $existingObject->add($newObject); } return $existingObject; } /** * This method is used in cases where an event got updated, and we * potentially need to send emails to attendees to let them know of updates * in the events. * * We will detect which attendees got added, which got removed and create * specific messages for these situations. * * @param VCalendar $calendar * @param array $eventInfo * @param array $oldEventInfo * @return array */ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) { // Merging attendee lists. $attendees = array(); foreach($oldEventInfo['attendees'] as $attendee) { $attendees[$attendee['href']] = array( 'href' => $attendee['href'], 'oldInstances' => $attendee['instances'], 'newInstances' => array(), 'name' => $attendee['name'], 'forceSend' => null, ); } foreach($eventInfo['attendees'] as $attendee) { if (isset($attendees[$attendee['href']])) { $attendees[$attendee['href']]['name'] = $attendee['name']; $attendees[$attendee['href']]['newInstances'] = $attendee['instances']; $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; } else { $attendees[$attendee['href']] = array( 'href' => $attendee['href'], 'oldInstances' => array(), 'newInstances' => $attendee['instances'], 'name' => $attendee['name'], 'forceSend' => $attendee['forceSend'], ); } } $messages = array(); foreach($attendees as $attendee) { // An organizer can also be an attendee. We should not generate any // messages for those. if ($attendee['href']===$eventInfo['organizer']) { continue; } $message = new Message(); $message->uid = $eventInfo['uid']; $message->component = 'VEVENT'; $message->sequence = $eventInfo['sequence']; $message->sender = $eventInfo['organizer']; $message->senderName = $eventInfo['organizerName']; $message->recipient = $attendee['href']; $message->recipientName = $attendee['name']; if (!$attendee['newInstances']) { // If there are no instances the attendee is a part of, it // means the attendee was removed and we need to send him a // CANCEL. $message->method = 'CANCEL'; // Creating the new iCalendar body. $icalMsg = new VCalendar(); $icalMsg->METHOD = $message->method; $event = $icalMsg->add('VEVENT', array( 'UID' => $message->uid, 'SEQUENCE' => $message->sequence, )); if (isset($calendar->VEVENT->SUMMARY)) { $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue()); } $event->add(clone $calendar->VEVENT->DTSTART); if (isset($calendar->VEVENT->DTEND)) { $event->add(clone $calendar->VEVENT->DTEND); } elseif (isset($calendar->VEVENT->DURATION)) { $event->add(clone $calendar->VEVENT->DURATION); } $org = $event->add('ORGANIZER', $eventInfo['organizer']); if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName']; $event->add('ATTENDEE', $attendee['href'], array( 'CN' => $attendee['name'], )); $message->significantChange = true; } else { // The attendee gets the updated event body $message->method = 'REQUEST'; // Creating the new iCalendar body. $icalMsg = new VCalendar(); $icalMsg->METHOD = $message->method; foreach($calendar->select('VTIMEZONE') as $timezone) { $icalMsg->add(clone $timezone); } // We need to find out that this change is significant. If it's // not, systems may opt to not send messages. // // We do this based on the 'significantChangeHash' which is // some value that changes if there's a certain set of // properties changed in the event, or simply if there's a // difference in instances that the attendee is invited to. $message->significantChange = $attendee['forceSend'] === 'REQUEST' || array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash']; foreach($attendee['newInstances'] as $instanceId => $instanceInfo) { $currentEvent = clone $eventInfo['instances'][$instanceId]; if ($instanceId === 'master') { // We need to find a list of events that the attendee // is not a part of to add to the list of exceptions. $exceptions = array(); foreach($eventInfo['instances'] as $instanceId=>$vevent) { if (!isset($attendee['newInstances'][$instanceId])) { $exceptions[] = $instanceId; } } // If there were exceptions, we need to add it to an // existing EXDATE property, if it exists. if ($exceptions) { if (isset($currentEvent->EXDATE)) { $currentEvent->EXDATE->setParts(array_merge( $currentEvent->EXDATE->getParts(), $exceptions )); } else { $currentEvent->EXDATE = $exceptions; } } // Cleaning up any scheduling information that // shouldn't be sent along. unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']); unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']); foreach($currentEvent->ATTENDEE as $attendee) { unset($attendee['SCHEDULE-FORCE-SEND']); unset($attendee['SCHEDULE-STATUS']); // We're adding PARTSTAT=NEEDS-ACTION to ensure that // iOS shows an "Inbox Item" if (!isset($attendee['PARTSTAT'])) { $attendee['PARTSTAT'] = 'NEEDS-ACTION'; } } } $icalMsg->add($currentEvent); } } $message->message = $icalMsg; $messages[] = $message; } return $messages; } /** * Parse an event update for an attendee. * * This function figures out if we need to send a reply to an organizer. * * @param VCalendar $calendar * @param array $eventInfo * @param array $oldEventInfo * @param string $attendee * @return Message[] */ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') { return array(); } // Don't bother generating messages for events that have already been // cancelled. if ($eventInfo['status']==='CANCELLED') { return array(); } $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? $oldEventInfo['attendees'][$attendee]['instances'] : array(); $instances = array(); foreach($oldInstances as $instance) { $instances[$instance['id']] = array( 'id' => $instance['id'], 'oldstatus' => $instance['partstat'], 'newstatus' => null, ); } foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) { if (isset($instances[$instance['id']])) { $instances[$instance['id']]['newstatus'] = $instance['partstat']; } else { $instances[$instance['id']] = array( 'id' => $instance['id'], 'oldstatus' => null, 'newstatus' => $instance['partstat'], ); } } // We need to also look for differences in EXDATE. If there are new // items in EXDATE, it means that an attendee deleted instances of an // event, which means we need to send DECLINED specifically for those // instances. // We only need to do that though, if the master event is not declined. if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') { foreach($eventInfo['exdate'] as $exDate) { if (!in_array($exDate, $oldEventInfo['exdate'])) { if (isset($instances[$exDate])) { $instances[$exDate]['newstatus'] = 'DECLINED'; } else { $instances[$exDate] = array( 'id' => $exDate, 'oldstatus' => null, 'newstatus' => 'DECLINED', ); } } } } // Gathering a few extra properties for each instance. foreach($instances as $recurId=>$instanceInfo) { if (isset($eventInfo['instances'][$recurId])) { $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; } else { $instances[$recurId]['dtstart'] = $recurId; } } $message = new Message(); $message->uid = $eventInfo['uid']; $message->method = 'REPLY'; $message->component = 'VEVENT'; $message->sequence = $eventInfo['sequence']; $message->sender = $attendee; $message->senderName = $eventInfo['attendees'][$attendee]['name']; $message->recipient = $eventInfo['organizer']; $message->recipientName = $eventInfo['organizerName']; $icalMsg = new VCalendar(); $icalMsg->METHOD = 'REPLY'; $hasReply = false; foreach($instances as $instance) { if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') { // Skip continue; } $event = $icalMsg->add('VEVENT', array( 'UID' => $message->uid, 'SEQUENCE' => $message->sequence, )); $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():''; // Adding properties from the correct source instance if (isset($eventInfo['instances'][$instance['id']])) { $instanceObj = $eventInfo['instances'][$instance['id']]; $event->add(clone $instanceObj->DTSTART); if (isset($instanceObj->DTEND)) { $event->add(clone $instanceObj->DTEND); } elseif (isset($instanceObj->DURATION)) { $event->add(clone $instanceObj->DURATION); } if (isset($instanceObj->SUMMARY)) { $event->add('SUMMARY', $instanceObj->SUMMARY->getValue()); } elseif ($summary) { $event->add('SUMMARY', $summary); } } else { // This branch of the code is reached, when a reply is // generated for an instance of a recurring event, through the // fact that the instance has disappeared by showing up in // EXDATE $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); // Treat is as a DATE field if (strlen($instance['id']) <= 8) { $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE')); } else { $recur = $event->add('DTSTART', $dt); } if ($summary) { $event->add('SUMMARY', $summary); } } if ($instance['id'] !== 'master') { $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); // Treat is as a DATE field if (strlen($instance['id']) <= 8) { $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE')); } else { $recur = $event->add('RECURRENCE-ID', $dt); } } $organizer = $event->add('ORGANIZER', $message->recipient); if ($message->recipientName) { $organizer['CN'] = $message->recipientName; } $attendee = $event->add('ATTENDEE', $message->sender, array( 'PARTSTAT' => $instance['newstatus'] )); if ($message->senderName) { $attendee['CN'] = $message->senderName; } $hasReply = true; } if ($hasReply) { $message->message = $icalMsg; return array($message); } else { return array(); } } /** * Returns attendee information and information about instances of an * event. * * Returns an array with the following keys: * * 1. uid * 2. organizer * 3. organizerName * 4. attendees * 5. instances * * @param VCalendar $calendar * @return array */ protected function parseEventInfo(VCalendar $calendar = null) { $uid = null; $organizer = null; $organizerName = null; $organizerForceSend = null; $sequence = null; $timezone = null; $status = null; $organizerScheduleAgent = 'SERVER'; $significantChangeHash = ''; // Now we need to collect a list of attendees, and which instances they // are a part of. $attendees = array(); $instances = array(); $exdate = array(); foreach($calendar->VEVENT as $vevent) { if (is_null($uid)) { $uid = $vevent->UID->getValue(); } else { if ($uid !== $vevent->UID->getValue()) { throw new ITipException('If a calendar contained more than one event, they must have the same UID.'); } } if (!isset($vevent->DTSTART)) { throw new ITipException('An event MUST have a DTSTART property.'); } if (isset($vevent->ORGANIZER)) { if (is_null($organizer)) { $organizer = $vevent->ORGANIZER->getNormalizedValue(); $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null; } else { if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) { throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); } } $organizerForceSend = isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ? strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) : null; $organizerScheduleAgent = isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) : 'SERVER'; } if (is_null($sequence) && isset($vevent->SEQUENCE)) { $sequence = $vevent->SEQUENCE->getValue(); } if (isset($vevent->EXDATE)) { foreach ($vevent->select('EXDATE') as $val) { $exdate = array_merge($exdate, $val->getParts()); } sort($exdate); } if (isset($vevent->STATUS)) { $status = strtoupper($vevent->STATUS->getValue()); } $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; if ($recurId==='master') { $timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); } if(isset($vevent->ATTENDEE)) { foreach($vevent->ATTENDEE as $attendee) { if ($this->scheduleAgentServerRules && isset($attendee['SCHEDULE-AGENT']) && strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT' ) { continue; } $partStat = isset($attendee['PARTSTAT']) ? strtoupper($attendee['PARTSTAT']) : 'NEEDS-ACTION'; $forceSend = isset($attendee['SCHEDULE-FORCE-SEND']) ? strtoupper($attendee['SCHEDULE-FORCE-SEND']) : null; if (isset($attendees[$attendee->getNormalizedValue()])) { $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array( 'id' => $recurId, 'partstat' => $partStat, 'force-send' => $forceSend, ); } else { $attendees[$attendee->getNormalizedValue()] = array( 'href' => $attendee->getNormalizedValue(), 'instances' => array( $recurId => array( 'id' => $recurId, 'partstat' => $partStat, ), ), 'name' => isset($attendee['CN'])?(string)$attendee['CN']:null, 'forceSend' => $forceSend, ); } } $instances[$recurId] = $vevent; } foreach($this->significantChangeProperties as $prop) { if (isset($vevent->$prop)) { $propertyValues = $vevent->select($prop); $significantChangeHash.=$prop.':'; if ($prop === 'EXDATE') { $significantChangeHash.= implode(',', $exdate).';'; } else { foreach($propertyValues as $val) { $significantChangeHash.= $val->getValue().';'; } } } } } $significantChangeHash = md5($significantChangeHash); return compact( 'uid', 'organizer', 'organizerName', 'organizerScheduleAgent', 'organizerForceSend', 'instances', 'attendees', 'sequence', 'exdate', 'timezone', 'significantChangeHash', 'status' ); } } sabre-vobject-3.5.0/lib/ITip/ITipException.php000066400000000000000000000005301264477030300211210ustar00rootroot00000000000000scheduleStatus) { return false; } else { list($scheduleStatus) = explode(';', $this->scheduleStatus); return $scheduleStatus; } } } sabre-vobject-3.5.0/lib/ITip/SameOrganizerForAllComponentsException.php000066400000000000000000000007601264477030300261750ustar00rootroot00000000000000iterator)) return $this->iterator; return new ElementList(array($this)); } /** * Sets the overridden iterator * * Note that this is not actually part of the iterator interface * * @param ElementList $iterator * @return void */ public function setIterator(ElementList $iterator) { $this->iterator = $iterator; } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ public function validate($options = 0) { return array(); } /* }}} */ /* {{{ Countable interface */ /** * Returns the number of elements * * @return int */ public function count() { $it = $this->getIterator(); return $it->count(); } /* }}} */ /* {{{ ArrayAccess Interface */ /** * Checks if an item exists through ArrayAccess. * * This method just forwards the request to the inner iterator * * @param int $offset * @return bool */ public function offsetExists($offset) { $iterator = $this->getIterator(); return $iterator->offsetExists($offset); } /** * Gets an item through ArrayAccess. * * This method just forwards the request to the inner iterator * * @param int $offset * @return mixed */ public function offsetGet($offset) { $iterator = $this->getIterator(); return $iterator->offsetGet($offset); } /** * Sets an item through ArrayAccess. * * This method just forwards the request to the inner iterator * * @param int $offset * @param mixed $value * @return void */ public function offsetSet($offset, $value) { $iterator = $this->getIterator(); $iterator->offsetSet($offset,$value); // @codeCoverageIgnoreStart // // This method always throws an exception, so we ignore the closing // brace } // @codeCoverageIgnoreEnd /** * Sets an item through ArrayAccess. * * This method just forwards the request to the inner iterator * * @param int $offset * @return void */ public function offsetUnset($offset) { $iterator = $this->getIterator(); $iterator->offsetUnset($offset); // @codeCoverageIgnoreStart // // This method always throws an exception, so we ignore the closing // brace } // @codeCoverageIgnoreEnd /* }}} */ } sabre-vobject-3.5.0/lib/Parameter.php000066400000000000000000000216331264477030300174570ustar00rootroot00000000000000name = strtoupper($name); $this->root = $root; if (is_null($name)) { $this->noName = true; $this->name = static::guessParameterNameByValue($value); } // If guessParameterNameByValue() returns an empty string // above, we're actually dealing with a parameter that has no value. // In that case we have to move the value to the name. if ($this->name === '') { $this->noName = false; $this->name = strtoupper($value); } else { $this->setValue($value); } } /** * Try to guess property name by value, can be used for vCard 2.1 nameless parameters. * * Figuring out what the name should have been. Note that a ton of * these are rather silly in 2014 and would probably rarely be * used, but we like to be complete. * * @param string $value * @return string */ public static function guessParameterNameByValue($value) { switch(strtoupper($value)) { // Encodings case '7-BIT' : case 'QUOTED-PRINTABLE' : case 'BASE64' : $name = 'ENCODING'; break; // Common types case 'WORK' : case 'HOME' : case 'PREF' : // Delivery Label Type case 'DOM' : case 'INTL' : case 'POSTAL' : case 'PARCEL' : // Telephone types case 'VOICE' : case 'FAX' : case 'MSG' : case 'CELL' : case 'PAGER' : case 'BBS' : case 'MODEM' : case 'CAR' : case 'ISDN' : case 'VIDEO' : // EMAIL types (lol) case 'AOL' : case 'APPLELINK' : case 'ATTMAIL' : case 'CIS' : case 'EWORLD' : case 'INTERNET' : case 'IBMMAIL' : case 'MCIMAIL' : case 'POWERSHARE' : case 'PRODIGY' : case 'TLX' : case 'X400' : // Photo / Logo format types case 'GIF' : case 'CGM' : case 'WMF' : case 'BMP' : case 'DIB' : case 'PICT' : case 'TIFF' : case 'PDF ': case 'PS' : case 'JPEG' : case 'MPEG' : case 'MPEG2' : case 'AVI' : case 'QTIME' : // Sound Digital Audio Type case 'WAVE' : case 'PCM' : case 'AIFF' : // Key types case 'X509' : case 'PGP' : $name = 'TYPE'; break; // Value types case 'INLINE' : case 'URL' : case 'CONTENT-ID' : case 'CID' : $name = 'VALUE'; break; default: $name = ''; } return $name; } /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * @param string|array $value * @return void */ public function setValue($value) { $this->value = $value; } /** * Returns the current value * * This method will always return a string, or null. If there were multiple * values, it will automatically concatinate them (separated by comma). * * @return string|null */ public function getValue() { if (is_array($this->value)) { return implode(',' , $this->value); } else { return $this->value; } } /** * Sets multiple values for this parameter. * * @param array $value * @return void */ public function setParts(array $value) { $this->value = $value; } /** * Returns all values for this parameter. * * If there were no values, an empty array will be returned. * * @return array */ public function getParts() { if (is_array($this->value)) { return $this->value; } elseif (is_null($this->value)) { return array(); } else { return array($this->value); } } /** * Adds a value to this parameter * * If the argument is specified as an array, all items will be added to the * parameter value list. * * @param string|array $part * @return void */ public function addValue($part) { if (is_null($this->value)) { $this->value = $part; } else { $this->value = array_merge((array)$this->value, (array)$part); } } /** * Checks if this parameter contains the specified value. * * This is a case-insensitive match. It makes sense to call this for for * instance the TYPE parameter, to see if it contains a keyword such as * 'WORK' or 'FAX'. * * @param string $value * @return bool */ public function has($value) { return in_array( strtolower($value), array_map('strtolower', (array)$this->value) ); } /** * Turns the object back into a serialized blob. * * @return string */ public function serialize() { $value = $this->getParts(); if (count($value)===0) { return $this->name . '='; } if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) { return implode(';', $value); } return $this->name . '=' . array_reduce( $value, function($out, $item) { if (!is_null($out)) $out.=','; // If there's no special characters in the string, we'll use the simple // format. // // The list of special characters is defined as: // // Any character except CONTROL, DQUOTE, ";", ":", "," // // by the iCalendar spec: // https://tools.ietf.org/html/rfc5545#section-3.1 // // And we add ^ to that because of: // https://tools.ietf.org/html/rfc6868 // // But we've found that iCal (7.0, shipped with OSX 10.9) // severaly trips on + characters not being quoted, so we // added + as well. if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { return $out.$item; } else { // Enclosing in double-quotes, and using RFC6868 for encoding any // special characters $out.='"' . strtr( $item, array( '^' => '^^', "\n" => '^n', '"' => '^\'', ) ) . '"'; return $out; } } ); } /** * This method returns an array, with the representation as it should be * encoded in json. This is used to create jCard or jCal documents. * * @return array */ public function jsonSerialize() { return $this->value; } /** * Called when this object is being cast to a string * * @return string */ public function __toString() { return (string)$this->getValue(); } /** * Returns the iterator for this object * * @return ElementList */ public function getIterator() { if (!is_null($this->iterator)) return $this->iterator; return $this->iterator = new ArrayObject((array)$this->value); } } sabre-vobject-3.5.0/lib/ParseException.php000066400000000000000000000005111264477030300204600ustar00rootroot00000000000000setInput($input); } if (is_null($this->input)) { throw new EofException('End of input stream, or no input supplied'); } if (!is_null($options)) { $this->options = $options; } switch($this->input[0]) { case 'vcalendar' : $this->root = new VCalendar(array(), false); break; case 'vcard' : $this->root = new VCard(array(), false); break; default : throw new ParseException('The root component must either be a vcalendar, or a vcard'); } foreach($this->input[1] as $prop) { $this->root->add($this->parseProperty($prop)); } if (isset($this->input[2])) foreach($this->input[2] as $comp) { $this->root->add($this->parseComponent($comp)); } // Resetting the input so we can throw an feof exception the next time. $this->input = null; return $this->root; } /** * Parses a component * * @param array $jComp * @return \Sabre\VObject\Component */ public function parseComponent(array $jComp) { // We can remove $self from PHP 5.4 onward. $self = $this; $properties = array_map( function($jProp) use ($self) { return $self->parseProperty($jProp); }, $jComp[1] ); if (isset($jComp[2])) { $components = array_map( function($jComp) use ($self) { return $self->parseComponent($jComp); }, $jComp[2] ); } else $components = array(); return $this->root->createComponent( $jComp[0], array_merge($properties, $components), $defaults = false ); } /** * Parses properties. * * @param array $jProp * @return \Sabre\VObject\Property */ public function parseProperty(array $jProp) { list( $propertyName, $parameters, $valueType ) = $jProp; $propertyName = strtoupper($propertyName); // This is the default class we would be using if we didn't know the // value type. We're using this value later in this function. $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); $parameters = (array)$parameters; $value = array_slice($jProp, 3); $valueType = strtoupper($valueType); if (isset($parameters['group'])) { $propertyName = $parameters['group'] . '.' . $propertyName; unset($parameters['group']); } $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType); $prop->setJsonValue($value); // We have to do something awkward here. FlatText as well as Text // represents TEXT values. We have to normalize these here. In the // future we can get rid of FlatText once we're allowed to break BC // again. if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') { $defaultPropertyClass = 'Sabre\VObject\Property\Text'; } // If the value type we received (e.g.: TEXT) was not the default value // type for the given property (e.g.: BDAY), we need to add a VALUE= // parameter. if ($defaultPropertyClass !== get_class($prop)) { $prop["VALUE"] = $valueType; } return $prop; } /** * Sets the input data * * @param resource|string|array $input * @return void */ public function setInput($input) { if (is_resource($input)) { $input = stream_get_contents($input); } if (is_string($input)) { $input = json_decode($input); } $this->input = $input; } } sabre-vobject-3.5.0/lib/Parser/MimeDir.php000066400000000000000000000461441264477030300203250ustar00rootroot00000000000000root = null; if (!is_null($input)) { $this->setInput($input); } if (!is_null($options)) $this->options = $options; $this->parseDocument(); return $this->root; } /** * Sets the input buffer. Must be a string or stream. * * @param resource|string $input * @return void */ public function setInput($input) { // Resetting the parser $this->lineIndex = 0; $this->startLine = 0; if (is_string($input)) { // Convering to a stream. $stream = fopen('php://temp', 'r+'); fwrite($stream, $input); rewind($stream); $this->input = $stream; } elseif (is_resource($input)) { $this->input = $input; } else { throw new \InvalidArgumentException('This parser can only read from strings or streams.'); } } /** * Parses an entire document. * * @return void */ protected function parseDocument() { $line = $this->readLine(); // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). // It's 0xEF 0xBB 0xBF in UTF-8 hex. if ( 3 <= strlen($line) && ord($line[0]) === 0xef && ord($line[1]) === 0xbb && ord($line[2]) === 0xbf) { $line = substr($line, 3); } switch(strtoupper($line)) { case 'BEGIN:VCALENDAR' : $class = isset(VCalendar::$componentMap['VCALENDAR']) ? VCalendar::$componentMap[$name] : 'Sabre\\VObject\\Component\\VCalendar'; break; case 'BEGIN:VCARD' : $class = isset(VCard::$componentMap['VCARD']) ? VCard::$componentMap['VCARD'] : 'Sabre\\VObject\\Component\\VCard'; break; default : throw new ParseException('This parser only supports VCARD and VCALENDAR files'); } $this->root = new $class(array(), false); while(true) { // Reading until we hit END: $line = $this->readLine(); if (strtoupper(substr($line,0,4)) === 'END:') { break; } $result = $this->parseLine($line); if ($result) { $this->root->add($result); } } $name = strtoupper(substr($line, 4)); if ($name!==$this->root->name) { throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"'); } } /** * Parses a line, and if it hits a component, it will also attempt to parse * the entire component * * @param string $line Unfolded line * @return Node */ protected function parseLine($line) { // Start of a new component if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') { $component = $this->root->createComponent(substr($line,6), array(), false); while(true) { // Reading until we hit END: $line = $this->readLine(); if (strtoupper(substr($line,0,4)) === 'END:') { break; } $result = $this->parseLine($line); if ($result) { $component->add($result); } } $name = strtoupper(substr($line, 4)); if ($name!==$component->name) { throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"'); } return $component; } else { // Property reader $property = $this->readProperty($line); if (!$property) { // Ignored line return false; } return $property; } } /** * We need to look ahead 1 line every time to see if we need to 'unfold' * the next line. * * If that was not the case, we store it here. * * @var null|string */ protected $lineBuffer; /** * The real current line number. */ protected $lineIndex = 0; /** * In the case of unfolded lines, this property holds the line number for * the start of the line. * * @var int */ protected $startLine = 0; /** * Contains a 'raw' representation of the current line. * * @var string */ protected $rawLine; /** * Reads a single line from the buffer. * * This method strips any newlines and also takes care of unfolding. * * @throws \Sabre\VObject\EofException * @return string */ protected function readLine() { if (!is_null($this->lineBuffer)) { $rawLine = $this->lineBuffer; $this->lineBuffer = null; } else { do { $eof = feof($this->input); $rawLine = fgets($this->input); if ($eof || (feof($this->input) && $rawLine===false)) { throw new EofException('End of document reached prematurely'); } if ($rawLine === false) { throw new ParseException('Error reading from input stream'); } $rawLine = rtrim($rawLine, "\r\n"); } while ($rawLine === ''); // Skipping empty lines $this->lineIndex++; } $line = $rawLine; $this->startLine = $this->lineIndex; // Looking ahead for folded lines. while (true) { $nextLine = rtrim(fgets($this->input), "\r\n"); $this->lineIndex++; if (!$nextLine) { break; } if ($nextLine[0] === "\t" || $nextLine[0] === " ") { $line .= substr($nextLine, 1); $rawLine .= "\n " . substr($nextLine, 1); } else { $this->lineBuffer = $nextLine; break; } } $this->rawLine = $rawLine; return $line; } /** * Reads a property or component from a line. * * @return void */ protected function readProperty($line) { if ($this->options & self::OPTION_FORGIVING) { $propNameToken = 'A-Z0-9\-\._\\/'; } else { $propNameToken = 'A-Z0-9\-\.'; } $paramNameToken = 'A-Z0-9\-'; $safeChar = '^";:,'; $qSafeChar = '^"'; $regex = "/ ^(?P [$propNameToken]+ ) (?=[;:]) # property name | (?<=:)(?P .+)$ # property value | ;(?P [$paramNameToken]+) (?=[=;:]) # parameter name | (=|,)(?P # parameter value (?: [$safeChar]*) | \"(?: [$qSafeChar]+)\" ) (?=[;:,]) /xi"; //echo $regex, "\n"; die(); preg_match_all($regex, $line, $matches, PREG_SET_ORDER); $property = array( 'name' => null, 'parameters' => array(), 'value' => null ); $lastParam = null; /** * Looping through all the tokens. * * Note that we are looping through them in reverse order, because if a * sub-pattern matched, the subsequent named patterns will not show up * in the result. */ foreach($matches as $match) { if (isset($match['paramValue'])) { if ($match['paramValue'] && $match['paramValue'][0] === '"') { $value = substr($match['paramValue'], 1, -1); } else { $value = $match['paramValue']; } $value = $this->unescapeParam($value); if (is_null($property['parameters'][$lastParam])) { $property['parameters'][$lastParam] = $value; } elseif (is_array($property['parameters'][$lastParam])) { $property['parameters'][$lastParam][] = $value; } else { $property['parameters'][$lastParam] = array( $property['parameters'][$lastParam], $value ); } continue; } if (isset($match['paramName'])) { $lastParam = strtoupper($match['paramName']); if (!isset($property['parameters'][$lastParam])) { $property['parameters'][$lastParam] = null; } continue; } if (isset($match['propValue'])) { $property['value'] = $match['propValue']; continue; } if (isset($match['name']) && $match['name']) { $property['name'] = strtoupper($match['name']); continue; } // @codeCoverageIgnoreStart throw new \LogicException('This code should not be reachable'); // @codeCoverageIgnoreEnd } if (is_null($property['value'])) { $property['value'] = ''; } if (!$property['name']) { if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { return false; } throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); } // vCard 2.1 states that parameters may appear without a name, and only // a value. We can deduce the value based on it's name. // // Our parser will get those as parameters without a value instead, so // we're filtering these parameters out first. $namedParameters = array(); $namelessParameters = array(); foreach($property['parameters'] as $name=>$value) { if (!is_null($value)) { $namedParameters[$name] = $value; } else { $namelessParameters[] = $name; } } $propObj = $this->root->createProperty($property['name'], null, $namedParameters); foreach($namelessParameters as $namelessParameter) { $propObj->add(null, $namelessParameter); } if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') { $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); } else { $propObj->setRawMimeDirValue($property['value']); } return $propObj; } /** * Unescapes a property value. * * vCard 2.1 says: * * Semi-colons must be escaped in some property values, specifically * ADR, ORG and N. * * Semi-colons must be escaped in parameter values, because semi-colons * are also use to separate values. * * No mention of escaping backslashes with another backslash. * * newlines are not escaped either, instead QUOTED-PRINTABLE is used to * span values over more than 1 line. * * vCard 3.0 says: * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be * escaped, all time time. * * Comma's are used for delimeters in multiple values * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped, * as in some properties semi-colon is used for separators. * * Properties using semi-colons: N, ADR, GEO, ORG * * Both ADR and N's individual parts may be broken up further with a * comma. * * Properties using commas: NICKNAME, CATEGORIES * * vCard 4.0 (rfc6350) says: * * Commas must be escaped. * * Semi-colons may be escaped, an unescaped semi-colon _may_ be a * delimiter, depending on the property. * * Backslashes must be escaped * * Newlines must be escaped as either \N or \n. * * Some compound properties may contain multiple parts themselves, so a * comma within a semi-colon delimited property may also be unescaped * to denote multiple parts _within_ the compound property. * * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP. * * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID. * * Even though the spec says that commas must always be escaped, the * example for GEO in Section 6.5.2 seems to violate this. * * iCalendar 2.0 (rfc5545) says: * * Commas or semi-colons may be used as delimiters, depending on the * property. * * Commas, semi-colons, backslashes, newline (\N or \n) are always * escaped, unless they are delimiters. * * Colons shall not be escaped. * * Commas can be considered the 'default delimiter' and is described as * the delimiter in cases where the order of the multiple values is * insignificant. * * Semi-colons are described as the delimiter for 'structured values'. * They are specifically used in Semi-colons are used as a delimiter in * REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however. * * Now for the parameters * * If delimiter is not set (null) this method will just return a string. * If it's a comma or a semi-colon the string will be split on those * characters, and always return an array. * * @param string $input * @param string $delimiter * @return string|string[] */ static public function unescapeValue($input, $delimiter = ';') { $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )'; if ($delimiter) { $regex .= ' | (' . $delimiter . ')'; } $regex .= ') #x'; $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $resultArray = array(); $result = ''; foreach($matches as $match) { switch ($match) { case '\\\\' : $result .='\\'; break; case '\N' : case '\n' : $result .="\n"; break; case '\;' : $result .=';'; break; case '\,' : $result .=','; break; case $delimiter : $resultArray[] = $result; $result = ''; break; default : $result .= $match; break; } } $resultArray[] = $result; return $delimiter ? $resultArray : $result; } /** * Unescapes a parameter value. * * vCard 2.1: * * Does not mention a mechanism for this. In addition, double quotes * are never used to wrap values. * * This means that parameters can simply not contain colons or * semi-colons. * * vCard 3.0 (rfc2425, rfc2426): * * Parameters _may_ be surrounded by double quotes. * * If this is not the case, semi-colon, colon and comma may simply not * occur (the comma used for multiple parameter values though). * * If it is surrounded by double-quotes, it may simply not contain * double-quotes. * * This means that a parameter can in no case encode double-quotes, or * newlines. * * vCard 4.0 (rfc6350) * * Behavior seems to be identical to vCard 3.0 * * iCalendar 2.0 (rfc5545) * * Behavior seems to be identical to vCard 3.0 * * Parameter escaping mechanism (rfc6868) : * * This rfc describes a new way to escape parameter values. * * New-line is encoded as ^n * * ^ is encoded as ^^. * * " is encoded as ^' * * @param string $input * @return void */ private function unescapeParam($input) { return preg_replace_callback( '#(\^(\^|n|\'))#', function($matches) { switch($matches[2]) { case 'n' : return "\n"; case '^' : return '^'; case '\'' : return '"'; // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd }, $input ); } /** * Gets the full quoted printable value. * * We need a special method for this, because newlines have both a meaning * in vCards, and in QuotedPrintable. * * This method does not do any decoding. * * @return string */ private function extractQuotedPrintableValue() { // We need to parse the raw line again to get the start of the value. // // We are basically looking for the first colon (:), but we need to // skip over the parameters first, as they may contain one. $regex = '/^ (?: [^:])+ # Anything but a colon (?: "[^"]")* # A parameter in double quotes : # start of the value we really care about (.*)$ /xs'; preg_match($regex, $this->rawLine, $matches); $value = $matches[1]; // Removing the first whitespace character from every line. Kind of // like unfolding, but we keep the newline. $value = str_replace("\n ", "\n", $value); // Microsoft products don't always correctly fold lines, they may be // missing a whitespace. So if 'forgiving' is turned on, we will take // those as well. if ($this->options & self::OPTION_FORGIVING) { while(substr($value,-1) === '=') { // Reading the line $this->readLine(); // Grabbing the raw form $value.="\n" . $this->rawLine; } } return $value; } } sabre-vobject-3.5.0/lib/Parser/Parser.php000066400000000000000000000035511264477030300202260ustar00rootroot00000000000000setInput($input); } $this->options = $options; } /** * This method starts the parsing process. * * If the input was not supplied during construction, it's possible to pass * it here instead. * * If either input or options are not supplied, the defaults will be used. * * @param mixed $input * @param int|null $options * @return array */ abstract public function parse($input = null, $options = null); /** * Sets the input data * * @param mixed $input * @return void */ abstract public function setInput($input); } sabre-vobject-3.5.0/lib/Property.php000066400000000000000000000332621264477030300173640ustar00rootroot00000000000000value syntax. * * @param Component $root The root document * @param string $name * @param string|array|null $value * @param array $parameters List of parameters * @param string $group The vcard property group * @return void */ function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) { $this->name = $name; $this->group = $group; $this->root = $root; foreach($parameters as $k=>$v) { $this->add($k, $v); } if (!is_null($value)) { $this->setValue($value); } } /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * @param string|array $value * @return void */ function setValue($value) { $this->value = $value; } /** * Returns the current value. * * This method will always return a singular value. If this was a * multi-value object, some decision will be made first on how to represent * it as a string. * * To get the correct multi-value version, use getParts. * * @return string */ function getValue() { if (is_array($this->value)) { if (count($this->value)==0) { return null; } elseif (count($this->value)===1) { return $this->value[0]; } else { return $this->getRawMimeDirValue($this->value); } } else { return $this->value; } } /** * Sets a multi-valued property. * * @param array $parts * @return void */ function setParts(array $parts) { $this->value = $parts; } /** * Returns a multi-valued property. * * This method always returns an array, if there was only a single value, * it will still be wrapped in an array. * * @return array */ function getParts() { if (is_null($this->value)) { return array(); } elseif (is_array($this->value)) { return $this->value; } else { return array($this->value); } } /** * Adds a new parameter, and returns the new item. * * If a parameter with same name already existed, the values will be * combined. * If nameless parameter is added, we try to guess it's name. * * @param string $name * @param string|null|array $value * @return Node */ function add($name, $value = null) { $noName = false; if ($name === null) { $name = Parameter::guessParameterNameByValue($value); $noName = true; } if (isset($this->parameters[strtoupper($name)])) { $this->parameters[strtoupper($name)]->addValue($value); } else { $param = new Parameter($this->root, $name, $value); $param->noName = $noName; $this->parameters[$param->name] = $param; } } /** * Returns an iterable list of children * * @return array */ function parameters() { return $this->parameters; } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ abstract function getValueType(); /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ abstract function setRawMimeDirValue($val); /** * Returns a raw mime-dir representation of the value. * * @return string */ abstract function getRawMimeDirValue(); /** * Turns the object back into a serialized blob. * * @return string */ function serialize() { $str = $this->name; if ($this->group) $str = $this->group . '.' . $this->name; foreach($this->parameters as $param) { $str.=';' . $param->serialize(); } $str.=':' . $this->getRawMimeDirValue(); $out = ''; while(strlen($str)>0) { if (strlen($str)>75) { $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); } else { $out.=$str . "\r\n"; $str=''; break; } } return $out; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ function getJsonValue() { return $this->getParts(); } /** * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. * * @param array $value * @return void */ function setJsonValue(array $value) { if (count($value)===1) { $this->setValue(reset($value)); } else { $this->setValue($value); } } /** * This method returns an array, with the representation as it should be * encoded in json. This is used to create jCard or jCal documents. * * @return array */ function jsonSerialize() { $parameters = array(); foreach($this->parameters as $parameter) { if ($parameter->name === 'VALUE') { continue; } $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize(); } // In jCard, we need to encode the property-group as a separate 'group' // parameter. if ($this->group) { $parameters['group'] = $this->group; } return array_merge( array( strtolower($this->name), (object)$parameters, strtolower($this->getValueType()), ), $this->getJsonValue() ); } /** * Called when this object is being cast to a string. * * If the property only had a single value, you will get just that. In the * case the property had multiple values, the contents will be escaped and * combined with ,. * * @return string */ function __toString() { return (string)$this->getValue(); } /* ArrayAccess interface {{{ */ /** * Checks if an array element exists * * @param mixed $name * @return bool */ function offsetExists($name) { if (is_int($name)) return parent::offsetExists($name); $name = strtoupper($name); foreach($this->parameters as $parameter) { if ($parameter->name == $name) return true; } return false; } /** * Returns a parameter. * * If the parameter does not exist, null is returned. * * @param string $name * @return Node */ function offsetGet($name) { if (is_int($name)) return parent::offsetGet($name); $name = strtoupper($name); if (!isset($this->parameters[$name])) { return null; } return $this->parameters[$name]; } /** * Creates a new parameter * * @param string $name * @param mixed $value * @return void */ function offsetSet($name, $value) { if (is_int($name)) { parent::offsetSet($name, $value); // @codeCoverageIgnoreStart // This will never be reached, because an exception is always // thrown. return; // @codeCoverageIgnoreEnd } $param = new Parameter($this->root, $name, $value); $this->parameters[$param->name] = $param; } /** * Removes one or more parameters with the specified name * * @param string $name * @return void */ function offsetUnset($name) { if (is_int($name)) { parent::offsetUnset($name); // @codeCoverageIgnoreStart // This will never be reached, because an exception is always // thrown. return; // @codeCoverageIgnoreEnd } unset($this->parameters[strtoupper($name)]); } /* }}} */ /** * This method is automatically called when the object is cloned. * Specifically, this will ensure all child elements are also cloned. * * @return void */ function __clone() { foreach($this->parameters as $key=>$child) { $this->parameters[$key] = clone $child; $this->parameters[$key]->parent = $this; } } /** * Validates the node for correctness. * * The following options are supported: * - Node::REPAIR - If something is broken, and automatic repair may * be attempted. * * An array is returned with warnings. * * Every item in the array has the following properties: * * level - (number between 1 and 3 with severity information) * * message - (human readable message) * * node - (reference to the offending node) * * @param int $options * @return array */ function validate($options = 0) { $warnings = array(); // Checking if our value is UTF-8 if (!StringUtil::isUTF8($this->getRawMimeDirValue())) { $oldValue = $this->getRawMimeDirValue(); $level = 3; if ($options & self::REPAIR) { $newValue = StringUtil::convertToUTF8($oldValue); if (true || StringUtil::isUTF8($newValue)) { $this->setRawMimeDirValue($newValue); $level = 1; } } if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) { $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')'; } else { $message = 'Property is not valid UTF-8! ' . $oldValue; } $warnings[] = array( 'level' => $level, 'message' => $message, 'node' => $this, ); } // Checking if the propertyname does not contain any invalid bytes. if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) { $warnings[] = array( 'level' => 1, 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed', 'node' => $this, ); if ($options & self::REPAIR) { // Uppercasing and converting underscores to dashes. $this->name = strtoupper( str_replace('_', '-', $this->name) ); // Removing every other invalid character $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name); } } if ($encoding = $this->offsetGet('ENCODING')) { if ($this->root->getDocumentType()===Document::VCARD40) { $warnings[] = array( 'level' => 1, 'message' => 'ENCODING parameter is not valid in vCard 4.', 'node' => $this ); } else { $encoding = (string)$encoding; $allowedEncoding = array(); switch($this->root->getDocumentType()) { case Document::ICALENDAR20 : $allowedEncoding = array('8BIT', 'BASE64'); break; case Document::VCARD21 : $allowedEncoding = array('QUOTED-PRINTABLE', 'BASE64', '8BIT'); break; case Document::VCARD30 : $allowedEncoding = array('B'); break; } if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) { $warnings[] = array( 'level' => 1, 'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.', 'node' => $this ); } } } // Validating inner parameters foreach($this->parameters as $param) { $warnings = array_merge($warnings, $param->validate($options)); } return $warnings; } } sabre-vobject-3.5.0/lib/Property/000077500000000000000000000000001264477030300166455ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/Property/Binary.php000066400000000000000000000052651264477030300206120ustar00rootroot00000000000000value = $value[0]; } else { throw new \InvalidArgumentException('The argument must either be a string or an array with only one child'); } } else { $this->value = $value; } } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ public function setRawMimeDirValue($val) { $this->value = base64_decode($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return base64_encode($this->value); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return 'BINARY'; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { return array(base64_encode($this->getValue())); } /** * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. * * @param array $value * @return void */ public function setJsonValue(array $value) { $value = array_map('base64_decode', $value); parent::setJsonValue($value); } } sabre-vobject-3.5.0/lib/Property/Boolean.php000066400000000000000000000025071264477030300207410ustar00rootroot00000000000000setValue($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return $this->value?'TRUE':'FALSE'; } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return 'BOOLEAN'; } } sabre-vobject-3.5.0/lib/Property/FlatText.php000066400000000000000000000023711264477030300211140ustar00rootroot00000000000000setValue($val); } } sabre-vobject-3.5.0/lib/Property/FloatValue.php000066400000000000000000000042421264477030300214220ustar00rootroot00000000000000delimiter, $val); foreach($val as &$item) { $item = (float)$item; } $this->setParts($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return implode( $this->delimiter, $this->getParts() ); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "FLOAT"; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $val = array_map( function($item) { return (float)$item; }, $this->getParts() ); // Special-casing the GEO property. // // See: // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2 if ($this->name==='GEO') { return array($val); } else { return $val; } } } sabre-vobject-3.5.0/lib/Property/ICalendar/000077500000000000000000000000001264477030300204675ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/Property/ICalendar/CalAddress.php000066400000000000000000000025621264477030300232120ustar00rootroot00000000000000getValue(); if (!strpos($input, ':')) { return $input; } list($schema, $everythingElse) = explode(':', $input, 2); return strtolower($schema) . ':' . $everythingElse; } } sabre-vobject-3.5.0/lib/Property/ICalendar/Date.php000066400000000000000000000006111264477030300220530ustar00rootroot00000000000000setDateTimes($parts); } else { parent::setParts($parts); } } /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * Instead of strings, you may also use DateTime here. * * @param string|array|\DateTime $value * @return void */ public function setValue($value) { if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) { $this->setDateTimes($value); } elseif ($value instanceof \DateTime) { $this->setDateTimes(array($value)); } else { parent::setValue($value); } } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ public function setRawMimeDirValue($val) { $this->setValue(explode($this->delimiter, $val)); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return implode($this->delimiter, $this->getParts()); } /** * Returns true if this is a DATE-TIME value, false if it's a DATE. * * @return bool */ public function hasTime() { return strtoupper((string)$this['VALUE']) !== 'DATE'; } /** * Returns true if this is a floating DATE or DATE-TIME. * * Note that DATE is always floating. */ public function isFloating() { return !$this->hasTime() || ( !isset($this['TZID']) && strpos($this->getValue(),'Z')===false ); } /** * Returns a date-time value. * * Note that if this property contained more than 1 date-time, only the * first will be returned. To get an array with multiple values, call * getDateTimes. * * If no timezone information is known, because it's either an all-day * property or floating time, we will use the DateTimeZone argument to * figure out the exact date. * * @param DateTimeZone $timeZone * @return \DateTime */ public function getDateTime(DateTimeZone $timeZone = null) { $dt = $this->getDateTimes($timeZone); if (!$dt) return null; return $dt[0]; } /** * Returns multiple date-time values. * * If no timezone information is known, because it's either an all-day * property or floating time, we will use the DateTimeZone argument to * figure out the exact date. * * @param DateTimeZone $timeZone * @return \DateTime[] */ public function getDateTimes(DateTimeZone $timeZone = null) { // Does the property have a TZID? $tzid = $this['TZID']; if ($tzid) { $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); } $dts = array(); foreach($this->getParts() as $part) { $dts[] = DateTimeParser::parse($part, $timeZone); } return $dts; } /** * Sets the property as a DateTime object. * * @param \DateTime $dt * @param bool isFloating If set to true, timezones will be ignored. * @return void */ public function setDateTime(\DateTime $dt, $isFloating = false) { $this->setDateTimes(array($dt), $isFloating); } /** * Sets the property as multiple date-time objects. * * The first value will be used as a reference for the timezones, and all * the otehr values will be adjusted for that timezone * * @param \DateTime[] $dt * @param bool isFloating If set to true, timezones will be ignored. * @return void */ public function setDateTimes(array $dt, $isFloating = false) { $values = array(); if($this->hasTime()) { $tz = null; $isUtc = false; foreach($dt as $d) { if ($isFloating) { $values[] = $d->format('Ymd\\THis'); continue; } if (is_null($tz)) { $tz = $d->getTimeZone(); $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z')); if (!$isUtc) { $this->offsetSet('TZID', $tz->getName()); } } else { $d->setTimeZone($tz); } if ($isUtc) { $values[] = $d->format('Ymd\\THis\\Z'); } else { $values[] = $d->format('Ymd\\THis'); } } if ($isUtc || $isFloating) { $this->offsetUnset('TZID'); } } else { foreach($dt as $d) { $values[] = $d->format('Ymd'); } $this->offsetUnset('TZID'); } $this->value = $values; } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return $this->hasTime()?'DATE-TIME':'DATE'; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $dts = $this->getDateTimes(); $hasTime = $this->hasTime(); $isFloating = $this->isFloating(); $tz = $dts[0]->getTimeZone(); $isUtc = $isFloating ? false : in_array($tz->getName() , array('UTC', 'GMT', 'Z')); return array_map( function($dt) use ($hasTime, $isUtc) { if ($hasTime) { return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':''); } else { return $dt->format('Y-m-d'); } }, $dts ); } /** * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. * * @param array $value * @return void */ public function setJsonValue(array $value) { // dates and times in jCal have one difference to dates and times in // iCalendar. In jCal date-parts are separated by dashes, and // time-parts are separated by colons. It makes sense to just remove // those. $this->setValue( array_map( function($item) { return strtr($item, array(':'=>'', '-'=>'')); }, $value ) ); } /** * We need to intercept offsetSet, because it may be used to alter the * VALUE from DATE-TIME to DATE or vice-versa. * * @param string $name * @param mixed $value * @return void */ public function offsetSet($name, $value) { parent::offsetSet($name, $value); if (strtoupper($name)!=='VALUE') { return; } // This will ensure that dates are correctly encoded. $this->setDateTimes($this->getDateTimes()); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ public function validate($options = 0) { $messages = parent::validate($options); $valueType = $this->getValueType(); $values = $this->getParts(); try { foreach($values as $value) { switch($valueType) { case 'DATE' : $foo = DateTimeParser::parseDate($value); break; case 'DATE-TIME' : $foo = DateTimeParser::parseDateTime($value); break; } } } catch (\LogicException $e) { $messages[] = array( 'level' => 3, 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, 'node' => $this, ); } return $messages; } } sabre-vobject-3.5.0/lib/Property/ICalendar/Duration.php000066400000000000000000000035761264477030300230000ustar00rootroot00000000000000setValue(explode($this->delimiter, $val)); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return implode($this->delimiter, $this->getParts()); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return 'DURATION'; } /** * Returns a DateInterval representation of the Duration property. * * If the property has more than one value, only the first is returned. * * @return \DateInterval */ public function getDateInterval() { $parts = $this->getParts(); $value = $parts[0]; return DateTimeParser::parseDuration($value); } } sabre-vobject-3.5.0/lib/Property/ICalendar/Period.php000066400000000000000000000055451264477030300224330ustar00rootroot00000000000000setValue(explode($this->delimiter, $val)); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return implode($this->delimiter, $this->getParts()); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "PERIOD"; } /** * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. * * @param array $value * @return void */ public function setJsonValue(array $value) { $value = array_map( function($item) { return strtr(implode('/', $item), array(':' => '', '-' => '')); }, $value ); parent::setJsonValue($value); } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $return = array(); foreach($this->getParts() as $item) { list($start, $end) = explode('/', $item, 2); $start = DateTimeParser::parseDateTime($start); // This is a duration value. if ($end[0]==='P') { $return[] = array( $start->format('Y-m-d\\TH:i:s'), $end ); } else { $end = DateTimeParser::parseDateTime($end); $return[] = array( $start->format('Y-m-d\\TH:i:s'), $end->format('Y-m-d\\TH:i:s'), ); } } return $return; } } sabre-vobject-3.5.0/lib/Property/ICalendar/Recur.php000066400000000000000000000113741264477030300222660ustar00rootroot00000000000000value array that is accessible using * getParts, and may be set using setParts. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class Recur extends Property { /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * @param string|array $value * @return void */ public function setValue($value) { // If we're getting the data from json, we'll be receiving an object if ($value instanceof \StdClass) { $value = (array)$value; } if (is_array($value)) { $newVal = array(); foreach($value as $k=>$v) { if (is_string($v)) { $v = strtoupper($v); // The value had multiple sub-values if (strpos($v,',')!==false) { $v = explode(',', $v); } } else { $v = array_map('strtoupper', $v); } $newVal[strtoupper($k)] = $v; } $this->value = $newVal; } elseif (is_string($value)) { $this->value = self::stringToArray($value); } else { throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); } } /** * Returns the current value. * * This method will always return a singular value. If this was a * multi-value object, some decision will be made first on how to represent * it as a string. * * To get the correct multi-value version, use getParts. * * @return string */ public function getValue() { $out = array(); foreach($this->value as $key=>$value) { $out[] = $key . '=' . (is_array($value)?implode(',', $value):$value); } return strtoupper(implode(';',$out)); } /** * Sets a multi-valued property. * * @param array $parts * @return void */ public function setParts(array $parts) { $this->setValue($parts); } /** * Returns a multi-valued property. * * This method always returns an array, if there was only a single value, * it will still be wrapped in an array. * * @return array */ public function getParts() { return $this->value; } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ public function setRawMimeDirValue($val) { $this->setValue($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return $this->getValue(); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "RECUR"; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $values = array(); foreach($this->getParts() as $k=>$v) { $values[strtolower($k)] = $v; } return array($values); } /** * Parses an RRULE value string, and turns it into a struct-ish array. * * @param string $value * @return array */ static function stringToArray($value) { $value = strtoupper($value); $newValue = array(); foreach(explode(';', $value) as $part) { // Skipping empty parts. if (empty($part)) { continue; } list($partName, $partValue) = explode('=', $part); // The value itself had multiple values.. if (strpos($partValue,',')!==false) { $partValue=explode(',', $partValue); } $newValue[$partName] = $partValue; } return $newValue; } } sabre-vobject-3.5.0/lib/Property/IntegerValue.php000066400000000000000000000027451264477030300217600ustar00rootroot00000000000000setValue((int)$val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return $this->value; } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "INTEGER"; } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { return array((int)$this->getValue()); } } sabre-vobject-3.5.0/lib/Property/Text.php000066400000000000000000000220071264477030300203030ustar00rootroot00000000000000 5, 'ADR' => 7, ); /** * Creates the property. * * You can specify the parameters either in key=>value syntax, in which case * parameters will automatically be created, or you can just pass a list of * Parameter objects. * * @param Component $root The root document * @param string $name * @param string|array|null $value * @param array $parameters List of parameters * @param string $group The vcard property group * @return void */ public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) { // There's two types of multi-valued text properties: // 1. multivalue properties. // 2. structured value properties // // The former is always separated by a comma, the latter by semi-colon. if (in_array($name, $this->structuredValues)) { $this->delimiter = ';'; } parent::__construct($root, $name, $value, $parameters, $group); } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ public function setRawMimeDirValue($val) { $this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); } /** * Sets the value as a quoted-printable encoded string. * * @param string $val * @return void */ public function setQuotedPrintableValue($val) { $val = quoted_printable_decode($val); // Quoted printable only appears in vCard 2.1, and the only character // that may be escaped there is ;. So we are simply splitting on just // that. // // We also don't have to unescape \\, so all we need to look for is a ; // that's not preceeded with a \. $regex = '# (?setValue($matches); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { $val = $this->getParts(); if (isset($this->minimumPropertyValues[$this->name])) { $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); } foreach($val as &$item) { if (!is_array($item)) { $item = array($item); } foreach($item as &$subItem) { $subItem = strtr( $subItem, array( '\\' => '\\\\', ';' => '\;', ',' => '\,', "\n" => '\n', "\r" => "", ) ); } $item = implode(',', $item); } return implode($this->delimiter, $val); } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { // Structured text values should always be returned as a single // array-item. Multi-value text should be returned as multiple items in // the top-array. if (in_array($this->name, $this->structuredValues)) { return array($this->getParts()); } else { return $this->getParts(); } } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "TEXT"; } /** * Turns the object back into a serialized blob. * * @return string */ public function serialize() { // We need to kick in a special type of encoding, if it's a 2.1 vcard. if ($this->root->getDocumentType() !== Document::VCARD21) { return parent::serialize(); } $val = $this->getParts(); if (isset($this->minimumPropertyValues[$this->name])) { $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); } // Imploding multiple parts into a single value, and splitting the // values with ;. if (count($val)>1) { foreach($val as $k=>$v) { $val[$k] = str_replace(';','\;', $v); } $val = implode(';', $val); } else { $val = $val[0]; } $str = $this->name; if ($this->group) $str = $this->group . '.' . $this->name; foreach($this->parameters as $param) { if ($param->getValue() === 'QUOTED-PRINTABLE') { continue; } $str.=';' . $param->serialize(); } // If the resulting value contains a \n, we must encode it as // quoted-printable. if (strpos($val,"\n") !== false) { $str.=';ENCODING=QUOTED-PRINTABLE:'; $lastLine=$str; $out = null; // The PHP built-in quoted-printable-encode does not correctly // encode newlines for us. Specifically, the \r\n sequence must in // vcards be encoded as =0D=OA and we must insert soft-newlines // every 75 bytes. for($ii=0;$ii= 32 && $ord <=126) { $lastLine.=$val[$ii]; } else { $lastLine.='=' . strtoupper(bin2hex($val[$ii])); } if (strlen($lastLine)>=75) { // Soft line break $out.=$lastLine. "=\r\n "; $lastLine = null; } } if (!is_null($lastLine)) $out.= $lastLine . "\r\n"; return $out; } else { $str.=':' . $val; $out = ''; while(strlen($str)>0) { if (strlen($str)>75) { $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); } else { $out.=$str . "\r\n"; $str=''; break; } } return $out; } } /** * Validates the node for correctness. * * The following options are supported: * - Node::REPAIR - If something is broken, and automatic repair may * be attempted. * * An array is returned with warnings. * * Every item in the array has the following properties: * * level - (number between 1 and 3 with severity information) * * message - (human readable message) * * node - (reference to the offending node) * * @param int $options * @return array */ public function validate($options = 0) { $warnings = parent::validate($options); if (isset($this->minimumPropertyValues[$this->name])) { $minimum = $this->minimumPropertyValues[$this->name]; $parts = $this->getParts(); if (count($parts) < $minimum) { $warnings[] = array( 'level' => 1, 'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts), 'node' => $this, ); if ($options & self::REPAIR) { $parts = array_pad($parts, $minimum, ''); $this->setParts($parts); } } } return $warnings; } } sabre-vobject-3.5.0/lib/Property/Time.php000066400000000000000000000037721264477030300202650ustar00rootroot00000000000000getValue()); $timeStr = ''; // Hour if (!is_null($parts['hour'])) { $timeStr.=$parts['hour']; if (!is_null($parts['minute'])) { $timeStr.=':'; } } else { // We know either minute or second _must_ be set, so we insert a // dash for an empty value. $timeStr.='-'; } // Minute if (!is_null($parts['minute'])) { $timeStr.=$parts['minute']; if (!is_null($parts['second'])) { $timeStr.=':'; } } else { if (isset($parts['second'])) { // Dash for empty minute $timeStr.='-'; } } // Second if (!is_null($parts['second'])) { $timeStr.=$parts['second']; } // Timezone if (!is_null($parts['timezone'])) { $timeStr.=$parts['timezone']; } return array($timeStr); } } sabre-vobject-3.5.0/lib/Property/Unknown.php000066400000000000000000000020451264477030300210160ustar00rootroot00000000000000getRawMimeDirValue()); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "UNKNOWN"; } } sabre-vobject-3.5.0/lib/Property/Uri.php000066400000000000000000000047231264477030300201230ustar00rootroot00000000000000name === 'URL') { $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x'; $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $newVal = ''; foreach($matches as $match) { switch($match) { case '\:' : $newVal.=':'; break; default : $newVal.=$match; break; } } $this->value = $newVal; } else { $this->value = $val; } } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { if (is_array($this->value)) { return $this->value[0]; } else { return $this->value; } } } sabre-vobject-3.5.0/lib/Property/UtcOffset.php000066400000000000000000000013601264477030300212600ustar00rootroot00000000000000value = $dt->format('Ymd'); } } sabre-vobject-3.5.0/lib/Property/VCard/DateAndOrTime.php000066400000000000000000000177161264477030300230110ustar00rootroot000000000000001) { throw new \InvalidArgumentException('Only one value allowed'); } if (isset($parts[0]) && $parts[0] instanceof \DateTime) { $this->setDateTime($parts[0]); } else { parent::setParts($parts); } } /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * Instead of strings, you may also use DateTime here. * * @param string|array|\DateTime $value * @return void */ public function setValue($value) { if ($value instanceof \DateTime) { $this->setDateTime($value); } else { parent::setValue($value); } } /** * Sets the property as a DateTime object. * * @param \DateTime $dt * @return void */ public function setDateTime(\DateTime $dt) { $values = array(); $tz = null; $isUtc = false; $tz = $dt->getTimeZone(); $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z')); if ($isUtc) { $value = $dt->format('Ymd\\THis\\Z'); } else { // Calculating the offset. $value = $dt->format('Ymd\\THisO'); } $this->value = $value; } /** * Returns a date-time value. * * Note that if this property contained more than 1 date-time, only the * first will be returned. To get an array with multiple values, call * getDateTimes. * * If no time was specified, we will always use midnight (in the default * timezone) as the time. * * If parts of the date were omitted, such as the year, we will grab the * current values for those. So at the time of writing, if the year was * omitted, we would have filled in 2014. * * @return \DateTime */ public function getDateTime() { $dts = array(); $now = new DateTime(); $tzFormat = $now->getTimezone()->getOffset($now)===0?'\\Z':'O'; $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat)); $value = $this->getValue(); $dateParts = DateTimeParser::parseVCardDateTime($this->getValue()); // This sets all the missing parts to the current date/time. // So if the year was missing for a birthday, we're making it 'this // year'. foreach($dateParts as $k=>$v) { if (is_null($v)) { $dateParts[$k] = $nowParts[$k]; } } return new DateTime("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]"); } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $parts = DateTimeParser::parseVCardDateTime($this->getValue()); $dateStr = ''; // Year if (!is_null($parts['year'])) { $dateStr.=$parts['year']; if (!is_null($parts['month'])) { // If a year and a month is set, we need to insert a separator // dash. $dateStr.='-'; } } else { if (!is_null($parts['month']) || !is_null($parts['date'])) { // Inserting two dashes $dateStr.='--'; } } // Month if (!is_null($parts['month'])) { $dateStr.=$parts['month']; if (isset($parts['date'])) { // If month and date are set, we need the separator dash. $dateStr.='-'; } } else { if (isset($parts['date'])) { // If the month is empty, and a date is set, we need a 'empty // dash' $dateStr.='-'; } } // Date if (!is_null($parts['date'])) { $dateStr.=$parts['date']; } // Early exit if we don't have a time string. if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) { return array($dateStr); } $dateStr.='T'; // Hour if (!is_null($parts['hour'])) { $dateStr.=$parts['hour']; if (!is_null($parts['minute'])) { $dateStr.=':'; } } else { // We know either minute or second _must_ be set, so we insert a // dash for an empty value. $dateStr.='-'; } // Minute if (!is_null($parts['minute'])) { $dateStr.=$parts['minute']; if (!is_null($parts['second'])) { $dateStr.=':'; } } else { if (isset($parts['second'])) { // Dash for empty minute $dateStr.='-'; } } // Second if (!is_null($parts['second'])) { $dateStr.=$parts['second']; } // Timezone if (!is_null($parts['timezone'])) { $dateStr.=$parts['timezone']; } return array($dateStr); } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * @return void */ public function setRawMimeDirValue($val) { $this->setValue($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return implode($this->delimiter, $this->getParts()); } /** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ public function validate($options = 0) { $messages = parent::validate($options); $value = $this->getValue(); try { DateTimeParser::parseVCardDateTime($value); } catch (\InvalidArgumentException $e) { $messages[] = array( 'level' => 3, 'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property', 'node' => $this, ); } return $messages; } } sabre-vobject-3.5.0/lib/Property/VCard/DateTime.php000066400000000000000000000012011264477030300220430ustar00rootroot00000000000000setValue($val); } /** * Returns a raw mime-dir representation of the value. * * @return string */ public function getRawMimeDirValue() { return $this->getValue(); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ public function getValueType() { return "LANGUAGE-TAG"; } } sabre-vobject-3.5.0/lib/Property/VCard/TimeStamp.php000066400000000000000000000027261264477030300222670ustar00rootroot00000000000000getValue()); $dateStr = $parts['year'] . '-' . $parts['month'] . '-' . $parts['date'] . 'T' . $parts['hour'] . ':' . $parts['minute'] . ':' . $parts['second']; // Timezone if (!is_null($parts['timezone'])) { $dateStr.=$parts['timezone']; } return array($dateStr); } } sabre-vobject-3.5.0/lib/Reader.php000066400000000000000000000035711264477030300167420ustar00rootroot00000000000000parse($data, $options); return $result; } /** * Parses a jCard or jCal object, and returns the top component. * * The options argument is a bitfield. Pass any of the OPTIONS constant to * alter the parsers' behaviour. * * You can either a string, a readable stream, or an array for it's input. * Specifying the array is useful if json_decode was already called on the * input. * * @param string|resource|array $data * @param int $options * @return Node */ static public function readJson($data, $options = 0) { $parser = new Parser\Json(); $result = $parser->parse($data, $options); return $result; } } sabre-vobject-3.5.0/lib/Recur/000077500000000000000000000000001264477030300161015ustar00rootroot00000000000000sabre-vobject-3.5.0/lib/Recur/EventIterator.php000066400000000000000000000326451264477030300214170ustar00rootroot00000000000000timeZone)) { $timeZone = new DateTimeZone('UTC'); } $this->timeZone = $timeZone; if (is_array($input)) { $events = $input; } elseif ($input instanceof VEvent) { // Single instance mode. $events = array($input); } else { // Calendar + UID mode. $uid = (string)$uid; if (!$uid) { throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); } if (!isset($input->VEVENT)) { throw new InvalidArgumentException('No events found in this calendar'); } $events = $input->getByUID($uid); } foreach($events as $vevent) { if (!isset($vevent->{'RECURRENCE-ID'})) { $this->masterEvent = $vevent; } else { $this->exceptions[ $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() ] = true; $this->overriddenEvents[] = $vevent; } } if (!$this->masterEvent) { // No base event was found. CalDAV does allow cases where only // overridden instances are stored. // // In this particular case, we're just going to grab the first // event and use that instead. This may not always give the // desired result. if (!count($this->overriddenEvents)) { throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); } $this->masterEvent = array_shift($this->overriddenEvents); } $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); $this->allDay = !$this->masterEvent->DTSTART->hasTime(); if (isset($this->masterEvent->EXDATE)) { foreach($this->masterEvent->EXDATE as $exDate) { foreach($exDate->getDateTimes($this->timeZone) as $dt) { $this->exceptions[$dt->getTimeStamp()] = true; } } } if (isset($this->masterEvent->DTEND)) { $this->eventDuration = $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - $this->startDate->getTimeStamp(); } elseif (isset($this->masterEvent->DURATION)) { $duration = $this->masterEvent->DURATION->getDateInterval(); $end = clone $this->startDate; $end->add($duration); $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); } elseif ($this->allDay) { $this->eventDuration = 3600 * 24; } else { $this->eventDuration = 0; } if (isset($this->masterEvent->RDATE)) { $this->recurIterator = new RDateIterator( $this->masterEvent->RDATE->getParts(), $this->startDate ); } elseif (isset($this->masterEvent->RRULE)) { $this->recurIterator = new RRuleIterator( $this->masterEvent->RRULE->getParts(), $this->startDate ); } else { $this->recurIterator = new RRuleIterator( array( 'FREQ' => 'DAILY', 'COUNT' => 1, ), $this->startDate ); } $this->rewind(); if (!$this->valid()) { throw new NoInstancesException('This recurrence rule does not generate any valid instances'); } } /** * Returns the date for the current position of the iterator. * * @return DateTime */ public function current() { if ($this->currentDate) { return clone $this->currentDate; } } /** * This method returns the start date for the current iteration of the * event. * * @return DateTime */ public function getDtStart() { if ($this->currentDate) { return clone $this->currentDate; } } /** * This method returns the end date for the current iteration of the * event. * * @return DateTime */ public function getDtEnd() { if (!$this->valid()) { return null; } $end = clone $this->currentDate; $end->modify('+' . $this->eventDuration . ' seconds'); return $end; } /** * Returns a VEVENT for the current iterations of the event. * * This VEVENT will have a recurrence id, and it's DTSTART and DTEND * altered. * * @return VEvent */ public function getEventObject() { if ($this->currentOverriddenEvent) { return $this->currentOverriddenEvent; } $event = clone $this->masterEvent; // Ignoring the following block, because PHPUnit's code coverage // ignores most of these lines, and this messes with our stats. // // @codeCoverageIgnoreStart unset( $event->RRULE, $event->EXDATE, $event->RDATE, $event->EXRULE, $event->{'RECURRENCE-ID'} ); // @codeCoverageIgnoreEnd $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); if (isset($event->DTEND)) { $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); } // Including a RECURRENCE-ID to the object, unless this is the first // object. // // The inner recurIterator is always one step ahead, this is why we're // checking for the key being higher than 1. if ($this->recurIterator->key() > 1) { $recurid = clone $event->DTSTART; $recurid->name = 'RECURRENCE-ID'; $event->add($recurid); } return $event; } /** * Returns the current position of the iterator. * * This is for us simply a 0-based index. * * @return int */ public function key() { // The counter is always 1 ahead. return $this->counter - 1; } /** * This is called after next, to see if the iterator is still at a valid * position, or if it's at the end. * * @return bool */ public function valid() { return !!$this->currentDate; } /** * Sets the iterator back to the starting point. */ public function rewind() { $this->recurIterator->rewind(); // re-creating overridden event index. $index = array(); foreach($this->overriddenEvents as $key=>$event) { $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); $index[$stamp] = $key; } krsort($index); $this->counter = 0; $this->overriddenEventsIndex = $index; $this->currentOverriddenEvent = null; $this->nextDate = null; $this->currentDate = clone $this->startDate; $this->next(); } /** * Advances the iterator with one step. * * @return void */ public function next() { $this->currentOverriddenEvent = null; $this->counter++; if ($this->nextDate) { // We had a stored value. $nextDate = $this->nextDate; $this->nextDate = null; } else { // We need to ask rruleparser for the next date. // We need to do this until we find a date that's not in the // exception list. do { if (!$this->recurIterator->valid()) { $nextDate = null; break; } $nextDate = $this->recurIterator->current(); $this->recurIterator->next(); } while(isset($this->exceptions[$nextDate->getTimeStamp()])); } // $nextDate now contains what rrule thinks is the next one, but an // overridden event may cut ahead. if ($this->overriddenEventsIndex) { $offset = end($this->overriddenEventsIndex); $timestamp = key($this->overriddenEventsIndex); if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { // Overridden event comes first. $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; // Putting the rrule next date aside. $this->nextDate = $nextDate; $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); // Ensuring that this item will only be used once. array_pop($this->overriddenEventsIndex); // Exit point! return; } } $this->currentDate = $nextDate; } /** * Quickly jump to a date in the future. * * @param DateTime $dateTime */ public function fastForward(DateTime $dateTime) { while($this->valid() && $this->getDtEnd() < $dateTime ) { $this->next(); } } /** * Returns true if this recurring event never ends. * * @return bool */ public function isInfinite() { return $this->recurIterator->isInfinite(); } /** * RRULE parser * * @var RRuleIterator */ protected $recurIterator; /** * The duration, in seconds, of the master event. * * We use this to calculate the DTEND for subsequent events. */ protected $eventDuration; /** * A reference to the main (master) event. * * @var VEVENT */ protected $masterEvent; /** * List of overridden events. * * @var array */ protected $overriddenEvents = array(); /** * Overridden event index. * * Key is timestamp, value is the index of the item in the $overriddenEvent * property. * * @var array */ protected $overriddenEventsIndex; /** * A list of recurrence-id's that are either part of EXDATE, or are * overridden. * * @var array */ protected $exceptions = array(); /** * Internal event counter * * @var int */ protected $counter; /** * The very start of the iteration process. * * @var DateTime */ protected $startDate; /** * Where we are currently in the iteration process * * @var DateTime */ protected $currentDate; /** * The next date from the rrule parser. * * Sometimes we need to temporary store the next date, because an * overridden event came before. * * @var DateTime */ protected $nextDate; } sabre-vobject-3.5.0/lib/Recur/NoInstancesException.php000066400000000000000000000007041264477030300227160ustar00rootroot00000000000000startDate = $start; $this->parseRDate($rrule); $this->currentDate = clone $this->startDate; } /* Implementation of the Iterator interface {{{ */ public function current() { if (!$this->valid()) return null; return clone $this->currentDate; } /** * Returns the current item number. * * @return int */ public function key() { return $this->counter; } /** * Returns whether the current item is a valid item for the recurrence * iterator. * * @return bool */ public function valid() { return ($this->counter <= count($this->dates)); } /** * Resets the iterator. * * @return void */ public function rewind() { $this->currentDate = clone $this->startDate; $this->counter = 0; } /** * Goes on to the next iteration. * * @return void */ public function next() { $this->counter++; if (!$this->valid()) return; $this->currentDate = DateTimeParser::parse( $this->dates[$this->counter-1] ); } /* End of Iterator implementation }}} */ /** * Returns true if this recurring event never ends. * * @return bool */ public function isInfinite() { return false; } /** * This method allows you to quickly go to the next occurrence after the * specified date. * * @param DateTime $dt * @return void */ public function fastForward(\DateTime $dt) { while($this->valid() && $this->currentDate < $dt ) { $this->next(); } } /** * The reference start date/time for the rrule. * * All calculations are based on this initial date. * * @var DateTime */ protected $startDate; /** * The date of the current iteration. You can get this by calling * ->current(). * * @var DateTime */ protected $currentDate; /** * The current item in the list. * * You can get this number with the key() method. * * @var int */ protected $counter = 0; /* }}} */ /** * This method receives a string from an RRULE property, and populates this * class with all the values. * * @param string|array $rrule * @return void */ protected function parseRDate($rdate) { if (is_string($rdate)) { $rdate = explode(',', $rdate); } $this->dates = $rdate; } } sabre-vobject-3.5.0/lib/Recur/RRuleIterator.php000066400000000000000000000623621264477030300213660ustar00rootroot00000000000000startDate = $start; $this->parseRRule($rrule); $this->currentDate = clone $this->startDate; } /* Implementation of the Iterator interface {{{ */ public function current() { if (!$this->valid()) return null; return clone $this->currentDate; } /** * Returns the current item number * * @return int */ public function key() { return $this->counter; } /** * Returns whether the current item is a valid item for the recurrence * iterator. This will return false if we've gone beyond the UNTIL or COUNT * statements. * * @return bool */ public function valid() { if (!is_null($this->count)) { return $this->counter < $this->count; } return is_null($this->until) || $this->currentDate <= $this->until; } /** * Resets the iterator * * @return void */ public function rewind() { $this->currentDate = clone $this->startDate; $this->counter = 0; } /** * Goes on to the next iteration * * @return void */ public function next() { $previousStamp = $this->currentDate->getTimeStamp(); // Otherwise, we find the next event in the normal RRULE // sequence. switch($this->frequency) { case 'hourly' : $this->nextHourly(); break; case 'daily' : $this->nextDaily(); break; case 'weekly' : $this->nextWeekly(); break; case 'monthly' : $this->nextMonthly(); break; case 'yearly' : $this->nextYearly(); break; } $this->counter++; } /* End of Iterator implementation }}} */ /** * Returns true if this recurring event never ends. * * @return bool */ public function isInfinite() { return !$this->count && !$this->until; } /** * This method allows you to quickly go to the next occurrence after the * specified date. * * @param DateTime $dt * @return void */ public function fastForward(\DateTime $dt) { while($this->valid() && $this->currentDate < $dt ) { $this->next(); } } /** * The reference start date/time for the rrule. * * All calculations are based on this initial date. * * @var DateTime */ protected $startDate; /** * The date of the current iteration. You can get this by calling * ->current(). * * @var DateTime */ protected $currentDate; /** * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly, * yearly. * * @var string */ protected $frequency; /** * The number of recurrences, or 'null' if infinitely recurring. * * @var int */ protected $count; /** * The interval. * * If for example frequency is set to daily, interval = 2 would mean every * 2 days. * * @var int */ protected $interval = 1; /** * The last instance of this recurrence, inclusively * * @var \DateTime|null */ protected $until; /** * Which seconds to recur. * * This is an array of integers (between 0 and 60) * * @var array */ protected $bySecond; /** * Which minutes to recur * * This is an array of integers (between 0 and 59) * * @var array */ protected $byMinute; /** * Which hours to recur * * This is an array of integers (between 0 and 23) * * @var array */ protected $byHour; /** * The current item in the list. * * You can get this number with the key() method. * * @var int */ protected $counter = 0; /** * Which weekdays to recur. * * This is an array of weekdays * * This may also be preceeded by a positive or negative integer. If present, * this indicates the nth occurrence of a specific day within the monthly or * yearly rrule. For instance, -2TU indicates the second-last tuesday of * the month, or year. * * @var array */ protected $byDay; /** * Which days of the month to recur * * This is an array of days of the months (1-31). The value can also be * negative. -5 for instance means the 5th last day of the month. * * @var array */ protected $byMonthDay; /** * Which days of the year to recur. * * This is an array with days of the year (1 to 366). The values can also * be negative. For instance, -1 will always represent the last day of the * year. (December 31st). * * @var array */ protected $byYearDay; /** * Which week numbers to recur. * * This is an array of integers from 1 to 53. The values can also be * negative. -1 will always refer to the last week of the year. * * @var array */ protected $byWeekNo; /** * Which months to recur. * * This is an array of integers from 1 to 12. * * @var array */ protected $byMonth; /** * Which items in an existing st to recur. * * These numbers work together with an existing by* rule. It specifies * exactly which items of the existing by-rule to filter. * * Valid values are 1 to 366 and -1 to -366. As an example, this can be * used to recur the last workday of the month. * * This would be done by setting frequency to 'monthly', byDay to * 'MO,TU,WE,TH,FR' and bySetPos to -1. * * @var array */ protected $bySetPos; /** * When the week starts. * * @var string */ protected $weekStart = 'MO'; /* Functions that advance the iterator {{{ */ /** * Does the processing for advancing the iterator for hourly frequency. * * @return void */ protected function nextHourly() { $this->currentDate->modify('+' . $this->interval . ' hours'); } /** * Does the processing for advancing the iterator for daily frequency. * * @return void */ protected function nextDaily() { if (!$this->byHour && !$this->byDay) { $this->currentDate->modify('+' . $this->interval . ' days'); return; } if (isset($this->byHour)) { $recurrenceHours = $this->getHours(); } if (isset($this->byDay)) { $recurrenceDays = $this->getDays(); } if (isset($this->byMonth)) { $recurrenceMonths = $this->getMonths(); } do { if ($this->byHour) { if ($this->currentDate->format('G') == '23') { // to obey the interval rule $this->currentDate->modify('+' . $this->interval-1 . ' days'); } $this->currentDate->modify('+1 hours'); } else { $this->currentDate->modify('+' . $this->interval . ' days'); } // Current month of the year $currentMonth = $this->currentDate->format('n'); // Current day of the week $currentDay = $this->currentDate->format('w'); // Current hour of the day $currentHour = $this->currentDate->format('G'); } while ( ($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)) || ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) ); } /** * Does the processing for advancing the iterator for weekly frequency. * * @return void */ protected function nextWeekly() { if (!$this->byHour && !$this->byDay) { $this->currentDate->modify('+' . $this->interval . ' weeks'); return; } if ($this->byHour) { $recurrenceHours = $this->getHours(); } if ($this->byDay) { $recurrenceDays = $this->getDays(); } // First day of the week: $firstDay = $this->dayMap[$this->weekStart]; do { if ($this->byHour) { $this->currentDate->modify('+1 hours'); } else { $this->currentDate->modify('+1 days'); } // Current day of the week $currentDay = (int) $this->currentDate->format('w'); // Current hour of the day $currentHour = (int) $this->currentDate->format('G'); // We need to roll over to the next week if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) { $this->currentDate->modify('+' . $this->interval-1 . ' weeks'); // We need to go to the first day of this week, but only if we // are not already on this first day of this week. if($this->currentDate->format('w') != $firstDay) { $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]); } } // We have a match } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours))); } /** * Does the processing for advancing the iterator for monthly frequency. * * @return void */ protected function nextMonthly() { $currentDayOfMonth = $this->currentDate->format('j'); if (!$this->byMonthDay && !$this->byDay) { // If the current day is higher than the 28th, rollover can // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { $this->currentDate->modify('+' . $this->interval . ' months'); } else { $increase = 0; do { $increase++; $tempDate = clone $this->currentDate; $tempDate->modify('+ ' . ($this->interval*$increase) . ' months'); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } return; } while(true) { $occurrences = $this->getMonthlyOccurrences(); foreach($occurrences as $occurrence) { // The first occurrence thats higher than the current // day of the month wins. if ($occurrence > $currentDayOfMonth) { break 2; } } // If we made it all the way here, it means there were no // valid occurrences, and we need to advance to the next // month. // // This line does not currently work in hhvm. Temporary workaround // follows: // $this->currentDate->modify('first day of this month'); $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); // end of workaround $this->currentDate->modify('+ ' . $this->interval . ' months'); // This goes to 0 because we need to start counting at the // beginning. $currentDayOfMonth = 0; } $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence); } /** * Does the processing for advancing the iterator for yearly frequency. * * @return void */ protected function nextYearly() { $currentMonth = $this->currentDate->format('n'); $currentYear = $this->currentDate->format('Y'); $currentDayOfMonth = $this->currentDate->format('j'); // No sub-rules, so we just advance by year if (!$this->byMonth) { // Unless it was a leap day! if ($currentMonth==2 && $currentDayOfMonth==29) { $counter = 0; do { $counter++; // Here we increase the year count by the interval, until // we hit a date that's also in a leap year. // // We could just find the next interval that's dividable by // 4, but that would ignore the rule that there's no leap // year every year that's dividable by a 100, but not by // 400. (1800, 1900, 2100). So we just rely on the datetime // functions instead. $nextDate = clone $this->currentDate; $nextDate->modify('+ ' . ($this->interval*$counter) . ' years'); } while ($nextDate->format('n')!=2); $this->currentDate = $nextDate; return; } // The easiest form $this->currentDate->modify('+' . $this->interval . ' years'); return; } $currentMonth = $this->currentDate->format('n'); $currentYear = $this->currentDate->format('Y'); $currentDayOfMonth = $this->currentDate->format('j'); $advancedToNewMonth = false; // If we got a byDay or getMonthDay filter, we must first expand // further. if ($this->byDay || $this->byMonthDay) { while(true) { $occurrences = $this->getMonthlyOccurrences(); foreach($occurrences as $occurrence) { // The first occurrence that's higher than the current // day of the month wins. // If we advanced to the next month or year, the first // occurrence is always correct. if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { break 2; } } // If we made it here, it means we need to advance to // the next month or year. $currentDayOfMonth = 1; $advancedToNewMonth = true; do { $currentMonth++; if ($currentMonth>12) { $currentYear+=$this->interval; $currentMonth = 1; } } while (!in_array($currentMonth, $this->byMonth)); $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); } // If we made it here, it means we got a valid occurrence $this->currentDate->setDate($currentYear, $currentMonth, $occurrence); return; } else { // These are the 'byMonth' rules, if there are no byDay or // byMonthDay sub-rules. do { $currentMonth++; if ($currentMonth>12) { $currentYear+=$this->interval; $currentMonth = 1; } } while (!in_array($currentMonth, $this->byMonth)); $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); return; } } /* }}} */ /** * This method receives a string from an RRULE property, and populates this * class with all the values. * * @param string|array $rrule * @return void */ protected function parseRRule($rrule) { if (is_string($rrule)) { $rrule = Property\ICalendar\Recur::stringToArray($rrule); } foreach($rrule as $key=>$value) { $key = strtoupper($key); switch($key) { case 'FREQ' : $value = strtolower($value); if (!in_array( $value, array('secondly','minutely','hourly','daily','weekly','monthly','yearly') )) { throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); } $this->frequency = $value; break; case 'UNTIL' : $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone()); // In some cases events are generated with an UNTIL= // parameter before the actual start of the event. // // Not sure why this is happening. We assume that the // intention was that the event only recurs once. // // So we are modifying the parameter so our code doesn't // break. if($this->until < $this->startDate) { $this->until = $this->startDate; } break; case 'INTERVAL' : // No break case 'COUNT' : $val = (int)$value; if ($val < 1) { throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!'); } $key = strtolower($key); $this->$key = $val; break; case 'BYSECOND' : $this->bySecond = (array)$value; break; case 'BYMINUTE' : $this->byMinute = (array)$value; break; case 'BYHOUR' : $this->byHour = (array)$value; break; case 'BYDAY' : $value = (array)$value; foreach($value as $part) { if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part); } } $this->byDay = $value; break; case 'BYMONTHDAY' : $this->byMonthDay = (array)$value; break; case 'BYYEARDAY' : $this->byYearDay = (array)$value; break; case 'BYWEEKNO' : $this->byWeekNo = (array)$value; break; case 'BYMONTH' : $this->byMonth = (array)$value; break; case 'BYSETPOS' : $this->bySetPos = (array)$value; break; case 'WKST' : $this->weekStart = strtoupper($value); break; default: throw new \InvalidArgumentException('Not supported: ' . strtoupper($key)); } } } /** * Mappings between the day number and english day name. * * @var array */ protected $dayNames = array( 0 => 'Sunday', 1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', ); /** * Returns all the occurrences for a monthly frequency with a 'byDay' or * 'byMonthDay' expansion for the current month. * * The returned list is an array of integers with the day of month (1-31). * * @return array */ protected function getMonthlyOccurrences() { $startDate = clone $this->currentDate; $byDayResults = array(); // Our strategy is to simply go through the byDays, advance the date to // that point and add it to the results. if ($this->byDay) foreach($this->byDay as $day) { $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]]; // Dayname will be something like 'wednesday'. Now we need to find // all wednesdays in this month. $dayHits = array(); // workaround for missing 'first day of the month' support in hhvm $checkDate = new \DateTime($startDate->format('Y-m-1')); // workaround modify always advancing the date even if the current day is a $dayName in hhvm if ($checkDate->format('l') !== $dayName) { $checkDate->modify($dayName); } do { $dayHits[] = $checkDate->format('j'); $checkDate->modify('next ' . $dayName); } while ($checkDate->format('n') === $startDate->format('n')); // So now we have 'all wednesdays' for month. It is however // possible that the user only really wanted the 1st, 2nd or last // wednesday. if (strlen($day)>2) { $offset = (int)substr($day,0,-2); if ($offset>0) { // It is possible that the day does not exist, such as a // 5th or 6th wednesday of the month. if (isset($dayHits[$offset-1])) { $byDayResults[] = $dayHits[$offset-1]; } } else { // if it was negative we count from the end of the array // might not exist, fx. -5th tuesday if (isset($dayHits[count($dayHits) + $offset])) { $byDayResults[] = $dayHits[count($dayHits) + $offset]; } } } else { // There was no counter (first, second, last wednesdays), so we // just need to add the all to the list). $byDayResults = array_merge($byDayResults, $dayHits); } } $byMonthDayResults = array(); if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) { // Removing values that are out of range for this month if ($monthDay > $startDate->format('t') || $monthDay < 0-$startDate->format('t')) { continue; } if ($monthDay>0) { $byMonthDayResults[] = $monthDay; } else { // Negative values $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; } } // If there was just byDay or just byMonthDay, they just specify our // (almost) final list. If both were provided, then byDay limits the // list. if ($this->byMonthDay && $this->byDay) { $result = array_intersect($byMonthDayResults, $byDayResults); } elseif ($this->byMonthDay) { $result = $byMonthDayResults; } else { $result = $byDayResults; } $result = array_unique($result); sort($result, SORT_NUMERIC); // The last thing that needs checking is the BYSETPOS. If it's set, it // means only certain items in the set survive the filter. if (!$this->bySetPos) { return $result; } $filteredResult = array(); foreach($this->bySetPos as $setPos) { if ($setPos<0) { $setPos = count($result)+($setPos+1); } if (isset($result[$setPos-1])) { $filteredResult[] = $result[$setPos-1]; } } sort($filteredResult, SORT_NUMERIC); return $filteredResult; } /** * Simple mapping from iCalendar day names to day numbers * * @var array */ protected $dayMap = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6, ); protected function getHours() { $recurrenceHours = array(); foreach($this->byHour as $byHour) { $recurrenceHours[] = $byHour; } return $recurrenceHours; } protected function getDays() { $recurrenceDays = array(); foreach($this->byDay as $byDay) { // The day may be preceeded with a positive (+n) or // negative (-n) integer. However, this does not make // sense in 'weekly' so we ignore it here. $recurrenceDays[] = $this->dayMap[substr($byDay,-2)]; } return $recurrenceDays; } protected function getMonths() { $recurrenceMonths = array(); foreach($this->byMonth as $byMonth) { $recurrenceMonths[] = $byMonth; } return $recurrenceMonths; } } sabre-vobject-3.5.0/lib/RecurrenceIterator.php000066400000000000000000000007231264477030300213430ustar00rootroot00000000000000children() as $component) { if (!$component instanceof VObject\Component) { continue; } // Get all timezones if ($component->name === 'VTIMEZONE') { $this->vtimezones[(string)$component->TZID] = $component; continue; } // Get component UID for recurring Events search if(!$component->UID) { $component->UID = sha1(microtime()) . '-vobjectimport'; } $uid = (string)$component->UID; // Take care of recurring events if (!array_key_exists($uid, $this->objects)) { $this->objects[$uid] = new VCalendar(); } $this->objects[$uid]->add(clone $component); } } /** * Every time getNext() is called, a new object will be parsed, until we * hit the end of the stream. * * When the end is reached, null will be returned. * * @return Sabre\VObject\Component|null */ public function getNext() { if($object=array_shift($this->objects)) { // create our baseobject $object->version = '2.0'; $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN'; $object->calscale = 'GREGORIAN'; // add vtimezone information to obj (if we have it) foreach ($this->vtimezones as $vtimezone) { $object->add($vtimezone); } return $object; } else { return null; } } } sabre-vobject-3.5.0/lib/Splitter/SplitterInterface.php000066400000000000000000000017331264477030300227730ustar00rootroot00000000000000input = $input; $this->parser = new MimeDir($input, $options); } /** * Every time getNext() is called, a new object will be parsed, until we * hit the end of the stream. * * When the end is reached, null will be returned. * * @return Sabre\VObject\Component|null */ public function getNext() { try { $object = $this->parser->parse(); if (!$object instanceof VObject\Component\VCard) { throw new VObject\ParseException('The supplied input contained non-VCARD data.'); } } catch (VObject\EofException $e) { return null; } return $object; } } sabre-vobject-3.5.0/lib/StringUtil.php000066400000000000000000000031561264477030300176430ustar00rootroot00000000000000 'UTC', 31 => 'Africa/Casablanca', // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. // I'm not even kidding.. We handle this special case in the // getTimeZone method. 2 => 'Europe/Lisbon', 1 => 'Europe/London', 4 => 'Europe/Berlin', 6 => 'Europe/Prague', 3 => 'Europe/Paris', 69 => 'Africa/Luanda', // This was a best guess 7 => 'Europe/Athens', 5 => 'Europe/Bucharest', 49 => 'Africa/Cairo', 50 => 'Africa/Harare', 59 => 'Europe/Helsinki', 27 => 'Asia/Jerusalem', 26 => 'Asia/Baghdad', 74 => 'Asia/Kuwait', 51 => 'Europe/Moscow', 56 => 'Africa/Nairobi', 25 => 'Asia/Tehran', 24 => 'Asia/Muscat', // Best guess 54 => 'Asia/Baku', 48 => 'Asia/Kabul', 58 => 'Asia/Yekaterinburg', 47 => 'Asia/Karachi', 23 => 'Asia/Calcutta', 62 => 'Asia/Kathmandu', 46 => 'Asia/Almaty', 71 => 'Asia/Dhaka', 66 => 'Asia/Colombo', 61 => 'Asia/Rangoon', 22 => 'Asia/Bangkok', 64 => 'Asia/Krasnoyarsk', 45 => 'Asia/Shanghai', 63 => 'Asia/Irkutsk', 21 => 'Asia/Singapore', 73 => 'Australia/Perth', 75 => 'Asia/Taipei', 20 => 'Asia/Tokyo', 72 => 'Asia/Seoul', 70 => 'Asia/Yakutsk', 19 => 'Australia/Adelaide', 44 => 'Australia/Darwin', 18 => 'Australia/Brisbane', 76 => 'Australia/Sydney', 43 => 'Pacific/Guam', 42 => 'Australia/Hobart', 68 => 'Asia/Vladivostok', 41 => 'Asia/Magadan', 17 => 'Pacific/Auckland', 40 => 'Pacific/Fiji', 67 => 'Pacific/Tongatapu', 29 => 'Atlantic/Azores', 53 => 'Atlantic/Cape_Verde', 30 => 'America/Noronha', 8 => 'America/Sao_Paulo', // Best guess 32 => 'America/Argentina/Buenos_Aires', 60 => 'America/Godthab', 28 => 'America/St_Johns', 9 => 'America/Halifax', 33 => 'America/Caracas', 65 => 'America/Santiago', 35 => 'America/Bogota', 10 => 'America/New_York', 34 => 'America/Indiana/Indianapolis', 55 => 'America/Guatemala', 11 => 'America/Chicago', 37 => 'America/Mexico_City', 36 => 'America/Edmonton', 38 => 'America/Phoenix', 12 => 'America/Denver', // Best guess 13 => 'America/Los_Angeles', // Best guess 14 => 'America/Anchorage', 15 => 'Pacific/Honolulu', 16 => 'Pacific/Midway', 39 => 'Pacific/Kwajalein', ); /** * This method will try to find out the correct timezone for an iCalendar * date-time value. * * You must pass the contents of the TZID parameter, as well as the full * calendar. * * If the lookup fails, this method will return the default PHP timezone * (as configured using date_default_timezone_set, or the date.timezone ini * setting). * * Alternatively, if $failIfUncertain is set to true, it will throw an * exception if we cannot accurately determine the timezone. * * @param string $tzid * @param Sabre\VObject\Component $vcalendar * @return DateTimeZone */ static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { // First we will just see if the tzid is a support timezone identifier. // // The only exception is if the timezone starts with (. This is to // handle cases where certain microsoft products generate timezone // identifiers that for instance look like: // // (GMT+01.00) Sarajevo/Warsaw/Zagreb // // Since PHP 5.5.10, the first bit will be used as the timezone and // this method will return just GMT+01:00. This is wrong, because it // doesn't take DST into account. if ($tzid[0]!=='(') { // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // // That's why we're checking if we'll be able to successfull instantiate // \DateTimeZone() before doing so. Otherwise we could simply instantiate // and catch the exception. $tzIdentifiers = \DateTimeZone::listIdentifiers(); try { if ( (in_array($tzid, $tzIdentifiers)) || (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) || (in_array($tzid, self::getIdentifiersBC())) ) { return new \DateTimeZone($tzid); } } catch(\Exception $e) { } } self::loadTzMaps(); // Next, we check if the tzid is somewhere in our tzid map. if (isset(self::$map[$tzid])) { return new \DateTimeZone(self::$map[$tzid]); } // Maybe the author was hyper-lazy and just included an offset. We // support it, but we aren't happy about it. if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { // Note that the path in the source will never be taken from PHP 5.5.10 // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it // already gets returned early in this function. Once we drop support // for versions under PHP 5.5.10, this bit can be taken out of the // source. // @codeCoverageIgnoreStart return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0')); // @codeCoverageIgnoreEnd } if ($vcalendar) { // If that didn't work, we will scan VTIMEZONE objects foreach($vcalendar->select('VTIMEZONE') as $vtimezone) { if ((string)$vtimezone->TZID === $tzid) { // Some clients add 'X-LIC-LOCATION' with the olson name. if (isset($vtimezone->{'X-LIC-LOCATION'})) { $lic = (string)$vtimezone->{'X-LIC-LOCATION'}; // Libical generators may specify strings like // "SystemV/EST5EDT". For those we must remove the // SystemV part. if (substr($lic,0,8)==='SystemV/') { $lic = substr($lic,8); } return self::getTimeZone($lic, null, $failIfUncertain); } // Microsoft may add a magic number, which we also have an // answer for. if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); // 2 can mean both Europe/Lisbon and Europe/Sarajevo. if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) { return new \DateTimeZone('Europe/Sarajevo'); } if (isset(self::$microsoftExchangeMap[$cdoId])) { return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); } } } } } if ($failIfUncertain) { throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); } // If we got all the way here, we default to UTC. return new \DateTimeZone(date_default_timezone_get()); } /** * This method will load in all the tz mapping information, if it's not yet * done. */ static public function loadTzMaps() { if (!is_null(self::$map)) return; self::$map = array_merge( include __DIR__ . '/timezonedata/windowszones.php', include __DIR__ . '/timezonedata/lotuszones.php', include __DIR__ . '/timezonedata/exchangezones.php', include __DIR__ . '/timezonedata/php-workaround.php' ); } /** * This method returns an array of timezone identifiers, that are supported * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers() * * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: * - It's not supported by some PHP versions as well as HHVM. * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. * (See timezonedata/php-bc.php and timezonedata php-workaround.php) * * @return array */ static public function getIdentifiersBC() { return include __DIR__ . '/timezonedata/php-bc.php'; } } sabre-vobject-3.5.0/lib/UUIDUtil.php000066400000000000000000000033221264477030300171360ustar00rootroot00000000000000getDocumentType(); if ($inputVersion===$targetVersion) { return clone $input; } if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) { throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); } if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) { throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); } $newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0'; $output = new Component\VCard(array( 'VERSION' => $newVersion, )); foreach($input->children as $property) { $this->convertProperty($input, $output, $property, $targetVersion); } return $output; } /** * Handles conversion of a single property. * * @param Component\VCard $input * @param Component\VCard $output * @param Property $property * @param int $targetVersion * @return void */ protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) { // Skipping these, those are automatically added. if (in_array($property->name, array('VERSION', 'PRODID'))) { return; } $parameters = $property->parameters(); $valueType = null; if (isset($parameters['VALUE'])) { $valueType = $parameters['VALUE']->getValue(); unset($parameters['VALUE']); } if (!$valueType) { $valueType = $property->getValueType(); } $newProperty = $output->createProperty( $property->name, $property->getParts(), array(), // parameters will get added a bit later. $valueType ); if ($targetVersion===Document::VCARD30) { if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) { $newProperty = $this->convertUriToBinary($output, $newProperty); } elseif ($property instanceof Property\VCard\DateAndOrTime) { // In vCard 4, the birth year may be optional. This is not the // case for vCard 3. Apple has a workaround for this that // allows applications that support Apple's extension still // omit birthyears in vCard 3, but applications that do not // support this, will just use a random birthyear. We're // choosing 1604 for the birthyear, because that's what apple // uses. $parts = DateTimeParser::parseVCardDateTime($property->getValue()); if (is_null($parts['year'])) { $newValue = '1604-' . $parts['month'] . '-' . $parts['date']; $newProperty->setValue($newValue); $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; } if ($newProperty->name == 'ANNIVERSARY') { // Microsoft non-standard anniversary $newProperty->name = 'X-ANNIVERSARY'; // We also need to add a new apple property for the same // purpose. This apple property needs a 'label' in the same // group, so we first need to find a groupname that doesn't // exist yet. $x = 1; while($output->select('ITEM' . $x . '.')) { $x++; } $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), array('VALUE' => 'DATE-AND-OR-TIME')); $output->add('ITEM' . $x . '.X-ABLABEL', '_$!!$_'); } } elseif ($property->name === 'KIND') { switch(strtolower($property->getValue())) { case 'org' : // vCard 3.0 does not have an equivalent to KIND:ORG, // but apple has an extension that means the same // thing. $newProperty = $output->createProperty('X-ABSHOWAS','COMPANY'); break; case 'individual' : // Individual is implicit, so we skip it. return; case 'group' : // OS X addressbook property $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP'); break; } } } elseif ($targetVersion===Document::VCARD40) { // These properties were removed in vCard 4.0 if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) { return; } if ($property instanceof Property\Binary) { $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', // then we're stripping the year from the vcard 4 value. $parts = DateTimeParser::parseVCardDateTime($property->getValue()); if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) { $newValue = '--' . $parts['month'] . '-' . $parts['date']; $newProperty->setValue($newValue); } // Regardless if the year matched or not, we do need to strip // X-APPLE-OMIT-YEAR. unset($parameters['X-APPLE-OMIT-YEAR']); } switch($property->name) { case 'X-ABSHOWAS' : if (strtoupper($property->getValue()) === 'COMPANY') { $newProperty = $output->createProperty('KIND','ORG'); } break; case 'X-ADDRESSBOOKSERVER-KIND' : if (strtoupper($property->getValue()) === 'GROUP') { $newProperty = $output->createProperty('KIND','GROUP'); } break; case 'X-ANNIVERSARY' : $newProperty->name = 'ANNIVERSARY'; // If we already have an anniversary property with the same // value, ignore. foreach ($output->select('ANNIVERSARY') as $anniversary) { if ($anniversary->getValue() === $newProperty->getValue()) { return; } } break; case 'X-ABDATE' : // Find out what the label was, if it exists. if (!$property->group) { break; } $label = $input->{$property->group . '.X-ABLABEL'}; // We only support converting anniversaries. if (!$label || $label->getValue()!=='_$!!$_') { break; } // If we already have an anniversary property with the same // value, ignore. foreach ($output->select('ANNIVERSARY') as $anniversary) { if ($anniversary->getValue() === $newProperty->getValue()) { return; } } $newProperty->name = 'ANNIVERSARY'; break; // Apple's per-property label system. case 'X-ABLABEL' : if($newProperty->getValue() === '_$!!$_') { // We can safely remove these, as they are converted to // ANNIVERSARY properties. return; } break; } } // set property group $newProperty->group = $property->group; if ($targetVersion===Document::VCARD40) { $this->convertParameters40($newProperty, $parameters); } else { $this->convertParameters30($newProperty, $parameters); } // Lastly, we need to see if there's a need for a VALUE parameter. // // We can do that by instantating a empty property with that name, and // seeing if the default valueType is identical to the current one. $tempProperty = $output->createProperty($newProperty->name); if ($tempProperty->getValueType() !== $newProperty->getValueType()) { $newProperty['VALUE'] = $newProperty->getValueType(); } $output->add($newProperty); } /** * Converts a BINARY property to a URI property. * * vCard 4.0 no longer supports BINARY properties. * * @param Component\VCard $output * @param Property\Uri $property The input property. * @param $parameters List of parameters that will eventually be added to * the new property. * @return Property\Uri */ protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) { $value = $newProperty->getValue(); $newProperty = $output->createProperty( $newProperty->name, null, // no value array(), // no parameters yet 'URI' // Forcing the BINARY type ); $mimeType = 'application/octet-stream'; // See if we can find a better mimetype. if (isset($parameters['TYPE'])) { $newTypes = array(); foreach($parameters['TYPE']->getParts() as $typePart) { if (in_array( strtoupper($typePart), array('JPEG','PNG','GIF') )) { $mimeType = 'image/' . strtolower($typePart); } else { $newTypes[] = $typePart; } } // If there were any parameters we're not converting to a // mime-type, we need to keep them. if ($newTypes) { $parameters['TYPE']->setParts($newTypes); } else { unset($parameters['TYPE']); } } $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value)); return $newProperty; } /** * Converts a URI property to a BINARY property. * * In vCard 4.0 attachments are encoded as data: uri. Even though these may * be valid in vCard 3.0 as well, we should convert those to BINARY if * possible, to improve compatibility. * * @param Component\VCard $output * @param Property\Uri $property The input property. * @return Property\Binary|null */ protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) { $value = $newProperty->getValue(); // Only converting data: uris if (substr($value, 0, 5)!=='data:') { return $newProperty; } $newProperty = $output->createProperty( $newProperty->name, null, // no value array(), // no parameters yet 'BINARY' ); $mimeType = substr($value, 5, strpos($value, ',')-5); if (strpos($mimeType, ';')) { $mimeType = substr($mimeType,0,strpos($mimeType, ';')); $newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1))); } else { $newProperty->setValue(substr($value, strpos($value,',')+1)); } unset($value); $newProperty['ENCODING'] = 'b'; switch($mimeType) { case 'image/jpeg' : $newProperty['TYPE'] = 'JPEG'; break; case 'image/png' : $newProperty['TYPE'] = 'PNG'; break; case 'image/gif' : $newProperty['TYPE'] = 'GIF'; break; } return $newProperty; } /** * Adds parameters to a new property for vCard 4.0 * * @param Property $newProperty * @param array $parameters * @return void */ protected function convertParameters40(Property $newProperty, array $parameters) { // Adding all parameters. foreach($parameters as $param) { // vCard 2.1 allowed parameters with no name if ($param->noName) $param->noName = false; switch($param->name) { // We need to see if there's any TYPE=PREF, because in vCard 4 // that's now PREF=1. case 'TYPE' : foreach($param->getParts() as $paramPart) { if (strtoupper($paramPart)==='PREF') { $newProperty->add('PREF','1'); } else { $newProperty->add($param->name, $paramPart); } } break; // These no longer exist in vCard 4 case 'ENCODING' : case 'CHARSET' : break; default : $newProperty->add($param->name, $param->getParts()); break; } } } /** * Adds parameters to a new property for vCard 3.0 * * @param Property $newProperty * @param array $parameters * @return void */ protected function convertParameters30(Property $newProperty, array $parameters) { // Adding all parameters. foreach($parameters as $param) { // vCard 2.1 allowed parameters with no name if ($param->noName) $param->noName = false; switch($param->name) { case 'ENCODING' : // This value only existed in vCard 2.1, and should be // removed for anything else. if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') { $newProperty->add($param->name, $param->getParts()); } break; /* * Converting PREF=1 to TYPE=PREF. * * Any other PREF numbers we'll drop. */ case 'PREF' : if ($param->getValue()=='1') { $newProperty->add('TYPE','PREF'); } break; default : $newProperty->add($param->name, $param->getParts()); break; } } } } sabre-vobject-3.5.0/lib/Version.php000066400000000000000000000005541264477030300171630ustar00rootroot00000000000000 'UTC', 'Casablanca, Monrovia' => 'Africa/Casablanca', 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon', 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London', 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague', 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris', 'Prague, Central Europe' => 'Europe/Prague', 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo', 'West Central Africa' => 'Africa/Luanda', // This was a best guess 'Athens, Istanbul, Minsk' => 'Europe/Athens', 'Bucharest' => 'Europe/Bucharest', 'Cairo' => 'Africa/Cairo', 'Harare, Pretoria' => 'Africa/Harare', 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki', 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem', 'Baghdad' => 'Asia/Baghdad', 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait', 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', 'East Africa, Nairobi' => 'Africa/Nairobi', 'Tehran' => 'Asia/Tehran', 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess 'Baku, Tbilisi, Yerevan' => 'Asia/Baku', 'Kabul' => 'Asia/Kabul', 'Ekaterinburg' => 'Asia/Yekaterinburg', 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi', 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta', 'Kathmandu, Nepal' => 'Asia/Kathmandu', 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty', 'Astana, Dhaka' => 'Asia/Dhaka', 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo', 'Rangoon' => 'Asia/Rangoon', 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', 'Krasnoyarsk' => 'Asia/Krasnoyarsk', 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai', 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk', 'Kuala Lumpur, Singapore' => 'Asia/Singapore', 'Perth, Western Australia' => 'Australia/Perth', 'Taipei' => 'Asia/Taipei', 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', 'Seoul, Korea Standard time' => 'Asia/Seoul', 'Yakutsk' => 'Asia/Yakutsk', 'Adelaide, Central Australia' => 'Australia/Adelaide', 'Darwin' => 'Australia/Darwin', 'Brisbane, East Australia' => 'Australia/Brisbane', 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney', 'Guam, Port Moresby' => 'Pacific/Guam', 'Hobart, Tasmania' => 'Australia/Hobart', 'Vladivostok' => 'Asia/Vladivostok', 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan', 'Auckland, Wellington' => 'Pacific/Auckland', 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji', 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu', 'Azores' => 'Atlantic/Azores', 'Cape Verde Is.' => 'Atlantic/Cape_Verde', 'Mid-Atlantic' => 'America/Noronha', 'Brasilia' => 'America/Sao_Paulo', // Best guess 'Buenos Aires' => 'America/Argentina/Buenos_Aires', 'Greenland' => 'America/Godthab', 'Newfoundland' => 'America/St_Johns', 'Atlantic Time (Canada)' => 'America/Halifax', 'Caracas, La Paz' => 'America/Caracas', 'Santiago' => 'America/Santiago', 'Bogota, Lima, Quito' => 'America/Bogota', 'Eastern Time (US & Canada)' => 'America/New_York', 'Indiana (East)' => 'America/Indiana/Indianapolis', 'Central America' => 'America/Guatemala', 'Central Time (US & Canada)' => 'America/Chicago', 'Mexico City, Tegucigalpa' => 'America/Mexico_City', 'Saskatchewan' => 'America/Edmonton', 'Arizona' => 'America/Phoenix', 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess 'Alaska' => 'America/Anchorage', 'Hawaii' => 'Pacific/Honolulu', 'Midway Island, Samoa' => 'Pacific/Midway', 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', ); sabre-vobject-3.5.0/lib/timezonedata/lotuszones.php000066400000000000000000000115201264477030300224420ustar00rootroot00000000000000 'Etc/GMT-12', 'Samoa' => 'Pacific/Apia', 'Hawaiian' => 'Pacific/Honolulu', 'Alaskan' => 'America/Anchorage', 'Pacific' => 'America/Los_Angeles', 'Pacific Standard Time' => 'America/Los_Angeles', 'Mexico Standard Time 2' => 'America/Chihuahua', 'Mountain' => 'America/Denver', // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones. 'US Mountain' => 'America/Phoenix', 'Canada Central' => 'America/Edmonton', 'Central America' => 'America/Guatemala', 'Central' => 'America/Chicago', // 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones. 'Mexico' => 'America/Mexico_City', 'Eastern' => 'America/New_York', 'SA Pacific' => 'America/Bogota', 'US Eastern' => 'America/Indiana/Indianapolis', 'Venezuela' => 'America/Caracas', 'Atlantic' => 'America/Halifax', 'Central Brazilian' => 'America/Manaus', 'Pacific SA' => 'America/Santiago', 'SA Western' => 'America/La_Paz', 'Newfoundland' => 'America/St_Johns', 'Argentina' => 'America/Argentina/Buenos_Aires', 'E. South America' => 'America/Belem', 'Greenland' => 'America/Godthab', 'Montevideo' => 'America/Montevideo', 'SA Eastern' => 'America/Belem', // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones. 'Azores' => 'Atlantic/Azores', 'Cape Verde' => 'Atlantic/Cape_Verde', 'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT. 'Morocco' => 'Africa/Casablanca', 'Central Europe' => 'Europe/Prague', 'Central European' => 'Europe/Sarajevo', 'Romance' => 'Europe/Paris', 'W. Central Africa' => 'Africa/Lagos', // Best guess 'W. Europe' => 'Europe/Amsterdam', 'E. Europe' => 'Europe/Minsk', 'Egypt' => 'Africa/Cairo', 'FLE' => 'Europe/Helsinki', 'GTB' => 'Europe/Athens', 'Israel' => 'Asia/Jerusalem', 'Jordan' => 'Asia/Amman', 'Middle East' => 'Asia/Beirut', 'Namibia' => 'Africa/Windhoek', 'South Africa' => 'Africa/Harare', 'Arab' => 'Asia/Kuwait', 'Arabic' => 'Asia/Baghdad', 'E. Africa' => 'Africa/Nairobi', 'Georgian' => 'Asia/Tbilisi', 'Russian' => 'Europe/Moscow', 'Iran' => 'Asia/Tehran', 'Arabian' => 'Asia/Muscat', 'Armenian' => 'Asia/Yerevan', 'Azerbijan' => 'Asia/Baku', 'Caucasus' => 'Asia/Yerevan', 'Mauritius' => 'Indian/Mauritius', 'Afghanistan' => 'Asia/Kabul', 'Ekaterinburg' => 'Asia/Yekaterinburg', 'Pakistan' => 'Asia/Karachi', 'West Asia' => 'Asia/Tashkent', 'India' => 'Asia/Calcutta', 'Sri Lanka' => 'Asia/Colombo', 'Nepal' => 'Asia/Kathmandu', 'Central Asia' => 'Asia/Dhaka', 'N. Central Asia' => 'Asia/Almaty', 'Myanmar' => 'Asia/Rangoon', 'North Asia' => 'Asia/Krasnoyarsk', 'SE Asia' => 'Asia/Bangkok', 'China' => 'Asia/Shanghai', 'North Asia East' => 'Asia/Irkutsk', 'Singapore' => 'Asia/Singapore', 'Taipei' => 'Asia/Taipei', 'W. Australia' => 'Australia/Perth', 'Korea' => 'Asia/Seoul', 'Tokyo' => 'Asia/Tokyo', 'Yakutsk' => 'Asia/Yakutsk', 'AUS Central' => 'Australia/Darwin', 'Cen. Australia' => 'Australia/Adelaide', 'AUS Eastern' => 'Australia/Sydney', 'E. Australia' => 'Australia/Brisbane', 'Tasmania' => 'Australia/Hobart', 'Vladivostok' => 'Asia/Vladivostok', 'West Pacific' => 'Pacific/Guam', 'Central Pacific' => 'Asia/Magadan', 'Fiji' => 'Pacific/Fiji', 'New Zealand' => 'Pacific/Auckland', 'Tonga' => 'Pacific/Tongatapu', ); sabre-vobject-3.5.0/lib/timezonedata/php-bc.php000066400000000000000000000064301264477030300213720ustar00rootroot00000000000000 'America/Chicago', 'Cuba' => 'America/Havana', 'Egypt' => 'Africa/Cairo', 'Eire' => 'Europe/Dublin', 'EST5EDT' => 'America/New_York', 'Factory' => 'UTC', 'GB-Eire' => 'Europe/London', 'GMT0' => 'UTC', 'Greenwich' => 'UTC', 'Hongkong' => 'Asia/Hong_Kong', 'Iceland' => 'Atlantic/Reykjavik', 'Iran' => 'Asia/Tehran', 'Israel' => 'Asia/Jerusalem', 'Jamaica' => 'America/Jamaica', 'Japan' => 'Asia/Tokyo', 'Kwajalein' => 'Pacific/Kwajalein', 'Libya' => 'Africa/Tripoli', 'MST7MDT' => 'America/Denver', 'Navajo' => 'America/Denver', 'NZ-CHAT' => 'Pacific/Chatham', 'Poland' => 'Europe/Warsaw', 'Portugal' => 'Europe/Lisbon', 'PST8PDT' => 'America/Los_Angeles', 'Singapore' => 'Asia/Singapore', 'Turkey' => 'Europe/Istanbul', 'Universal' => 'UTC', 'W-SU' => 'Europe/Moscow', 'Zulu' => 'UTC', ); sabre-vobject-3.5.0/lib/timezonedata/windowszones.php000066400000000000000000000123761264477030300230000ustar00rootroot00000000000000 'Australia/Darwin', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'Afghanistan Standard Time' => 'Asia/Kabul', 'Alaskan Standard Time' => 'America/Anchorage', 'Arab Standard Time' => 'Asia/Riyadh', 'Arabian Standard Time' => 'Asia/Dubai', 'Arabic Standard Time' => 'Asia/Baghdad', 'Argentina Standard Time' => 'America/Buenos_Aires', 'Atlantic Standard Time' => 'America/Halifax', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Azores Standard Time' => 'Atlantic/Azores', 'Bahia Standard Time' => 'America/Bahia', 'Bangladesh Standard Time' => 'Asia/Dhaka', 'Belarus Standard Time' => 'Europe/Minsk', 'Canada Central Standard Time' => 'America/Regina', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'Caucasus Standard Time' => 'Asia/Yerevan', 'Cen. Australia Standard Time' => 'Australia/Adelaide', 'Central America Standard Time' => 'America/Guatemala', 'Central Asia Standard Time' => 'Asia/Almaty', 'Central Brazilian Standard Time' => 'America/Cuiaba', 'Central Europe Standard Time' => 'Europe/Budapest', 'Central European Standard Time' => 'Europe/Warsaw', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Central Standard Time' => 'America/Chicago', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'China Standard Time' => 'Asia/Shanghai', 'Dateline Standard Time' => 'Etc/GMT+12', 'E. Africa Standard Time' => 'Africa/Nairobi', 'E. Australia Standard Time' => 'Australia/Brisbane', 'E. South America Standard Time' => 'America/Sao_Paulo', 'Eastern Standard Time' => 'America/New_York', 'Egypt Standard Time' => 'Africa/Cairo', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'FLE Standard Time' => 'Europe/Kiev', 'Fiji Standard Time' => 'Pacific/Fiji', 'GMT Standard Time' => 'Europe/London', 'GTB Standard Time' => 'Europe/Bucharest', 'Georgian Standard Time' => 'Asia/Tbilisi', 'Greenland Standard Time' => 'America/Godthab', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'India Standard Time' => 'Asia/Calcutta', 'Iran Standard Time' => 'Asia/Tehran', 'Israel Standard Time' => 'Asia/Jerusalem', 'Jordan Standard Time' => 'Asia/Amman', 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 'Korea Standard Time' => 'Asia/Seoul', 'Libya Standard Time' => 'Africa/Tripoli', 'Line Islands Standard Time' => 'Pacific/Kiritimati', 'Magadan Standard Time' => 'Asia/Magadan', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Middle East Standard Time' => 'Asia/Beirut', 'Montevideo Standard Time' => 'America/Montevideo', 'Morocco Standard Time' => 'Africa/Casablanca', 'Mountain Standard Time' => 'America/Denver', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Myanmar Standard Time' => 'Asia/Rangoon', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'Namibia Standard Time' => 'Africa/Windhoek', 'Nepal Standard Time' => 'Asia/Katmandu', 'New Zealand Standard Time' => 'Pacific/Auckland', 'Newfoundland Standard Time' => 'America/St_Johns', 'North Asia East Standard Time' => 'Asia/Irkutsk', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'Pacific SA Standard Time' => 'America/Santiago', 'Pacific Standard Time' => 'America/Los_Angeles', 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel', 'Pakistan Standard Time' => 'Asia/Karachi', 'Paraguay Standard Time' => 'America/Asuncion', 'Romance Standard Time' => 'Europe/Paris', 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Russia Time Zone 11' => 'Asia/Kamchatka', 'Russia Time Zone 3' => 'Europe/Samara', 'Russian Standard Time' => 'Europe/Moscow', 'SA Eastern Standard Time' => 'America/Cayenne', 'SA Pacific Standard Time' => 'America/Bogota', 'SA Western Standard Time' => 'America/La_Paz', 'SE Asia Standard Time' => 'Asia/Bangkok', 'Samoa Standard Time' => 'Pacific/Apia', 'Singapore Standard Time' => 'Asia/Singapore', 'South Africa Standard Time' => 'Africa/Johannesburg', 'Sri Lanka Standard Time' => 'Asia/Colombo', 'Syria Standard Time' => 'Asia/Damascus', 'Taipei Standard Time' => 'Asia/Taipei', 'Tasmania Standard Time' => 'Australia/Hobart', 'Tokyo Standard Time' => 'Asia/Tokyo', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Turkey Standard Time' => 'Europe/Istanbul', 'US Eastern Standard Time' => 'America/Indianapolis', 'US Mountain Standard Time' => 'America/Phoenix', 'UTC' => 'Etc/GMT', 'UTC+12' => 'Etc/GMT-12', 'UTC-02' => 'Etc/GMT+2', 'UTC-11' => 'Etc/GMT+11', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Venezuela Standard Time' => 'America/Caracas', 'Vladivostok Standard Time' => 'Asia/Vladivostok', 'W. Australia Standard Time' => 'Australia/Perth', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'W. Europe Standard Time' => 'Europe/Berlin', 'West Asia Standard Time' => 'Asia/Tashkent', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Yakutsk Standard Time' => 'Asia/Yakutsk', ); sabre-vobject-3.5.0/tests/000077500000000000000000000000001264477030300154155ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/000077500000000000000000000000001264477030300167515ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/AttachIssueTest.php000066400000000000000000000005351264477030300225420ustar00rootroot00000000000000assertEquals($event, $obj->serialize()); } } sabre-vobject-3.5.0/tests/VObject/CliTest.php000066400000000000000000000340071264477030300210350ustar00rootroot00000000000000cli = 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, <<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, <<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 = <<assertStringStartsWith( $expected, stream_get_contents($this->cli->stdout) ); } public function testConvertJCalFail() { $inputStream = fopen('php://memory','r+'); fwrite($inputStream, <<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, <<cli->stdin = $inputStream; $this->assertEquals( 0, $this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-')) ); rewind($this->cli->stdout); $expected = <<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, <<cli->stdin = $inputStream; $this->assertEquals( 0, $this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-')) ); rewind($this->cli->stdout); $version = Version::VERSION; $expected = <<assertEquals( strtr($expected, array("\n"=>"\r\n")), stream_get_contents($this->cli->stdout) ); } public function testVCard4030() { $inputStream = fopen('php://memory','r+'); fwrite($inputStream, <<cli->stdin = $inputStream; $this->assertEquals( 0, $this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-')) ); $version = Version::VERSION; rewind($this->cli->stdout); $expected = <<assertEquals( strtr($expected, array("\n"=>"\r\n")), stream_get_contents($this->cli->stdout) ); } public function testVCard4021() { $inputStream = fopen('php://memory','r+'); fwrite($inputStream, <<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, <<cli->stdin = $inputStream; $result = $this->cli->main(array('vobject', 'validate', '-')); $this->assertEquals( 0, $result ); } function testValidateFail() { $inputStream = fopen('php://memory','r+'); fwrite($inputStream, <<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, <<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, <<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, <<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, <<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, <<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; } sabre-vobject-3.5.0/tests/VObject/Component/000077500000000000000000000000001264477030300207135ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Component/VAlarmTest.php000066400000000000000000000143721264477030300234550ustar00rootroot00000000000000assertEquals($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 = <<assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00'))); } } sabre-vobject-3.5.0/tests/VObject/Component/VAvailabilityTest.php000066400000000000000000000201111264477030300250170ustar00rootroot00000000000000assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY); } function testRFCxxxSection3_1_availabilityprop_required() { // UID and DTSTAMP are present. $this->assertIsValid(Reader::read( <<assertIsNotValid(Reader::read( <<assertIsNotValid(Reader::read( <<assertIsNotValid(Reader::read( <<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 = <<assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE); } function testRFCxxxSection3_1_availableprop_required() { // UID, DTSTAMP and DTSTART are present. $this->assertIsValid(Reader::read( <<assertIsNotValid(Reader::read( <<assertIsNotValid(Reader::read( <<assertIsNotValid(Reader::read( <<assertIsNotValid(Reader::read( <<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( <<_template( <<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 = <<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 = <<assertValidate( $input, 0, 3, "An iCalendar object must have at least 1 component." ); } function testCalDAVNoComponents() { $input = <<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 = <<assertValidate( $input, VCalendar::PROFILE_CALDAV, 3, "A calendar object on a CalDAV server may only have components with the same UID." ); } function testCalDAVMultiComponent() { $input = <<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 = <<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']); } } } sabre-vobject-3.5.0/tests/VObject/Component/VCardTest.php000066400000000000000000000161411264477030300232660ustar00rootroot00000000000000validate(); $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 = <<assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue()); } function testPreferredWithPref() { $vcard = <<assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue()); } function testPreferredWith40Pref() { $vcard = <<assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue()); } function testPreferredNotFound() { $vcard = <<assertNull($vcard->preferred('EMAIL')); } function testNoUIDCardDAV() { $vcard = <<assertValidate( $vcard, VCARD::PROFILE_CARDDAV, 3, 'vCards on CardDAV servers MUST have a UID property.' ); } function testNoUIDNoCardDAV() { $vcard = <<assertValidate( $vcard, 0, 2, 'Adding a UID to a vCard property is recommended.' ); } function testNoUIDNoCardDAVRepair() { $vcard = <<assertValidate( $vcard, VCARD::REPAIR, 1, 'Adding a UID to a vCard property is recommended.' ); } function testVCard21CardDAV() { $vcard = <<assertValidate( $vcard, VCARD::PROFILE_CARDDAV, 3, 'CardDAV servers are not allowed to accept vCard 2.1.' ); } function testVCard21NoCardDAV() { $vcard = <<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']); } } } sabre-vobject-3.5.0/tests/VObject/Component/VEventTest.php000066400000000000000000000100501264477030300234670ustar00rootroot00000000000000assertEquals($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; } } sabre-vobject-3.5.0/tests/VObject/Component/VFreeBusyTest.php000066400000000000000000000040001264477030300241300ustar00rootroot00000000000000VFREEBUSY; $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 = <<validate(); $messages = array(); foreach($warnings as $warning) { $messages[] = $warning['message']; } $this->assertEquals(array(), $messages); } } sabre-vobject-3.5.0/tests/VObject/Component/VJournalTest.php000066400000000000000000000046771264477030300240420ustar00rootroot00000000000000assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); } public function testValidate() { $input = <<validate(); $messages = array(); foreach($warnings as $warning) { $messages[] = $warning['message']; } $this->assertEquals(array(), $messages); } public function testValidateBroken() { $input = <<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; } } sabre-vobject-3.5.0/tests/VObject/Component/VTimeZoneTest.php000066400000000000000000000016551264477030300241530ustar00rootroot00000000000000validate(); $messages = array(); foreach($warnings as $warning) { $messages[] = $warning['message']; } $this->assertEquals(array(), $messages); } function testGetTimeZone() { $input = <<assertEquals( $tz, $obj->VTIMEZONE->getTimeZone() ); } } sabre-vobject-3.5.0/tests/VObject/Component/VTodoTest.php000066400000000000000000000116361264477030300233260ustar00rootroot00000000000000assertEquals($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 = <<validate(); $messages = array(); foreach($warnings as $warning) { $messages[] = $warning['message']; } $this->assertEquals(array(), $messages); } public function testValidateInvalid() { $input = <<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 = <<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 = <<validate(); $messages = array(); foreach($warnings as $warning) { $messages[] = $warning['message']; } $this->assertEquals(array( "DUE must occur after DTSTART", ), $messages); } } sabre-vobject-3.5.0/tests/VObject/ComponentTest.php000066400000000000000000000321371264477030300222720ustar00rootroot00000000000000createComponent('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', ); } } sabre-vobject-3.5.0/tests/VObject/DateTimeParserTest.php000066400000000000000000000271261264477030300232030ustar00rootroot00000000000000assertEquals('+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' ), ), ); } } sabre-vobject-3.5.0/tests/VObject/DocumentTest.php000066400000000000000000000033051264477030300221010ustar00rootroot00000000000000assertEquals(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 { } sabre-vobject-3.5.0/tests/VObject/ElementListTest.php000066400000000000000000000011711264477030300225470ustar00rootroot00000000000000createComponent('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); } } sabre-vobject-3.5.0/tests/VObject/EmClientTest.php000066400000000000000000000023121264477030300220200ustar00rootroot00000000000000VEVENT->DTSTART->getDateTime(); $this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt); } } sabre-vobject-3.5.0/tests/VObject/EmptyParameterTest.php000066400000000000000000000027251264477030300232670ustar00rootroot00000000000000assertInstanceOf('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 = <<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); } } sabre-vobject-3.5.0/tests/VObject/EmptyValueIssueTest.php000066400000000000000000000012161264477030300234260ustar00rootroot00000000000000assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); } } sabre-vobject-3.5.0/tests/VObject/FreeBusyGeneratorTest.php000066400000000000000000000167241264477030300237270ustar00rootroot00000000000000getResult(); $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() ); } } sabre-vobject-3.5.0/tests/VObject/GoogleColonEscapingTest.php000066400000000000000000000011511264477030300242010ustar00rootroot00000000000000assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue()); } } sabre-vobject-3.5.0/tests/VObject/ICalendar/000077500000000000000000000000001264477030300205735ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/ICalendar/AttachParseTest.php000066400000000000000000000011731264477030300243450ustar00rootroot00000000000000VEVENT->ATTACH; $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop); $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue()); } } sabre-vobject-3.5.0/tests/VObject/ITip/000077500000000000000000000000001264477030300176165ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/ITip/BrokerAttendeeReplyTest.php000066400000000000000000000606301264477030300251060ustar00rootroot00000000000000 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } function testRecurringReply() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } function testRecurringAllDay() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } function testNoChange() { $oldMessage = <<parse($oldMessage, $newMessage, $expected); } function testNoChangeForceSend() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } function testNoRelevantAttendee() { $oldMessage = <<parse($oldMessage, $newMessage, $expected); } /** * In this test, an event exists in an attendees calendar. The event * is recurring, and the attendee deletes 1 instance of the event. * This instance shows up in EXDATE * * This should automatically generate a DECLINED message for that * specific instance. */ function testCreateReplyByException() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => null, 'recipient' => 'mailto:organizer@example.org', 'recipientName' => null, 'message' => <<parse($oldMessage, $newMessage, $expected); } /** * This test is identical to the last, but now we're working with * timezones. * * @depends testCreateReplyByException */ function testCreateReplyByExceptionTz() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => null, 'recipient' => 'mailto:organizer@example.org', 'recipientName' => null, 'message' => <<parse($oldMessage, $newMessage, $expected); } /** * @depends testCreateReplyByException */ function testCreateReplyByExceptionAllDay() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => null, 'recipient' => 'mailto:organizer@example.org', 'recipientName' => null, 'message' => <<parse($oldMessage, $newMessage, $expected); } function testDeclined() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } function testDeclinedCancelledEvent() { $oldMessage = <<parse($oldMessage, $newMessage, $expected); } /** * In this test, a new exception is created by an attendee as well. * * Except in this case, there was already an overridden event, and the * overridden event was marked as cancelled by the attendee. * * For any other attendence status, the new status would have been * declined, but for this, no message should we sent. */ function testDontCreateReplyWhenEventWasDeclined() { $oldMessage = <<parse($oldMessage, $newMessage, $expected); } function testScheduleAgentOnOrganizer() { $oldMessage = <<parse($oldMessage, $newMessage, $expected); } function testAcceptedAllDay() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } /** * This function tests an attendee updating their status to an event where * they don't have the master event of. * * This is possible in cases an organizer created a recurring event, and * invited an attendee for one instance of the event. */ function testReplyNoMasterEvent() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } /** * A party crasher is an attendee that accepted an event, but was not in * any original invite. * * @depends testAccepted */ function testPartyCrasher() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected); } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerDeleteEventTest.php000066400000000000000000000167201264477030300245460ustar00rootroot00000000000000 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testOrganizerDeleteWithDuration() { $oldMessage = << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testAttendeeDeleteWithDtend() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); } function testAttendeeDeleteWithDuration() { $oldMessage = << 'foobar', 'method' => 'REPLY', 'component' => 'VEVENT', 'sender' => 'mailto:one@example.org', 'senderName' => 'One', 'recipient' => 'mailto:strunk@example.org', 'recipientName' => 'Strunk', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); } function testAttendeeDeleteCancelledEvent() { $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); } function testNoCalendar() { $this->parse(null, null, array(), 'mailto:one@example.org'); } function testVTodo() { $oldMessage = <<parse($oldMessage, null, array(), 'mailto:one@example.org'); } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerNewEventTest.php000066400000000000000000000303021264477030300240650ustar00rootroot00000000000000parse($message); } function testVTODO() { $message = <<parse($message); } function testSimpleInvite() { $message = << '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 = <<parse($message, array()); } /** * @expectedException \Sabre\VObject\ITip\ITipException */ function testBrokenEventOrganizerMisMatch() { $message = <<parse($message, array()); } function testRecurrenceInvite() { $message = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:three@example.org', 'recipientName' => 'Three', 'message' => <<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 = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:three@example.org', 'recipientName' => 'Three', 'message' => <<parse($message, $expected); } function testScheduleAgentClient() { $message = <<parse($message, $expected); } /** * @expectedException Sabre\VObject\ITip\ITipException */ function testMultipleUID() { $message = <<parse($message, array()); } /** * @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException * */ function testChangingOrganizers() { $message = <<parse($message, array()); } function testNoOrganizerHasAttendee() { $message = <<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); } } } } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerProcessMessageTest.php000066400000000000000000000042471264477030300252660ustar00rootroot00000000000000process($itip, null, $expected); } function testRequestUpdate() { $itip = <<process($itip, $old, $expected); } function testCancel() { $itip = <<process($itip, $old, $expected); } function testCancelNoExistingEvent() { $itip = <<process($itip, $old, $expected); } function testUnsupportedComponent() { $itip = <<process($itip, $old, $expected); } function testUnsupportedMethod() { $itip = <<process($itip, $old, $expected); } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerProcessReplyTest.php000066400000000000000000000200041264477030300247620ustar00rootroot00000000000000process($itip, $old, $expected); } function testReplyAccept() { $itip = <<process($itip, $old, $expected); } function testReplyRequestStatus() { $itip = <<process($itip, $old, $expected); } function testReplyPartyCrasher() { $itip = <<process($itip, $old, $expected); } function testReplyNewException() { // This is a reply to 1 instance of a recurring event. This should // automatically create an exception. $itip = <<process($itip, $old, $expected); } function testReplyNewExceptionTz() { // This is a reply to 1 instance of a recurring event. This should // automatically create an exception. $itip = <<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 = <<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 = <<process($itip, $old, $expected); } /** * @depends testReplyAccept */ function testReplyAcceptUpdateRSVP() { $itip = <<process($itip, $old, $expected); } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerTester.php000066400000000000000000000060721264477030300227470ustar00rootroot00000000000000parseEvent($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 ); } } sabre-vobject-3.5.0/tests/VObject/ITip/BrokerUpdateEventTest.php000066400000000000000000000475271264477030300245770ustar00rootroot00000000000000 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => true, 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'significantChange' => false, 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:three@example.org', 'recipientName' => 'Three', 'significantChange' => true, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteChangeFromNonSchedulingToSchedulingObject() { $oldMessage = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteChangeFromSchedulingToNonSchedulingObject() { $oldMessage = << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testNoAttendees() { $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testRemoveInstance() { $oldMessage = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'message' => <<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 = << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => true, 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'significantChange' => true, 'message' => << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:three@example.org', 'recipientName' => 'Three', 'significantChange' => true, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteNoChange() { $oldMessage = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => false, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteNoChangeForceSend() { $oldMessage = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => true, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteRemoveAttendees() { $oldMessage = << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => true, 'message' => << 'foobar', 'method' => 'CANCEL', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:two@example.org', 'recipientName' => 'Two', 'significantChange' => true, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } function testInviteChangeExdateOrder() { $oldMessage = << 'foobar', 'method' => 'REQUEST', 'component' => 'VEVENT', 'sender' => 'mailto:strunk@example.org', 'senderName' => 'Strunk', 'recipient' => 'mailto:one@example.org', 'recipientName' => 'One', 'significantChange' => false, 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } } sabre-vobject-3.5.0/tests/VObject/ITip/EvolutionTest.php000066400000000000000000001324551264477030300231650ustar00rootroot00000000000000 '20140813T153116Z-12176-1000-1065-6@johnny-lubuntu', 'method' => 'REQUEST', 'sender' => 'mailto:martin@fruux.com', 'senderName' => null, 'recipient' => 'mailto:dominik@fruux.com', 'recipientName' => null, 'message' => $expectedICS, ) ); $this->parse(null, $ics, $expected, 'mailto:martin@fruux.com'); } /** * This is an event originally from evolution, then parsed by sabredav and * again mangled by iCal. This triggered a few bugs related to email * address scheme casing. */ public function testAttendeeModify() { $old = <<parse($old, $new, array(), 'mailto:a1@example.org'); } } sabre-vobject-3.5.0/tests/VObject/ITip/MessageTest.php000066400000000000000000000012241264477030300225520ustar00rootroot00000000000000assertFalse($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()); } } sabre-vobject-3.5.0/tests/VObject/Issue153Test.php000066400000000000000000000004251264477030300216440ustar00rootroot00000000000000assertEquals('Test Benutzer', (string)$obj->fn); } } sabre-vobject-3.5.0/tests/VObject/Issue26Test.php000066400000000000000000000012321264477030300215600ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); $it = new Recur\EventIterator($vcal, 'bae5d57a98'); iterator_to_array($it); } } sabre-vobject-3.5.0/tests/VObject/Issue36WorkAroundTest.php000066400000000000000000000015641264477030300236050ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Recur\EventIterator', $it); } } sabre-vobject-3.5.0/tests/VObject/Issue40Test.php000066400000000000000000000014411264477030300215560ustar00rootroot00000000000000add('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()); } } sabre-vobject-3.5.0/tests/VObject/Issue64Test.php000066400000000000000000000006751264477030300215740ustar00rootroot00000000000000convert(\Sabre\VObject\Document::VCARD30); $vcard = $vcard->serialize(); $converted = Reader::read($vcard); $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted); } } sabre-vobject-3.5.0/tests/VObject/Issue96Test.php000066400000000000000000000010241264477030300215660ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); $this->assertEquals("http://www.example.org", $vcard->url->getValue()); } } sabre-vobject-3.5.0/tests/VObject/JCalTest.php000066400000000000000000000133011264477030300211310ustar00rootroot00000000000000add('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()); } } sabre-vobject-3.5.0/tests/VObject/JCardTest.php000066400000000000000000000134571264477030300213170ustar00rootroot00000000000000 "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()); } } sabre-vobject-3.5.0/tests/VObject/LineFoldingIssueTest.php000066400000000000000000000006371264477030300235330ustar00rootroot00000000000000assertEquals($event, $obj->serialize()); } } sabre-vobject-3.5.0/tests/VObject/ParameterTest.php000066400000000000000000000066701264477030300222530ustar00rootroot00000000000000assertEquals('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()); } } sabre-vobject-3.5.0/tests/VObject/Parser/000077500000000000000000000000001264477030300202055ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Parser/JsonTest.php000066400000000000000000000271151264477030300224750ustar00rootroot00000000000000 "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 = <<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 = <<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); } } sabre-vobject-3.5.0/tests/VObject/Parser/MimeDirTest.php000066400000000000000000000006231264477030300231050ustar00rootroot00000000000000parse(fopen(__FILE__,'a')); } } sabre-vobject-3.5.0/tests/VObject/Parser/QuotedPrintableTest.php000066400000000000000000000064161264477030300246670ustar00rootroot00000000000000assertInstanceOf('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 = <<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; */ } } sabre-vobject-3.5.0/tests/VObject/Property/000077500000000000000000000000001264477030300205755ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Property/BinaryTest.php000066400000000000000000000005001264477030300233650ustar00rootroot00000000000000add('PHOTO', array('a','b')); } } sabre-vobject-3.5.0/tests/VObject/Property/BooleanTest.php000066400000000000000000000011041264477030300235210ustar00rootroot00000000000000assertTrue($vcard->{'X-AWESOME'}->getValue()); $this->assertFalse($vcard->{'X-SUCKS'}->getValue()); $this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType()); $this->assertEquals($input, $vcard->serialize()); } } sabre-vobject-3.5.0/tests/VObject/Property/CompoundTest.php000066400000000000000000000023251264477030300237340ustar00rootroot00000000000000createProperty('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())); } } sabre-vobject-3.5.0/tests/VObject/Property/FloatTest.php000066400000000000000000000012261264477030300232140ustar00rootroot00000000000000parse($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() ); } } sabre-vobject-3.5.0/tests/VObject/Property/ICalendar/000077500000000000000000000000001264477030300224175ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Property/ICalendar/CalAddressTest.php000066400000000000000000000012251264477030300257750ustar00rootroot00000000000000add('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'), ); } } sabre-vobject-3.5.0/tests/VObject/Property/ICalendar/DateTimeTest.php000066400000000000000000000240651264477030300254730ustar00rootroot00000000000000vcal = 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()); } } sabre-vobject-3.5.0/tests/VObject/Property/ICalendar/DurationTest.php000066400000000000000000000007311264477030300255560ustar00rootroot00000000000000add('VEVENT', array('DURATION' => array('PT1H'))); $this->assertEquals( new \DateInterval('PT1H'), $event->{'DURATION'}->getDateInterval() ); } } sabre-vobject-3.5.0/tests/VObject/Property/ICalendar/RecurTest.php000066400000000000000000000022001264477030300250420ustar00rootroot00000000000000add('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()); } } sabre-vobject-3.5.0/tests/VObject/Property/TextTest.php000066400000000000000000000040501264477030300230710ustar00rootroot00000000000000'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 = <<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())); } } sabre-vobject-3.5.0/tests/VObject/Property/VCard/000077500000000000000000000000001264477030300215745ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Property/VCard/DateAndOrTimeTest.php000066400000000000000000000133651264477030300255750ustar00rootroot00000000000000createProperty('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 = <<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()); } } sabre-vobject-3.5.0/tests/VObject/Property/VCard/LanguageTagTest.php000066400000000000000000000024151264477030300253260ustar00rootroot00000000000000parse($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() ); } } sabre-vobject-3.5.0/tests/VObject/PropertyTest.php000066400000000000000000000300101264477030300221400ustar00rootroot00000000000000createProperty('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']); } } sabre-vobject-3.5.0/tests/VObject/ReaderTest.php000066400000000000000000000346471264477030300215420ustar00rootroot00000000000000assertInstanceOf('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 = <<assertInstanceOf('Sabre\\VObject\\Component', $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children)); } } sabre-vobject-3.5.0/tests/VObject/Recur/000077500000000000000000000000001264477030300200315ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/000077500000000000000000000000001264477030300226245ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php000066400000000000000000000024351264477030300270330ustar00rootroot00000000000000assertInstanceOf('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'); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/BySetPosHangTest.php000066400000000000000000000027471264477030300265150ustar00rootroot00000000000000assertInstanceOf('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); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php000066400000000000000000000043521264477030300301060ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31')); $result = $vcal->serialize(); $output = <<assertEquals($output, str_replace("\r", "", $result)); } function testExpandWithReferenceTimezone() { $input = <<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 = <<assertEquals($output, str_replace("\r", "", $result)); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php000066400000000000000000000023331264477030300301160ustar00rootroot00000000000000VEVENT->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); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php000066400000000000000000000021301264477030300272610ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01')); $result = $vcal->serialize(); $output = <<assertEquals($output, str_replace("\r", "", $result)); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php000066400000000000000000000056761264477030300301330ustar00rootroot00000000000000vcal = 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 } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/Issue48Test.php000066400000000000000000000020471264477030300254440ustar00rootroot00000000000000assertInstanceOf('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); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/Issue50Test.php000066400000000000000000000057731264477030300254460ustar00rootroot00000000000000assertInstanceOf('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); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/MainTest.php000066400000000000000000001130721264477030300250650ustar00rootroot00000000000000createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07')); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $this->assertTrue($it->isInfinite()); } /** * @expectedException InvalidArgumentException * @depends testValues */ function testInvalidFreq() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z'; $ev->UID = 'foo'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); } /** * @expectedException InvalidArgumentException */ function testVCalendarNoUID() { $vcal = new VCalendar(); $it = new EventIterator($vcal); } /** * @expectedException InvalidArgumentException */ function testVCalendarInvalidUID() { $vcal = new VCalendar(); $it = new EventIterator($vcal,'foo'); } /** * @depends testValues */ function testHourly() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07 12:00:00', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,$ev->uid); // Max is to prevent overflow $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07 12:00:00', $tz), new DateTime('2011-10-07 15:00:00', $tz), new DateTime('2011-10-07 18:00:00', $tz), new DateTime('2011-10-07 21:00:00', $tz), new DateTime('2011-10-08 00:00:00', $tz), new DateTime('2011-10-08 03:00:00', $tz), new DateTime('2011-10-08 06:00:00', $tz), new DateTime('2011-10-08 09:00:00', $tz), new DateTime('2011-10-08 12:00:00', $tz), new DateTime('2011-10-08 15:00:00', $tz), new DateTime('2011-10-08 18:00:00', $tz), new DateTime('2011-10-08 21:00:00', $tz), ), $result ); } /** * @depends testValues */ function testDaily() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,$ev->uid); // Max is to prevent overflow $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), new DateTime('2011-10-10', $tz), new DateTime('2011-10-13', $tz), new DateTime('2011-10-16', $tz), new DateTime('2011-10-19', $tz), new DateTime('2011-10-22', $tz), new DateTime('2011-10-25', $tz), ), $result ); } /** * @depends testValues */ function testNoRRULE() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,$ev->uid); // Max is to prevent overflow $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), ), $result ); } /** * @depends testValues */ function testDailyByDayByHour() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-08 06:00:00', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new datetime('2011-10-08 06:00:00', $tz), new datetime('2011-10-08 07:00:00', $tz), new datetime('2011-10-09 06:00:00', $tz), new datetime('2011-10-09 07:00:00', $tz), new datetime('2011-10-15 06:00:00', $tz), new datetime('2011-10-15 07:00:00', $tz), new datetime('2011-10-16 06:00:00', $tz), new datetime('2011-10-16 07:00:00', $tz), new datetime('2011-10-22 06:00:00', $tz), new datetime('2011-10-22 07:00:00', $tz), new datetime('2011-10-23 06:00:00', $tz), new datetime('2011-10-23 07:00:00', $tz), ), $result ); } /** * @depends testValues */ function testDailyByHour() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2012-10-11 12:00:00', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new datetime('2012-10-11 12:00:00', $tz), new datetime('2012-10-11 13:00:00', $tz), new datetime('2012-10-11 14:00:00', $tz), new datetime('2012-10-11 15:00:00', $tz), new datetime('2012-10-13 10:00:00', $tz), new datetime('2012-10-13 11:00:00', $tz), new datetime('2012-10-13 12:00:00', $tz), new datetime('2012-10-13 13:00:00', $tz), new datetime('2012-10-13 14:00:00', $tz), new datetime('2012-10-13 15:00:00', $tz), new datetime('2012-10-15 10:00:00', $tz), new datetime('2012-10-15 11:00:00', $tz), ), $result ); } /** * @depends testValues */ function testDailyByDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), new DateTime('2011-10-11', $tz), new DateTime('2011-10-19', $tz), new DateTime('2011-10-21', $tz), new DateTime('2011-10-25', $tz), new DateTime('2011-11-02', $tz), new DateTime('2011-11-04', $tz), new DateTime('2011-11-08', $tz), new DateTime('2011-11-16', $tz), new DateTime('2011-11-18', $tz), new DateTime('2011-11-22', $tz), new DateTime('2011-11-30', $tz), ), $result ); } /** * @depends testValues */ function testWeekly() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Max is to prevent overflow $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), new DateTime('2011-10-21', $tz), new DateTime('2011-11-04', $tz), new DateTime('2011-11-18', $tz), new DateTime('2011-12-02', $tz), new DateTime('2011-12-16', $tz), new DateTime('2011-12-30', $tz), new DateTime('2012-01-13', $tz), new DateTime('2012-01-27', $tz), new DateTime('2012-02-10', $tz), ), $result ); } /** * @depends testValues */ function testWeeklyByDayByHour() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07 08:00:00', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 15; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07 08:00:00', $tz), new DateTime('2011-10-07 09:00:00', $tz), new DateTime('2011-10-07 10:00:00', $tz), new DateTime('2011-10-18 08:00:00', $tz), new DateTime('2011-10-18 09:00:00', $tz), new DateTime('2011-10-18 10:00:00', $tz), new DateTime('2011-10-19 08:00:00', $tz), new DateTime('2011-10-19 09:00:00', $tz), new DateTime('2011-10-19 10:00:00', $tz), new DateTime('2011-10-21 08:00:00', $tz), new DateTime('2011-10-21 09:00:00', $tz), new DateTime('2011-10-21 10:00:00', $tz), new DateTime('2011-11-01 08:00:00', $tz), new DateTime('2011-11-01 09:00:00', $tz), new DateTime('2011-11-01 10:00:00', $tz), ), $result ); } /** * @depends testValues */ function testWeeklyByDaySpecificHour() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07 18:00:00', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07 18:00:00', $tz), new DateTime('2011-10-18 18:00:00', $tz), new DateTime('2011-10-19 18:00:00', $tz), new DateTime('2011-10-21 18:00:00', $tz), new DateTime('2011-11-01 18:00:00', $tz), new DateTime('2011-11-02 18:00:00', $tz), new DateTime('2011-11-04 18:00:00', $tz), new DateTime('2011-11-15 18:00:00', $tz), new DateTime('2011-11-16 18:00:00', $tz), new DateTime('2011-11-18 18:00:00', $tz), new DateTime('2011-11-29 18:00:00', $tz), new DateTime('2011-11-30 18:00:00', $tz), ), $result ); } /** * @depends testValues */ function testWeeklyByDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // Grabbing the next 12 items $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), new DateTime('2011-10-18', $tz), new DateTime('2011-10-19', $tz), new DateTime('2011-10-21', $tz), new DateTime('2011-11-01', $tz), new DateTime('2011-11-02', $tz), new DateTime('2011-11-04', $tz), new DateTime('2011-11-15', $tz), new DateTime('2011-11-16', $tz), new DateTime('2011-11-18', $tz), new DateTime('2011-11-29', $tz), new DateTime('2011-11-30', $tz), ), $result ); } /** * @depends testValues */ function testMonthly() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-12-05', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 14; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-12-05', $tz), new DateTime('2012-03-05', $tz), new DateTime('2012-06-05', $tz), new DateTime('2012-09-05', $tz), new DateTime('2012-12-05', $tz), ), $result ); } /** * @depends testValues */ function testMonthlyEndOfMonth() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-12-31', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 14; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-12-31', $tz), new DateTime('2012-08-31', $tz), new DateTime('2012-10-31', $tz), new DateTime('2012-12-31', $tz), new DateTime('2013-08-31', $tz), new DateTime('2013-10-31', $tz), new DateTime('2013-12-31', $tz), new DateTime('2014-08-31', $tz), new DateTime('2014-10-31', $tz), new DateTime('2014-12-31', $tz), new DateTime('2015-08-31', $tz), new DateTime('2015-10-31', $tz), ), $result ); } /** * @depends testValues */ function testMonthlyByMonthDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 14; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-01-01', $tz), new DateTime('2011-01-25', $tz), new DateTime('2011-01-31', $tz), new DateTime('2011-06-01', $tz), new DateTime('2011-06-24', $tz), new DateTime('2011-11-01', $tz), new DateTime('2011-11-24', $tz), new DateTime('2012-04-01', $tz), new DateTime('2012-04-24', $tz), ), $result ); } /** * A pretty slow test. Had to be marked as 'medium' for phpunit to not die * after 1 second. Would be good to optimize later. * * @depends testValues * @medium */ function testMonthlyByDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-01-03', $tz), new DateTime('2011-01-05', $tz), new DateTime('2011-01-10', $tz), new DateTime('2011-01-17', $tz), new DateTime('2011-01-18', $tz), new DateTime('2011-01-20', $tz), new DateTime('2011-01-24', $tz), new DateTime('2011-01-31', $tz), new DateTime('2011-03-02', $tz), new DateTime('2011-03-07', $tz), new DateTime('2011-03-14', $tz), new DateTime('2011-03-17', $tz), new DateTime('2011-03-21', $tz), new DateTime('2011-03-22', $tz), new DateTime('2011-03-28', $tz), new DateTime('2011-05-02', $tz), ), $result ); } /** * @depends testValues */ function testMonthlyByDayByMonthDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-08-01', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-08-01', $tz), new DateTime('2012-10-01', $tz), new DateTime('2013-04-01', $tz), new DateTime('2013-07-01', $tz), new DateTime('2014-09-01', $tz), new DateTime('2014-12-01', $tz), new DateTime('2015-06-01', $tz), new DateTime('2016-02-01', $tz), new DateTime('2016-08-01', $tz), new DateTime('2017-05-01', $tz), ), $result ); } /** * @depends testValues */ function testMonthlyByDayBySetPos() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-01-03', $tz), new DateTime('2011-01-31', $tz), new DateTime('2011-02-01', $tz), new DateTime('2011-02-28', $tz), new DateTime('2011-03-01', $tz), new DateTime('2011-03-31', $tz), new DateTime('2011-04-01', $tz), new DateTime('2011-04-29', $tz), new DateTime('2011-05-02', $tz), new DateTime('2011-05-31', $tz), ), $result ); } /** * @depends testValues */ function testYearly() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-01-01', $tz), new DateTime('2014-01-01', $tz), new DateTime('2017-01-01', $tz), new DateTime('2020-01-01', $tz), new DateTime('2023-01-01', $tz), new DateTime('2026-01-01', $tz), new DateTime('2029-01-01', $tz), new DateTime('2032-01-01', $tz), new DateTime('2035-01-01', $tz), new DateTime('2038-01-01', $tz), ), $result ); } /** * @depends testValues */ function testYearlyLeapYear() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=3'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2012-02-29', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2012-02-29', $tz), new DateTime('2016-02-29', $tz), new DateTime('2020-02-29', $tz), ), $result ); } /** * @depends testValues */ function testYearlyByMonth() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-04-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-04-07', $tz), new DateTime('2011-10-07', $tz), new DateTime('2015-04-07', $tz), new DateTime('2015-10-07', $tz), new DateTime('2019-04-07', $tz), new DateTime('2019-10-07', $tz), new DateTime('2023-04-07', $tz), new DateTime('2023-10-07', $tz), ), $result ); } /** * @depends testValues */ function testYearlyByMonthByDay() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-04-04', $tz), new DateTime('2011-04-24', $tz), new DateTime('2011-10-03', $tz), new DateTime('2011-10-30', $tz), new DateTime('2016-04-04', $tz), new DateTime('2016-04-24', $tz), new DateTime('2016-10-03', $tz), new DateTime('2016-10-30', $tz), ), $result ); } /** * @depends testValues */ function testFastForward() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); // The idea is that we're fast-forwarding too far in the future, so // there will be no results left. $it->fastForward(new DateTime('2020-05-05', new DateTimeZone('UTC'))); $max = 20; $result = array(); while($item = $it->current()) { $result[] = $item; $max--; if (!$max) break; $it->next(); } $tz = new DateTimeZone('UTC'); $this->assertEquals(array(), $result); } /** * @depends testValues */ function testComplexExclusions() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RRULE = 'FREQ=YEARLY;COUNT=10'; $dtStart = $vcal->createProperty('DTSTART'); $tz = new DateTimeZone('Canada/Eastern'); $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz)); $exDate1 = $vcal->createProperty('EXDATE'); $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz))); $exDate2 = $vcal->createProperty('EXDATE'); $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz))); $ev->add($dtStart); $ev->add($exDate1); $ev->add($exDate2); $vcal->add($ev); $it = new EventIterator($vcal,(string)$ev->uid); $max = 20; $result = array(); foreach($it as $k=>$item) { $result[] = $item; $max--; if (!$max) break; } $this->assertEquals( array( new DateTime('2011-01-01 13:50:20', $tz), new DateTime('2013-01-01 13:50:20', $tz), new DateTime('2015-01-01 13:50:20', $tz), new DateTime('2017-01-01 13:50:20', $tz), new DateTime('2018-01-01 13:50:20', $tz), new DateTime('2019-01-01 13:50:20', $tz), new DateTime('2020-01-01 13:50:20', $tz), ), $result ); } /** * @depends testValues */ function testOverridenEvent() { $vcal = new VCalendar(); $ev1 = $vcal->createComponent('VEVENT'); $ev1->UID = 'overridden'; $ev1->RRULE = 'FREQ=DAILY;COUNT=10'; $ev1->DTSTART = '20120107T120000Z'; $ev1->SUMMARY = 'baseEvent'; $vcal->add($ev1); // ev2 overrides an event, and puts it on 2pm instead. $ev2 = $vcal->createComponent('VEVENT'); $ev2->UID = 'overridden'; $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; $ev2->DTSTART = '20120110T140000Z'; $ev2->SUMMARY = 'Event 2'; $vcal->add($ev2); // ev3 overrides an event, and puts it 2 days and 2 hours later $ev3 = $vcal->createComponent('VEVENT'); $ev3->UID = 'overridden'; $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; $ev3->DTSTART = '20120115T140000Z'; $ev3->SUMMARY = 'Event 3'; $vcal->add($ev3); $it = new EventIterator($vcal,'overridden'); $dates = array(); $summaries = array(); while($it->valid()) { $dates[] = $it->getDTStart(); $summaries[] = (string)$it->getEventObject()->SUMMARY; $it->next(); } $tz = new DateTimeZone('UTC'); $this->assertEquals(array( new DateTime('2012-01-07 12:00:00',$tz), new DateTime('2012-01-08 12:00:00',$tz), new DateTime('2012-01-09 12:00:00',$tz), new DateTime('2012-01-10 14:00:00',$tz), new DateTime('2012-01-11 12:00:00',$tz), new DateTime('2012-01-12 12:00:00',$tz), new DateTime('2012-01-14 12:00:00',$tz), new DateTime('2012-01-15 12:00:00',$tz), new DateTime('2012-01-15 14:00:00',$tz), new DateTime('2012-01-16 12:00:00',$tz), ), $dates); $this->assertEquals(array( 'baseEvent', 'baseEvent', 'baseEvent', 'Event 2', 'baseEvent', 'baseEvent', 'baseEvent', 'baseEvent', 'Event 3', 'baseEvent', ), $summaries); } /** * @depends testValues */ function testOverridenEvent2() { $vcal = new VCalendar(); $ev1 = $vcal->createComponent('VEVENT'); $ev1->UID = 'overridden'; $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; $ev1->DTSTART = '20120112T120000Z'; $ev1->SUMMARY = 'baseEvent'; $vcal->add($ev1); // ev2 overrides an event, and puts it 6 days earlier instead. $ev2 = $vcal->createComponent('VEVENT'); $ev2->UID = 'overridden'; $ev2->{'RECURRENCE-ID'} = '20120119T120000Z'; $ev2->DTSTART = '20120113T120000Z'; $ev2->SUMMARY = 'Override!'; $vcal->add($ev2); $it = new EventIterator($vcal,'overridden'); $dates = array(); $summaries = array(); while($it->valid()) { $dates[] = $it->getDTStart(); $summaries[] = (string)$it->getEventObject()->SUMMARY; $it->next(); } $tz = new DateTimeZone('UTC'); $this->assertEquals(array( new DateTime('2012-01-12 12:00:00',$tz), new DateTime('2012-01-13 12:00:00',$tz), new DateTime('2012-01-26 12:00:00',$tz), ), $dates); $this->assertEquals(array( 'baseEvent', 'Override!', 'baseEvent', ), $summaries); } /** * @depends testValues */ function testOverridenEventNoValuesExpected() { $vcal = new VCalendar(); $ev1 = $vcal->createComponent('VEVENT'); $ev1->UID = 'overridden'; $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; $ev1->DTSTART = '20120124T120000Z'; $ev1->SUMMARY = 'baseEvent'; $vcal->add($ev1); // ev2 overrides an event, and puts it 6 days earlier instead. $ev2 = $vcal->createComponent('VEVENT'); $ev2->UID = 'overridden'; $ev2->{'RECURRENCE-ID'} = '20120131T120000Z'; $ev2->DTSTART = '20120125T120000Z'; $ev2->SUMMARY = 'Override!'; $vcal->add($ev2); $it = new EventIterator($vcal,'overridden'); $dates = array(); $summaries = array(); // The reported problem was specifically related to the VCALENDAR // expansion. In this parcitular case, we had to forward to the 28th of // january. $it->fastForward(new DateTime('2012-01-28 23:00:00')); // We stop the loop when it hits the 6th of februari. Normally this // iterator would hit 24, 25 (overriden from 31) and 7 feb but because // we 'filter' from the 28th till the 6th, we should get 0 results. while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) { $dates[] = $it->getDTStart(); $summaries[] = (string)$it->getEventObject()->SUMMARY; $it->next(); } $this->assertEquals(array(), $dates); $this->assertEquals(array(), $summaries); } /** * @depends testValues */ function testRDATE() { $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; $ev->RDATE = array( new DateTime('2014-08-07', new DateTimeZone('UTC')), new DateTime('2014-08-08', new DateTimeZone('UTC')), ); $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); $ev->add($dtStart); $vcal->add($ev); $it = new EventIterator($vcal,$ev->uid); // Max is to prevent overflow $max = 12; $result = array(); foreach($it as $item) { $result[] = $item; $max--; if (!$max) break; } $tz = new DateTimeZone('UTC'); $this->assertEquals( array( new DateTime('2011-10-07', $tz), new DateTime('2014-08-07', $tz), new DateTime('2014-08-08', $tz), ), $result ); } /** * @depends testValues * @expectedException \InvalidArgumentException */ function testNoMasterBadUID() { $vcal = new VCalendar(); // ev2 overrides an event, and puts it on 2pm instead. $ev2 = $vcal->createComponent('VEVENT'); $ev2->UID = 'overridden'; $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; $ev2->DTSTART = '20120110T140000Z'; $ev2->SUMMARY = 'Event 2'; $vcal->add($ev2); // ev3 overrides an event, and puts it 2 days and 2 hours later $ev3 = $vcal->createComponent('VEVENT'); $ev3->UID = 'overridden'; $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; $ev3->DTSTART = '20120115T140000Z'; $ev3->SUMMARY = 'Event 3'; $vcal->add($ev3); $it = new EventIterator($vcal,'broken'); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php000066400000000000000000000020441264477030300276300ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01')); $result = $vcal->serialize(); $output = <<assertEquals($output, str_replace("\r","",$result)); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/NoInstancesTest.php000066400000000000000000000015141264477030300264220ustar00rootroot00000000000000assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); $it = new EventIterator($vcal, 'foo'); } } sabre-vobject-3.5.0/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php000066400000000000000000000042001264477030300277620ustar00rootroot00000000000000expand(new DateTime('2014-08-01'), new DateTime('2014-09-01')); $expected = <<serialize(); $newIcs = str_replace("\r\n","\n", $newIcs); $this->assertEquals( $expected, $newIcs ); } function testRemoveFirstEvent() { $input = <<expand(new DateTime('2014-08-01'), new DateTime('2014-08-19')); $expected = <<serialize(); $newIcs = str_replace("\r\n","\n", $newIcs); $this->assertEquals( $expected, $newIcs ); } } sabre-vobject-3.5.0/tests/VObject/Recur/RDateIteratorTest.php000066400000000000000000000025061264477030300241160ustar00rootroot00000000000000assertEquals( $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()); } } sabre-vobject-3.5.0/tests/VObject/Recur/RRuleIteratorTest.php000066400000000000000000000447361264477030300241630ustar00rootroot00000000000000parse( '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 ); } } sabre-vobject-3.5.0/tests/VObject/RecurrenceIterator/000077500000000000000000000000001264477030300225605ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics000066400000000000000000000016741264477030300304470ustar00rootroot00000000000000BEGIN: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 sabre-vobject-3.5.0/tests/VObject/SlashRTest.php000066400000000000000000000006341264477030300215210ustar00rootroot00000000000000add('test', "abc\r\ndef"); $this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize()); } } sabre-vobject-3.5.0/tests/VObject/Splitter/000077500000000000000000000000001264477030300205575ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/VObject/Splitter/ICalendarTest.php000066400000000000000000000146301264477030300237560ustar00rootroot00000000000000version = VObject\Version::VERSION; } function createStream($data) { $stream = fopen('php://memory','r+'); fwrite($stream, $data); rewind($stream); return $stream; } function testICalendarImportValidEvent() { $data = <<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 = <<createStream($data); $objects = new ICalendar($tempFile); } function testICalendarImportEndOfData() { $data = <<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 = <<createStream($data); $objects = new ICalendar($tempFile); } function testICalendarImportMultipleValidEvents() { $event[] = <<createStream($data); $objects = new ICalendar($tempFile); $return = ""; $i = 0; while($object=$objects->getNext()) { $expected = <<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 = <<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 = <<createStream($data); $objects = new ICalendar($tempFile); $return = ""; $i = 0; while($object=$objects->getNext()) { $expected = <<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 = <<createStream($data); $objects = new ICalendar($tempFile); $return = ""; while($object=$objects->getNext()) { $return .= $object->serialize(); } $messages = VObject\Reader::read($return)->validate(); $this->assertEquals(array(), $messages); } } sabre-vobject-3.5.0/tests/VObject/Splitter/VCardTest.php000066400000000000000000000065521264477030300231370ustar00rootroot00000000000000createStream($data); $objects = new VCard($tempFile); $count = 0; while($objects->getNext()) { $count++; } $this->assertEquals(1, $count); } /** * @expectedException Sabre\VObject\ParseException */ function testVCardImportWrongType() { $event[] = <<createStream($data); $splitter = new VCard($tempFile); while($object=$splitter->getNext()) { } } function testVCardImportValidVCardsWithCategories() { $data = <<createStream($data); $splitter = new VCard($tempFile); $count = 0; while($object=$splitter->getNext()) { $count++; } $this->assertEquals(4, $count); } function testVCardImportEndOfData() { $data = <<createStream($data); $objects = new VCard($tempFile); $object=$objects->getNext(); $this->assertNull($objects->getNext()); } /** * @expectedException \Sabre\VObject\ParseException */ function testVCardImportCheckInvalidArgumentException() { $data = <<createStream($data); $objects = new VCard($tempFile); while($objects->getNext()) { } } function testVCardImportMultipleValidVCards() { $data = <<createStream($data); $objects = new VCard($tempFile); $count = 0; while($objects->getNext()) { $count++; } $this->assertEquals(2, $count); } function testImportMultipleSeparatedWithNewLines() { $data = <<createStream($data); $objects = new VCard($tempFile); $count = 0; while ($objects->getNext()) { $count++; } $this->assertEquals(2, $count); } function testVCardImportVCardWithoutUID() { $data = <<createStream($data); $objects = new VCard($tempFile); $count = 0; while($objects->getNext()) { $count++; } $this->assertEquals(1, $count); } } sabre-vobject-3.5.0/tests/VObject/StringUtilTest.php000066400000000000000000000017541264477030300224350ustar00rootroot00000000000000assertEquals(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); } } sabre-vobject-3.5.0/tests/VObject/TestCase.php000066400000000000000000000024411264477030300211760ustar00rootroot00000000000000fail('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 ); } } sabre-vobject-3.5.0/tests/VObject/TimeZoneUtilTest.php000066400000000000000000000206311264477030300227140ustar00rootroot00000000000000assertInstanceOf('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 = <<assertEquals($ex->getName(), $tz->getName()); } function testWetherMicrosoftIsStillInsane() { $vobj = <<assertEquals($ex->getName(), $tz->getName()); } function testUnknownExchangeId() { $vobj = <<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 = <<assertEquals($ex->getName(), $tz->getName()); } function testLjubljanaBug() { $vobj = <<assertEquals($ex->getName(), $tz->getName()); } function testWeirdSystemVLICs() { $vobj = <<assertEquals($ex->getName(), $tz->getName()); } } sabre-vobject-3.5.0/tests/VObject/UUIDUtilTest.php000066400000000000000000000014421264477030300217270ustar00rootroot00000000000000assertTrue( 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() ) ); } } sabre-vobject-3.5.0/tests/VObject/VCard21Test.php000066400000000000000000000013611264477030300214650ustar00rootroot00000000000000serialize($input); $this->assertEquals($input, $output); } function testPropertyPadValueCount() { $input = <<serialize($input); $expected = <<assertEquals($expected, $output); } } sabre-vobject-3.5.0/tests/VObject/VCardConverterTest.php000066400000000000000000000227361264477030300232230ustar00rootroot00000000000000convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); } function testConvert40to40() { $input = <<convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); } function testConvert21to40() { $input = <<convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); } function testConvert30to30() { $input = <<convert(Document::VCARD30); $this->assertVObjEquals( $output, $vcard ); } function testConvert40to30() { $input = <<convert(Document::VCARD30); $this->assertVObjEquals( $output, $vcard ); } function testConvertGroupCard() { $input = <<convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); $input = $output; $output = <<convert(Document::VCARD30); $this->assertVObjEquals( $output, $vcard ); } function testBDAYConversion() { $input = <<convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); $input = $output; $output = <<convert(Document::VCARD30); $this->assertVObjEquals( $output, $vcard ); } /** * @expectedException InvalidArgumentException */ function testUnknownSourceVCardVersion() { $input = <<convert(Document::VCARD40); } /** * @expectedException InvalidArgumentException */ function testUnknownTargetVCardVersion() { $input = <<convert(Document::VCARD21); } function testConvertIndividualCard() { $input = <<convert(Document::VCARD30); $this->assertVObjEquals( $output, $vcard ); $input = $output; $output = <<convert(Document::VCARD40); $this->assertVObjEquals( $output, $vcard ); } function testAnniversary() { $input = <<!$_ 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 = <<!$_ ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210 ITEM2.X-ABLABEL:_$!!$_ ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210 ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210 ITEM3.X-ABLABEL:_$!!$_ 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 = <<assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); $vcard = $vcard->convert(Document::VCARD40); $vcard = $vcard->serialize(); $converted = Reader::read($vcard); $converted->validate(); $version = Version::VERSION; $expected = <<assertEquals($expected, str_replace("\r","", $vcard)); } } sabre-vobject-3.5.0/tests/VObject/VersionTest.php000066400000000000000000000003361264477030300217510ustar00rootroot00000000000000assertEquals(-1, version_compare('2.0.0',$v)); } } sabre-vobject-3.5.0/tests/VObject/issue153.vcf000066400000000000000000000653611264477030300210450ustar00rootroot00000000000000BEGIN: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 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 zbVbk4/OrNpefLsnyyg5UUAf/9k= END:VCARD sabre-vobject-3.5.0/tests/VObject/issue64.vcf000066400000000000000000000653251264477030300207660ustar00rootroot00000000000000BEGIN: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 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 sabre-vobject-3.5.0/tests/bootstrap.php000066400000000000000000000007211264477030300201430ustar00rootroot00000000000000addPsr4('Sabre\\VObject\\',__DIR__ . '/VObject'); if (!defined('SABRE_TEMPDIR')) { define('SABRE_TEMPDIR', __DIR__ . '/temp/'); } if (!file_exists(SABRE_TEMPDIR)) { mkdir(SABRE_TEMPDIR); } sabre-vobject-3.5.0/tests/phpcs/000077500000000000000000000000001264477030300165325ustar00rootroot00000000000000sabre-vobject-3.5.0/tests/phpcs/ruleset.xml000066400000000000000000000036421264477030300207440ustar00rootroot00000000000000 sabre.io codesniffer ruleset sabre-vobject-3.5.0/tests/phpunit.xml000066400000000000000000000010111264477030300176170ustar00rootroot00000000000000 VObject/ ../lib/ ../lib/Sabre/VObject/includes.php