Revert "update composer libs"

This reverts commit e779335d06
This commit is contained in:
Mario 2019-04-25 11:24:09 +02:00
parent 701167bc12
commit f1c0797780
102 changed files with 3992 additions and 3132 deletions

414
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -128,6 +128,6 @@ To do so, locate the ImageMagick `policy.xml` configuration file and add the fol
<!-- ... --> <!-- ... -->
<policy domain="delegate" rights="none" pattern="*" /> <policy domain="delegate" rights="none" pattern="*" />
<policy domain="coder" rights="none" pattern="*" /> <policy domain="coder" rights="none" pattern="*" />
<policy domain="coder" rights="read | write" pattern="{GIF,JPEG,JPG,PNG}" /> <policy domain="coder" rights="read | write" pattern="{GIF,JPEG,PNG}" />
</policymap> </policymap>
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "blueimp-file-upload", "name": "blueimp-file-upload",
"version": "9.30.0", "version": "9.28.0",
"title": "jQuery File Upload", "title": "jQuery File Upload",
"description": "File Upload widget with multiple file selection, drag&amp;drop support, progress bar, validation and preview images.", "description": "File Upload widget with multiple file selection, drag&amp;drop support, progress bar, validation and preview images.",
"keywords": [ "keywords": [

View File

@ -149,9 +149,7 @@
<span class="preview"></span> <span class="preview"></span>
</td> </td>
<td> <td>
{% if (window.innerWidth > 480 || !o.options.loadImageFileTypes.test(file.type)) { %} <p class="name">{%=file.name%}</p>
<p class="name">{%=file.name%}</p>
{% } %}
<strong class="error text-danger"></strong> <strong class="error text-danger"></strong>
</td> </td>
<td> <td>
@ -187,15 +185,13 @@
</span> </span>
</td> </td>
<td> <td>
{% if (window.innerWidth > 480 || !file.thumbnailUrl) { %} <p class="name">
<p class="name"> {% if (file.url) { %}
{% if (file.url) { %} <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
<a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a> {% } else { %}
{% } else { %} <span>{%=file.name%}</span>
<span>{%=file.name%}</span> {% } %}
{% } %} </p>
</p>
{% } %}
{% if (file.error) { %} {% if (file.error) { %}
<div><span class="label label-danger">Error</span> {%=file.error%}</div> <div><span class="label label-danger">Error</span> {%=file.error%}</div>
{% } %} {% } %}

View File

@ -154,9 +154,7 @@
<span class="preview"></span> <span class="preview"></span>
</td> </td>
<td> <td>
{% if (window.innerWidth > 480 || !o.options.loadImageFileTypes.test(file.type)) { %} <p class="name">{%=file.name%}</p>
<p class="name">{%=file.name%}</p>
{% } %}
<strong class="error"></strong> <strong class="error"></strong>
</td> </td>
<td> <td>
@ -186,11 +184,9 @@
</span> </span>
</td> </td>
<td> <td>
{% if (window.innerWidth > 480 || !file.thumbnailUrl) { %} <p class="name">
<p class="name"> <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
<a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a> </p>
</p>
{% } %}
{% if (file.error) { %} {% if (file.error) { %}
<div><span class="error">Error</span> {%=file.error%}</div> <div><span class="error">Error</span> {%=file.error%}</div>
{% } %} {% } %}

View File

@ -1,6 +1,6 @@
{ {
"name": "blueimp-file-upload", "name": "blueimp-file-upload",
"version": "9.30.0", "version": "9.28.0",
"title": "jQuery File Upload", "title": "jQuery File Upload",
"description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.", "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.",
"keywords": [ "keywords": [

View File

@ -919,7 +919,6 @@ return array(
'Zotlabs\\Module\\Cdav' => $baseDir . '/Zotlabs/Module/Cdav.php', 'Zotlabs\\Module\\Cdav' => $baseDir . '/Zotlabs/Module/Cdav.php',
'Zotlabs\\Module\\Changeaddr' => $baseDir . '/Zotlabs/Module/Changeaddr.php', 'Zotlabs\\Module\\Changeaddr' => $baseDir . '/Zotlabs/Module/Changeaddr.php',
'Zotlabs\\Module\\Channel' => $baseDir . '/Zotlabs/Module/Channel.php', 'Zotlabs\\Module\\Channel' => $baseDir . '/Zotlabs/Module/Channel.php',
'Zotlabs\\Module\\Channel_calendar' => $baseDir . '/Zotlabs/Module/Channel_calendar.php',
'Zotlabs\\Module\\Chanview' => $baseDir . '/Zotlabs/Module/Chanview.php', 'Zotlabs\\Module\\Chanview' => $baseDir . '/Zotlabs/Module/Chanview.php',
'Zotlabs\\Module\\Chat' => $baseDir . '/Zotlabs/Module/Chat.php', 'Zotlabs\\Module\\Chat' => $baseDir . '/Zotlabs/Module/Chat.php',
'Zotlabs\\Module\\Chatsvc' => $baseDir . '/Zotlabs/Module/Chatsvc.php', 'Zotlabs\\Module\\Chatsvc' => $baseDir . '/Zotlabs/Module/Chatsvc.php',

View File

@ -1087,7 +1087,6 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Cdav' => __DIR__ . '/../..' . '/Zotlabs/Module/Cdav.php', 'Zotlabs\\Module\\Cdav' => __DIR__ . '/../..' . '/Zotlabs/Module/Cdav.php',
'Zotlabs\\Module\\Changeaddr' => __DIR__ . '/../..' . '/Zotlabs/Module/Changeaddr.php', 'Zotlabs\\Module\\Changeaddr' => __DIR__ . '/../..' . '/Zotlabs/Module/Changeaddr.php',
'Zotlabs\\Module\\Channel' => __DIR__ . '/../..' . '/Zotlabs/Module/Channel.php', 'Zotlabs\\Module\\Channel' => __DIR__ . '/../..' . '/Zotlabs/Module/Channel.php',
'Zotlabs\\Module\\Channel_calendar' => __DIR__ . '/../..' . '/Zotlabs/Module/Channel_calendar.php',
'Zotlabs\\Module\\Chanview' => __DIR__ . '/../..' . '/Zotlabs/Module/Chanview.php', 'Zotlabs\\Module\\Chanview' => __DIR__ . '/../..' . '/Zotlabs/Module/Chanview.php',
'Zotlabs\\Module\\Chat' => __DIR__ . '/../..' . '/Zotlabs/Module/Chat.php', 'Zotlabs\\Module\\Chat' => __DIR__ . '/../..' . '/Zotlabs/Module/Chat.php',
'Zotlabs\\Module\\Chatsvc' => __DIR__ . '/../..' . '/Zotlabs/Module/Chatsvc.php', 'Zotlabs\\Module\\Chatsvc' => __DIR__ . '/../..' . '/Zotlabs/Module/Chatsvc.php',

View File

@ -1,20 +1,20 @@
[ [
{ {
"name": "blueimp/jquery-file-upload", "name": "blueimp/jquery-file-upload",
"version": "v9.30.0", "version": "v9.28.0",
"version_normalized": "9.30.0.0", "version_normalized": "9.28.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/vkhramtsov/jQuery-File-Upload.git", "url": "https://github.com/vkhramtsov/jQuery-File-Upload.git",
"reference": "1fceec556879403e5c1ae32a7c448aa12b8c3558" "reference": "ff5accfe2e5c4a522777faa980a90cf86a636d1d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/vkhramtsov/jQuery-File-Upload/zipball/1fceec556879403e5c1ae32a7c448aa12b8c3558", "url": "https://api.github.com/repos/vkhramtsov/jQuery-File-Upload/zipball/ff5accfe2e5c4a522777faa980a90cf86a636d1d",
"reference": "1fceec556879403e5c1ae32a7c448aa12b8c3558", "reference": "ff5accfe2e5c4a522777faa980a90cf86a636d1d",
"shasum": "" "shasum": ""
}, },
"time": "2019-04-22T09:21:57+00:00", "time": "2018-11-13T05:41:39+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -886,17 +886,17 @@
}, },
{ {
"name": "sabre/vobject", "name": "sabre/vobject",
"version": "4.2.0", "version": "4.1.6",
"version_normalized": "4.2.0.0", "version_normalized": "4.1.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sabre-io/vobject.git", "url": "https://github.com/sabre-io/vobject.git",
"reference": "bd500019764e434ff65872d426f523e7882a0739" "reference": "122cacbdea2c6133ac04db86ec05854beef75adf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sabre-io/vobject/zipball/bd500019764e434ff65872d426f523e7882a0739", "url": "https://api.github.com/repos/sabre-io/vobject/zipball/122cacbdea2c6133ac04db86ec05854beef75adf",
"reference": "bd500019764e434ff65872d426f523e7882a0739", "reference": "122cacbdea2c6133ac04db86ec05854beef75adf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -905,12 +905,13 @@
"sabre/xml": ">=1.5 <3.0" "sabre/xml": ">=1.5 <3.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "> 4.8.35, <6.0.0" "phpunit/phpunit": "> 4.8.35, <6.0.0",
"sabre/cs": "^1.0.0"
}, },
"suggest": { "suggest": {
"hoa/bench": "If you would like to run the benchmark scripts" "hoa/bench": "If you would like to run the benchmark scripts"
}, },
"time": "2019-02-19T13:05:37+00:00", "time": "2018-04-20T07:22:50+00:00",
"bin": [ "bin": [
"bin/vobject", "bin/vobject",
"bin/generate_vcards" "bin/generate_vcards"
@ -984,17 +985,17 @@
}, },
{ {
"name": "sabre/xml", "name": "sabre/xml",
"version": "1.5.1", "version": "1.5.0",
"version_normalized": "1.5.1.0", "version_normalized": "1.5.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sabre-io/xml.git", "url": "https://github.com/sabre-io/xml.git",
"reference": "a367665f1df614c3b8fefc30a54de7cd295e444e" "reference": "59b20e5bbace9912607481634f97d05a776ffca7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sabre-io/xml/zipball/a367665f1df614c3b8fefc30a54de7cd295e444e", "url": "https://api.github.com/repos/sabre-io/xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7",
"reference": "a367665f1df614c3b8fefc30a54de7cd295e444e", "reference": "59b20e5bbace9912607481634f97d05a776ffca7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1006,10 +1007,10 @@
"sabre/uri": ">=1.0,<3.0.0" "sabre/uri": ">=1.0,<3.0.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.8|~5.7", "phpunit/phpunit": "*",
"sabre/cs": "~1.0.0" "sabre/cs": "~1.0.0"
}, },
"time": "2019-01-09T13:51:57+00:00", "time": "2016-10-09T22:57:52+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -1181,17 +1182,17 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.11.0", "version": "v1.10.0",
"version_normalized": "1.11.0.0", "version_normalized": "1.10.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "82ebae02209c21113908c229e9883c419720738a" "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
"reference": "82ebae02209c21113908c229e9883c419720738a", "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1200,11 +1201,11 @@
"suggest": { "suggest": {
"ext-ctype": "For best performance" "ext-ctype": "For best performance"
}, },
"time": "2019-02-06T07:57:58+00:00", "time": "2018-08-06T14:22:27+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.11-dev" "dev-master": "1.9-dev"
} }
}, },
"installation-source": "dist", "installation-source": "dist",

View File

@ -16,7 +16,6 @@ bin/hoa
# Development stuff # Development stuff
testdata/ testdata/
.php_cs.cache
# OS X # OS X
.DS_Store .DS_Store

View File

@ -1,39 +1,20 @@
language: php language: php
sudo: required
php: php:
- 5.5 - 5.5
- 5.6 - 5.6
- 7.0 - 7.0
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3
env: sudo: false
global:
- RUN_PHPSTAN="FALSE"
matrix: script:
include: - ./bin/phpunit --configuration tests/phpunit.xml
- name: 'PHPStan' - ./bin/sabre-cs-fixer fix . --dry-run --diff
php: 7.2
env: RUN_PHPSTAN="TRUE"
fast_finish: true
allow_failures:
- php: 5.5
install:
- if [ $RUN_PHPSTAN == "TRUE" ]; then wget https://github.com/phpstan/phpstan/releases/download/0.10.3/phpstan.phar; fi
before_script: before_script:
- composer install - composer install
script:
- if [ $RUN_PHPSTAN == "FALSE" ]; then ./bin/phpunit --configuration tests/phpunit.xml --coverage-clover=coverage.xml; fi
- if [ $RUN_PHPSTAN == "TRUE" ]; then php phpstan.phar analyse -c phpstan.neon lib; fi
after_success:
- bash <(curl -s https://codecov.io/bash)
cache: cache:
directories: directories:
- $HOME/.composer/cache - $HOME/.composer/cache

View File

@ -1,19 +1,6 @@
ChangeLog ChangeLog
========= =========
4.2.0 (2019-02-19)
------------------
* #432: DTSTAMP must be specified in UTC
* #435: ORGANIZER e-mail address are case-insensitive
* #441: Repairing BASE64 encoded vCard version 3
4.2.0-alpha1 (2018-09-26)
-------------------------
* #412: Broker: add timezone to CANCEL messages
* #424: Support php7.3
4.1.6 (2018-04-20) 4.1.6 (2018-04-20)
------------------ ------------------
@ -42,7 +29,7 @@ ChangeLog
* #363: Repair script and de-duplicate properties that are only allowed once, * #363: Repair script and de-duplicate properties that are only allowed once,
but appear more than once. (@ddolcimascolo). but appear more than once. (@ddolcimascolo).
* #377: Added Pacific Time (US & Canada) as exchange timezone * #377: Addes Pacific Time (US & Canada) as exchange timezone
* #384: Added fallback for VCards without `FN` * #384: Added fallback for VCards without `FN`

View File

@ -1,7 +1,7 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
include __DIR__.'/../vendor/autoload.php'; include __DIR__ . '/../vendor/autoload.php';
$data = stream_get_contents(STDIN); $data = stream_get_contents(STDIN);
@ -9,4 +9,4 @@ $start = microtime(true);
$lol = Sabre\VObject\Reader::read($data); $lol = Sabre\VObject\Reader::read($data);
echo 'time: '.(microtime(true) - $start)."\n"; echo "time: " . (microtime(true) - $start) . "\n";

View File

@ -1,16 +1,16 @@
<?php <?php
include __DIR__.'/../vendor/autoload.php'; include __DIR__ . '/../vendor/autoload.php';
if ($argc < 2) { if ($argc < 2) {
echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " freebusy benchmark\n"; echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " freebusy benchmark\n";
echo "\n"; echo "\n";
echo "This script can be used to measure the speed of generating a\n"; echo "This script can be used to measure the speed of generating a\n";
echo "free-busy report based on a calendar.\n"; echo "free-busy report based on a calendar.\n";
echo "\n"; echo "\n";
echo "The process will be repeated 100 times to get accurate stats\n"; echo "The process will be repeated 100 times to get accurate stats\n";
echo "\n"; echo "\n";
echo 'Usage: '.$argv[0]." inputfile.ics\n"; echo "Usage: " . $argv[0] . " inputfile.ics\n";
die(); die();
} }
@ -30,24 +30,33 @@ $timeZone = new \DateTimeZone('America/Toronto');
$bench->fb->start(); $bench->fb->start();
for ($i = 0; $i < $repeat; ++$i) { for ($i = 0; $i < $repeat; $i++) {
$fb = new Sabre\VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone); $fb = new Sabre\VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone);
$results = $fb->getResult(); $results = $fb->getResult();
} }
$bench->fb->stop(); $bench->fb->stop();
echo $bench,"\n"; echo $bench,"\n";
function formatMemory($input) function formatMemory($input) {
{
if (strlen($input) > 6) { if (strlen($input) > 6) {
return round($input / (1024 * 1024)).'M';
return round($input / (1024 * 1024)) . 'M';
} elseif (strlen($input) > 3) { } elseif (strlen($input) > 3) {
return round($input / 1024).'K';
return round($input / 1024) . 'K';
} }
} }
unset($input, $splitter); unset($input, $splitter);
echo 'peak memory usage: '.formatMemory(memory_get_peak_usage()), "\n"; echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n";
echo 'current memory usage: '.formatMemory(memory_get_usage()), "\n"; echo "current memory usage: " . formatMemory(memory_get_usage()), "\n";

View File

@ -1,15 +1,15 @@
<?php <?php
include __DIR__.'/../vendor/autoload.php'; include __DIR__ . '/../vendor/autoload.php';
if ($argc < 2) { if ($argc < 2) {
echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " manipulation benchmark\n"; echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " manipulation benchmark\n";
echo "\n"; echo "\n";
echo "This script can be used to measure the speed of opening a large amount of\n"; echo "This script can be used to measure the speed of opening a large amount of\n";
echo "vcards, making a few alterations and serializing them again.\n"; echo "vcards, making a few alterations and serializing them again.\n";
echo 'system.'; echo "system.";
echo "\n"; echo "\n";
echo 'Usage: '.$argv[0]." inputfile.vcf\n"; echo "Usage: " . $argv[0] . " inputfile.vcf\n";
die(); die();
} }
@ -22,21 +22,18 @@ $splitter = new Sabre\VObject\Splitter\VCard($input);
$bench = new Hoa\Bench\Bench(); $bench = new Hoa\Bench\Bench();
while (true) { while (true) {
$bench->parse->start(); $bench->parse->start();
$vcard = $splitter->getNext(); $vcard = $splitter->getNext();
$bench->parse->pause(); $bench->parse->pause();
if (!$vcard) { if (!$vcard) break;
break;
}
$bench->manipulate->start(); $bench->manipulate->start();
$vcard->{'X-FOO'} = 'Random new value!'; $vcard->{'X-FOO'} = 'Random new value!';
$emails = []; $emails = [];
if (isset($vcard->EMAIL)) { if (isset($vcard->EMAIL)) foreach ($vcard->EMAIL as $email) {
foreach ($vcard->EMAIL as $email) { $emails[] = (string)$email;
$emails[] = (string) $email;
}
} }
$bench->manipulate->pause(); $bench->manipulate->pause();
@ -45,20 +42,28 @@ while (true) {
$bench->serialize->pause(); $bench->serialize->pause();
$vcard->destroy(); $vcard->destroy();
} }
echo $bench,"\n"; echo $bench,"\n";
function formatMemory($input) function formatMemory($input) {
{
if (strlen($input) > 6) { if (strlen($input) > 6) {
return round($input / (1024 * 1024)).'M';
return round($input / (1024 * 1024)) . 'M';
} elseif (strlen($input) > 3) { } elseif (strlen($input) > 3) {
return round($input / 1024).'K';
return round($input / 1024) . 'K';
} }
} }
unset($input, $splitter); unset($input, $splitter);
echo 'peak memory usage: '.formatMemory(memory_get_peak_usage()), "\n"; echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n";
echo 'current memory usage: '.formatMemory(memory_get_usage()), "\n"; echo "current memory usage: " . formatMemory(memory_get_usage()), "\n";

View File

@ -2,9 +2,9 @@
<?php <?php
$windowsZonesUrl = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml'; $windowsZonesUrl = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml';
$outputFile = __DIR__.'/../lib/timezonedata/windowszones.php'; $outputFile = __DIR__ . '/../lib/timezonedata/windowszones.php';
echo 'Fetching timezone map from: '.$windowsZonesUrl, "\n"; echo "Fetching timezone map from: " . $windowsZonesUrl, "\n";
$data = file_get_contents($windowsZonesUrl); $data = file_get_contents($windowsZonesUrl);
@ -13,14 +13,16 @@ $xml = simplexml_load_string($data);
$map = []; $map = [];
foreach ($xml->xpath('//mapZone') as $mapZone) { foreach ($xml->xpath('//mapZone') as $mapZone) {
$from = (string) $mapZone['other'];
$to = (string) $mapZone['type']; $from = (string)$mapZone['other'];
$to = (string)$mapZone['type'];
list($to) = explode(' ', $to, 2); list($to) = explode(' ', $to, 2);
if (!isset($map[$from])) { if (!isset($map[$from])) {
$map[$from] = $to; $map[$from] = $to;
} }
} }
ksort($map); ksort($map);
@ -31,19 +33,19 @@ fwrite($f, "<?php\n\n");
fwrite($f, "/**\n"); fwrite($f, "/**\n");
fwrite($f, " * Automatically generated timezone file\n"); fwrite($f, " * Automatically generated timezone file\n");
fwrite($f, " *\n"); fwrite($f, " *\n");
fwrite($f, ' * Last update: '.date(DATE_W3C)."\n"); fwrite($f, " * Last update: " . date(DATE_W3C) . "\n");
fwrite($f, ' * Source: '.$windowsZonesUrl."\n"); fwrite($f, " * Source: " . $windowsZonesUrl . "\n");
fwrite($f, " *\n"); fwrite($f, " *\n");
fwrite($f, " * @copyright Copyright (C) fruux GmbH (https://fruux.com/).\n"); fwrite($f, " * @copyright Copyright (C) fruux GmbH (https://fruux.com/).\n");
fwrite($f, " * @license http://sabre.io/license/ Modified BSD License\n"); fwrite($f, " * @license http://sabre.io/license/ Modified BSD License\n");
fwrite($f, " */\n"); fwrite($f, " */\n");
fwrite($f, "\n"); fwrite($f, "\n");
fwrite($f, 'return '); fwrite($f, "return ");
fwrite($f, var_export($map, true).';'); fwrite($f, var_export($map, true) . ';');
fclose($f); fclose($f);
echo "Formatting\n"; echo "Formatting\n";
exec(__DIR__.'/sabre-cs-fixer fix '.escapeshellarg($outputFile)); exec(__DIR__ . '/sabre-cs-fixer fix ' . escapeshellarg($outputFile));
echo "Done\n"; echo "Done\n";

View File

@ -23,53 +23,54 @@ HI
$events = 100; $events = 100;
if (isset($argv[1])) { if (isset($argv[1])) $events = (int)$argv[1];
$events = (int) $argv[1];
}
include __DIR__.'/../vendor/autoload.php'; include __DIR__ . '/../vendor/autoload.php';
fwrite(STDERR, 'Generating '.$events." events\n"); fwrite(STDERR, "Generating " . $events . " events\n");
$currentDate = new DateTime('-'.round($events / 2).' days'); $currentDate = new DateTime('-' . round($events / 2) . ' days');
$calendar = new VObject\Component\VCalendar(); $calendar = new VObject\Component\VCalendar();
$ii = 0; $ii = 0;
while ($ii < $events) { while ($ii < $events) {
++$ii;
$ii++;
$event = $calendar->add('VEVENT'); $event = $calendar->add('VEVENT');
$event->DTSTART = 'bla'; $event->DTSTART = 'bla';
$event->SUMMARY = 'Event #'.$ii; $event->SUMMARY = 'Event #' . $ii;
$event->UID = md5(microtime(true)); $event->UID = md5(microtime(true));
$doctorRandom = mt_rand(1, 1000); $doctorRandom = mt_rand(1, 1000);
switch ($doctorRandom) { switch ($doctorRandom) {
// All-day event // All-day event
case 1: case 1 :
$event->DTEND = 'bla'; $event->DTEND = 'bla';
$dtStart = clone $currentDate; $dtStart = clone $currentDate;
$dtEnd = clone $currentDate; $dtEnd = clone $currentDate;
$dtEnd->modify('+'.mt_rand(1, 3).' days'); $dtEnd->modify('+' . mt_rand(1, 3) . ' days');
$event->DTSTART->setDateTime($dtStart); $event->DTSTART->setDateTime($dtStart);
$event->DTSTART['VALUE'] = 'DATE'; $event->DTSTART['VALUE'] = 'DATE';
$event->DTEND->setDateTime($dtEnd); $event->DTEND->setDateTime($dtEnd);
break; break;
case 2: case 2 :
$event->RRULE = 'FREQ=DAILY;COUNT='.mt_rand(1, 10); $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1, 10);
// no break intentional // No break intentional
default: default :
$dtStart = clone $currentDate; $dtStart = clone $currentDate;
$dtStart->setTime(mt_rand(1, 23), mt_rand(0, 59), mt_rand(0, 59)); $dtStart->setTime(mt_rand(1, 23), mt_rand(0, 59), mt_rand(0, 59));
$event->DTSTART->setDateTime($dtStart); $event->DTSTART->setDateTime($dtStart);
$event->DURATION = 'PT'.mt_rand(1, 3).'H'; $event->DURATION = 'PT' . mt_rand(1, 3) . 'H';
break; break;
}
$currentDate->modify('+ '.mt_rand(0, 3).' days'); }
$currentDate->modify('+ ' . mt_rand(0, 3) . ' days');
} }
fwrite(STDERR, "Validating\n"); fwrite(STDERR, "Validating\n");

View File

@ -6,8 +6,8 @@ namespace Sabre\VObject;
// This sucks.. we have to try to find the composer autoloader. But chances // This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest // are, we can't find it this way. So we'll do our bestest
$paths = [ $paths = [
__DIR__.'/../vendor/autoload.php', // In case vobject is cloned directly __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly
__DIR__.'/../../../autoload.php', // In case vobject is a composer dependency. __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency.
]; ];
foreach ($paths as $path) { foreach ($paths as $path) {
@ -22,12 +22,14 @@ if (!class_exists('Sabre\\VObject\\Version')) {
die(1); die(1);
} }
echo 'sabre/vobject ', Version::VERSION, " duplicate contact merge tool\n"; echo "sabre/vobject ", Version::VERSION, " duplicate contact merge tool\n";
if ($argc < 3) { if ($argc < 3) {
echo "\n"; echo "\n";
echo 'Usage: ', $argv[0], " input.vcf output.vcf [debug.log]\n"; echo "Usage: ", $argv[0], " input.vcf output.vcf [debug.log]\n";
die(1); die(1);
} }
$input = fopen($argv[1], 'r'); $input = fopen($argv[1], 'r');
@ -40,72 +42,83 @@ $splitter = new Splitter\VCard($input);
// but not in others, we don't consider them for the sake of finding // but not in others, we don't consider them for the sake of finding
// differences. // differences.
$ignoredProperties = [ $ignoredProperties = [
'PRODID', "PRODID",
'VERSION', "VERSION",
'REV', "REV",
'UID', "UID",
'X-ABLABEL', "X-ABLABEL",
]; ];
$collectedNames = []; $collectedNames = [];
$stats = [ $stats = [
'Total vcards' => 0, "Total vcards" => 0,
'No FN property' => 0, "No FN property" => 0,
'Ignored duplicates' => 0, "Ignored duplicates" => 0,
'Merged values' => 0, "Merged values" => 0,
'Error' => 0, "Error" => 0,
'Unique cards' => 0, "Unique cards" => 0,
'Total written' => 0, "Total written" => 0,
]; ];
function writeStats() function writeStats() {
{
global $stats; global $stats;
foreach ($stats as $name => $value) { foreach ($stats as $name => $value) {
echo str_pad($name, 23, ' ', STR_PAD_RIGHT), str_pad($value, 6, ' ', STR_PAD_LEFT), "\n"; echo str_pad($name, 23, " ", STR_PAD_RIGHT), str_pad($value, 6, " ", STR_PAD_LEFT), "\n";
} }
// Moving cursor back a few lines. // Moving cursor back a few lines.
echo "\033[".count($stats).'A'; echo "\033[" . count($stats) . "A";
} }
function write($vcard) function write($vcard) {
{
global $stats, $output; global $stats, $output;
++$stats['Total written']; $stats["Total written"]++;
fwrite($output, $vcard->serialize()."\n"); fwrite($output, $vcard->serialize() . "\n");
} }
while ($vcard = $splitter->getNext()) { while ($vcard = $splitter->getNext()) {
++$stats['Total vcards'];
$stats["Total vcards"]++;
writeStats(); writeStats();
$fn = isset($vcard->FN) ? (string) $vcard->FN : null; $fn = isset($vcard->FN) ? (string)$vcard->FN : null;
if (empty($fn)) { if (empty($fn)) {
// Immediately write this vcard, we don't compare it. // Immediately write this vcard, we don't compare it.
++$stats['No FN property']; $stats["No FN property"]++;
++$stats['Unique cards']; $stats['Unique cards']++;
write($vcard); write($vcard);
$vcard->destroy(); $vcard->destroy();
continue; continue;
} }
if (!isset($collectedNames[$fn])) { if (!isset($collectedNames[$fn])) {
$collectedNames[$fn] = $vcard; $collectedNames[$fn] = $vcard;
++$stats['Unique cards']; $stats['Unique cards']++;
continue; continue;
} else { } else {
// Starting comparison for all properties. We only check if properties // Starting comparison for all properties. We only check if properties
// in the current vcard exactly appear in the earlier vcard as well. // in the current vcard exactly appear in the earlier vcard as well.
foreach ($vcard->children() as $newProp) { foreach ($vcard->children() as $newProp) {
if (in_array($newProp->name, $ignoredProperties)) { if (in_array($newProp->name, $ignoredProperties)) {
// We don't care about properties such as UID and REV. // We don't care about properties such as UID and REV.
continue; continue;
} }
$ok = false; $ok = false;
foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {
if ($compareProp->serialize() === $newProp->serialize()) { if ($compareProp->serialize() === $newProp->serialize()) {
$ok = true; $ok = true;
break; break;
@ -113,11 +126,14 @@ while ($vcard = $splitter->getNext()) {
} }
if (!$ok) { if (!$ok) {
if ('EMAIL' === $newProp->name || 'TEL' === $newProp->name) {
if ($newProp->name === 'EMAIL' || $newProp->name === 'TEL') {
// We're going to make another attempt to find this // We're going to make another attempt to find this
// property, this time just by value. If we find it, we // property, this time just by value. If we find it, we
// consider it a success. // consider it a success.
foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {
if ($compareProp->getValue() === $newProp->getValue()) { if ($compareProp->getValue() === $newProp->getValue()) {
$ok = true; $ok = true;
break; break;
@ -125,36 +141,44 @@ while ($vcard = $splitter->getNext()) {
} }
if (!$ok) { if (!$ok) {
// Merging the new value in the old vcard. // Merging the new value in the old vcard.
$collectedNames[$fn]->add(clone $newProp); $collectedNames[$fn]->add(clone $newProp);
$ok = true; $ok = true;
++$stats['Merged values']; $stats['Merged values']++;
} }
} }
} }
if (!$ok) { if (!$ok) {
// echo $newProp->serialize() . " does not appear in earlier vcard!\n";
++$stats['Error'];
if ($debug) {
fwrite($debug, "Missing '".$newProp->name."' property in duplicate. Earlier vcard:\n".$collectedNames[$fn]->serialize()."\n\nLater:\n".$vcard->serialize()."\n\n");
}
// echo $newProp->serialize() . " does not appear in earlier vcard!\n";
$stats['Error']++;
if ($debug) fwrite($debug, "Missing '" . $newProp->name . "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n");
$vcard->destroy(); $vcard->destroy();
continue 2; continue 2;
} }
} }
} }
$vcard->destroy(); $vcard->destroy();
++$stats['Ignored duplicates']; $stats['Ignored duplicates']++;
} }
foreach ($collectedNames as $vcard) { foreach ($collectedNames as $vcard) {
// Overwriting any old PRODID // Overwriting any old PRODID
$vcard->PRODID = '-//Sabre//Sabre VObject '.Version::VERSION.'//EN'; $vcard->PRODID = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
write($vcard); write($vcard);
writeStats(); writeStats();
} }
echo str_repeat("\n", count($stats)), "\nDone.\n"; echo str_repeat("\n", count($stats)), "\nDone.\n";

View File

@ -1,14 +1,14 @@
<?php <?php
include __DIR__.'/../vendor/autoload.php'; include __DIR__ . '/../vendor/autoload.php';
if ($argc < 4) { if ($argc < 4) {
echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " RRULE benchmark\n"; echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " RRULE benchmark\n";
echo "\n"; echo "\n";
echo "This script can be used to measure the speed of the 'recurrence expansion'\n"; echo "This script can be used to measure the speed of the 'recurrence expansion'\n";
echo 'system.'; echo "system.";
echo "\n"; echo "\n";
echo 'Usage: '.$argv[0]." inputfile.ics startdate enddate\n"; echo "Usage: " . $argv[0] . " inputfile.ics startdate enddate\n";
die(); die();
} }

View File

@ -37,7 +37,9 @@
"sabre/xml" : ">=1.5 <3.0" "sabre/xml" : ">=1.5 <3.0"
}, },
"require-dev" : { "require-dev" : {
"phpunit/phpunit" : "> 4.8.35, <6.0.0" "phpunit/phpunit" : "> 4.8.35, <6.0.0",
"sabre/cs" : "^1.0.0"
}, },
"suggest" : { "suggest" : {
"hoa/bench" : "If you would like to run the benchmark scripts" "hoa/bench" : "If you would like to run the benchmark scripts"

View File

@ -11,8 +11,8 @@ use Sabre\VObject\Component\VCalendar;
* @author Dominik Tobschall (http://tobschall.de/) * @author Dominik Tobschall (http://tobschall.de/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class BirthdayCalendarGenerator class BirthdayCalendarGenerator {
{
/** /**
* Input objects. * Input objects.
* *
@ -41,11 +41,12 @@ class BirthdayCalendarGenerator
* *
* @param mixed $objects * @param mixed $objects
*/ */
public function __construct($objects = null) function __construct($objects = null) {
{
if ($objects) { if ($objects) {
$this->setObjects($objects); $this->setObjects($objects);
} }
} }
/** /**
@ -55,38 +56,52 @@ class BirthdayCalendarGenerator
* It's also possible to supply an array of strings or objects. * It's also possible to supply an array of strings or objects.
* *
* @param mixed $objects * @param mixed $objects
*
* @return void
*/ */
public function setObjects($objects) function setObjects($objects) {
{
if (!is_array($objects)) { if (!is_array($objects)) {
$objects = [$objects]; $objects = [$objects];
} }
$this->objects = []; $this->objects = [];
foreach ($objects as $object) { foreach ($objects as $object) {
if (is_string($object)) { if (is_string($object)) {
$vObj = Reader::read($object); $vObj = Reader::read($object);
if (!$vObj instanceof Component\VCard) { if (!$vObj instanceof Component\VCard) {
throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects'); throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
} }
$this->objects[] = $vObj; $this->objects[] = $vObj;
} elseif ($object instanceof Component\VCard) { } elseif ($object instanceof Component\VCard) {
$this->objects[] = $object; $this->objects[] = $object;
} else { } else {
throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects'); throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
} }
} }
} }
/** /**
* Sets the output format for the SUMMARY. * Sets the output format for the SUMMARY
* *
* @param string $format * @param string $format
*
* @return void
*/ */
public function setFormat($format) function setFormat($format) {
{
$this->format = $format; $this->format = $format;
} }
/** /**
@ -94,11 +109,12 @@ class BirthdayCalendarGenerator
* *
* @return Component/VCalendar * @return Component/VCalendar
*/ */
public function getResult() function getResult() {
{
$calendar = new VCalendar(); $calendar = new VCalendar();
foreach ($this->objects as $object) { foreach ($this->objects as $object) {
// Skip if there is no BDAY property. // Skip if there is no BDAY property.
if (!$object->select('BDAY')) { if (!$object->select('BDAY')) {
continue; continue;
@ -136,7 +152,7 @@ class BirthdayCalendarGenerator
$unknownYear = false; $unknownYear = false;
if (!$dateParts['year']) { if (!$dateParts['year']) {
$object->BDAY = self::DEFAULT_YEAR.'-'.$dateParts['month'].'-'.$dateParts['date']; $object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date'];
$unknownYear = true; $unknownYear = true;
} }
@ -145,8 +161,8 @@ class BirthdayCalendarGenerator
$event = $calendar->add('VEVENT', [ $event = $calendar->add('VEVENT', [
'SUMMARY' => sprintf($this->format, $object->FN->getValue()), 'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
'DTSTART' => new \DateTime($object->BDAY->getValue()), 'DTSTART' => new \DateTime($object->BDAY->getValue()),
'RRULE' => 'FREQ=YEARLY', 'RRULE' => 'FREQ=YEARLY',
'TRANSP' => 'TRANSPARENT', 'TRANSP' => 'TRANSPARENT',
]); ]);
// add VALUE=date // add VALUE=date
@ -156,17 +172,20 @@ class BirthdayCalendarGenerator
if ($unknownYear) { if ($unknownYear) {
$event->add('X-SABRE-BDAY', 'BDAY', [ $event->add('X-SABRE-BDAY', 'BDAY', [
'X-SABRE-VCARD-UID' => $object->UID->getValue(), 'X-SABRE-VCARD-UID' => $object->UID->getValue(),
'X-SABRE-VCARD-FN' => $object->FN->getValue(), 'X-SABRE-VCARD-FN' => $object->FN->getValue(),
'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR, 'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
]); ]);
} else { } else {
$event->add('X-SABRE-BDAY', 'BDAY', [ $event->add('X-SABRE-BDAY', 'BDAY', [
'X-SABRE-VCARD-UID' => $object->UID->getValue(), 'X-SABRE-VCARD-UID' => $object->UID->getValue(),
'X-SABRE-VCARD-FN' => $object->FN->getValue(), 'X-SABRE-VCARD-FN' => $object->FN->getValue(),
]); ]);
} }
} }
return $calendar; return $calendar;
} }
} }

View File

@ -12,8 +12,8 @@ use
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Cli class Cli {
{
/** /**
* No output. * No output.
* *
@ -96,8 +96,8 @@ class Cli
* *
* @return int * @return int
*/ */
public function main(array $argv) function main(array $argv) {
{
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
// We cannot easily test this, so we'll skip it. Pretty basic anyway. // We cannot easily test this, so we'll skip it. Pretty basic anyway.
@ -113,96 +113,103 @@ class Cli
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
try { try {
list($options, $positional) = $this->parseArguments($argv); list($options, $positional) = $this->parseArguments($argv);
if (isset($options['q'])) { if (isset($options['q'])) {
$this->quiet = true; $this->quiet = true;
} }
$this->log($this->colorize('green', 'sabre/vobject ').$this->colorize('yellow', Version::VERSION)); $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
foreach ($options as $name => $value) { foreach ($options as $name => $value) {
switch ($name) { switch ($name) {
case 'q':
case 'q' :
// Already handled earlier. // Already handled earlier.
break; break;
case 'h': case 'h' :
case 'help': case 'help' :
$this->showHelp(); $this->showHelp();
return 0; return 0;
break; break;
case 'format': case 'format' :
switch ($value) { switch ($value) {
// jcard/jcal documents // jcard/jcal documents
case 'jcard': case 'jcard' :
case 'jcal': case 'jcal' :
// specific document versions // specific document versions
case 'vcard21': case 'vcard21' :
case 'vcard30': case 'vcard30' :
case 'vcard40': case 'vcard40' :
case 'icalendar20': case 'icalendar20' :
// specific formats // specific formats
case 'json': case 'json' :
case 'mimedir': case 'mimedir' :
// icalendar/vcad // icalendar/vcad
case 'icalendar': case 'icalendar' :
case 'vcard': case 'vcard' :
$this->format = $value; $this->format = $value;
break; break;
default: default :
throw new InvalidArgumentException('Unknown format: '.$value); throw new InvalidArgumentException('Unknown format: ' . $value);
} }
break; break;
case 'pretty': case 'pretty' :
if (version_compare(PHP_VERSION, '5.4.0') >= 0) { if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
$this->pretty = true; $this->pretty = true;
} }
break; break;
case 'forgiving': case 'forgiving' :
$this->forgiving = true; $this->forgiving = true;
break; break;
case 'inputformat': case 'inputformat' :
switch ($value) { switch ($value) {
// json formats // json formats
case 'jcard': case 'jcard' :
case 'jcal': case 'jcal' :
case 'json': case 'json' :
$this->inputFormat = 'json'; $this->inputFormat = 'json';
break; break;
// mimedir formats // mimedir formats
case 'mimedir': case 'mimedir' :
case 'icalendar': case 'icalendar' :
case 'vcard': case 'vcard' :
case 'vcard21': case 'vcard21' :
case 'vcard30': case 'vcard30' :
case 'vcard40': case 'vcard40' :
case 'icalendar20': case 'icalendar20' :
$this->inputFormat = 'mimedir'; $this->inputFormat = 'mimedir';
break; break;
default: default :
throw new InvalidArgumentException('Unknown format: '.$value); throw new InvalidArgumentException('Unknown format: ' . $value);
} }
break; break;
default: default :
throw new InvalidArgumentException('Unknown option: '.$name); throw new InvalidArgumentException('Unknown option: ' . $name);
} }
} }
if (0 === count($positional)) { if (count($positional) === 0) {
$this->showHelp(); $this->showHelp();
return 1; return 1;
} }
if (1 === count($positional)) { if (count($positional) === 1) {
throw new InvalidArgumentException('Inputfile is a required argument'); throw new InvalidArgumentException('Inputfile is a required argument');
} }
@ -211,12 +218,12 @@ class Cli
} }
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) { if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
throw new InvalidArgumentException('Uknown command: '.$positional[0]); throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
} }
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
$this->showHelp(); $this->showHelp();
$this->log('Error: '.$e->getMessage(), 'red'); $this->log('Error: ' . $e->getMessage(), 'red');
return 1; return 1;
} }
@ -225,71 +232,76 @@ class Cli
$this->inputPath = $positional[1]; $this->inputPath = $positional[1];
$this->outputPath = isset($positional[2]) ? $positional[2] : '-'; $this->outputPath = isset($positional[2]) ? $positional[2] : '-';
if ('-' !== $this->outputPath) { if ($this->outputPath !== '-') {
$this->stdout = fopen($this->outputPath, 'w'); $this->stdout = fopen($this->outputPath, 'w');
} }
if (!$this->inputFormat) { if (!$this->inputFormat) {
if ('.json' === substr($this->inputPath, -5)) { if (substr($this->inputPath, -5) === '.json') {
$this->inputFormat = 'json'; $this->inputFormat = 'json';
} else { } else {
$this->inputFormat = 'mimedir'; $this->inputFormat = 'mimedir';
} }
} }
if (!$this->format) { if (!$this->format) {
if ('.json' === substr($this->outputPath, -5)) { if (substr($this->outputPath, -5) === '.json') {
$this->format = 'json'; $this->format = 'json';
} else { } else {
$this->format = 'mimedir'; $this->format = 'mimedir';
} }
} }
$realCode = 0; $realCode = 0;
try { try {
while ($input = $this->readInput()) { while ($input = $this->readInput()) {
$returnCode = $this->$command($input); $returnCode = $this->$command($input);
if (0 !== $returnCode) { if ($returnCode !== 0) $realCode = $returnCode;
$realCode = $returnCode;
}
} }
} catch (EofException $e) { } catch (EofException $e) {
// end of file // end of file
} catch (\Exception $e) { } catch (\Exception $e) {
$this->log('Error: '.$e->getMessage(), 'red'); $this->log('Error: ' . $e->getMessage(), 'red');
return 2; return 2;
} }
return $realCode; return $realCode;
} }
/** /**
* Shows the help message. * Shows the help message.
*
* @return void
*/ */
protected function showHelp() protected function showHelp() {
{
$this->log('Usage:', 'yellow'); $this->log('Usage:', 'yellow');
$this->log(' vobject [options] command [arguments]'); $this->log(" vobject [options] command [arguments]");
$this->log(''); $this->log('');
$this->log('Options:', 'yellow'); $this->log('Options:', 'yellow');
$this->log($this->colorize('green', ' -q ')."Don't output anything."); $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', ' -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', ' --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($this->colorize('green', ' --forgiving ') . "Makes the parser less strict.");
$this->log(' vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.'); $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($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it");
$this->log(' must be specified here.'); $this->log(" must be specified here.");
// Only PHP 5.4 and up // Only PHP 5.4 and up
if (version_compare(PHP_VERSION, '5.4.0') >= 0) { if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
$this->log($this->colorize('green', ' --pretty ').'json pretty-print.'); $this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
} }
$this->log(''); $this->log('');
$this->log('Commands:', 'yellow'); $this->log('Commands:', 'yellow');
$this->log($this->colorize('green', ' validate').' source_file Validates a file for correctness.'); $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', ' 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', ' convert') . ' source_file [output_file] Converts a file.');
$this->log($this->colorize('green', ' color').' source_file Colorize a file, useful for debbugging.'); $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.');
$this->log( $this->log(
<<<HELP <<<HELP
@ -307,6 +319,7 @@ HELP
$this->log(' vobject color calendar.ics'); $this->log(' vobject color calendar.ics');
$this->log(''); $this->log('');
$this->log('https://github.com/fruux/sabre-vobject', 'purple'); $this->log('https://github.com/fruux/sabre-vobject', 'purple');
} }
/** /**
@ -316,23 +329,24 @@ HELP
* *
* @return int * @return int
*/ */
protected function validate(Component $vObj) protected function validate(Component $vObj) {
{
$returnCode = 0; $returnCode = 0;
switch ($vObj->name) { switch ($vObj->name) {
case 'VCALENDAR': case 'VCALENDAR' :
$this->log('iCalendar: '.(string) $vObj->VERSION); $this->log("iCalendar: " . (string)$vObj->VERSION);
break; break;
case 'VCARD': case 'VCARD' :
$this->log('vCard: '.(string) $vObj->VERSION); $this->log("vCard: " . (string)$vObj->VERSION);
break; break;
} }
$warnings = $vObj->validate(); $warnings = $vObj->validate();
if (!count($warnings)) { if (!count($warnings)) {
$this->log(' No warnings!'); $this->log(" No warnings!");
} else { } else {
$levels = [ $levels = [
1 => 'REPAIRED', 1 => 'REPAIRED',
2 => 'WARNING', 2 => 'WARNING',
@ -340,15 +354,19 @@ HELP
]; ];
$returnCode = 2; $returnCode = 2;
foreach ($warnings as $warn) { foreach ($warnings as $warn) {
$extra = ''; $extra = '';
if ($warn['node'] instanceof Property) { if ($warn['node'] instanceof Property) {
$extra = ' (property: "'.$warn['node']->name.'")'; $extra = ' (property: "' . $warn['node']->name . '")';
} }
$this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra); $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
} }
} }
return $returnCode; return $returnCode;
} }
/** /**
@ -358,23 +376,24 @@ HELP
* *
* @return int * @return int
*/ */
protected function repair(Component $vObj) protected function repair(Component $vObj) {
{
$returnCode = 0; $returnCode = 0;
switch ($vObj->name) { switch ($vObj->name) {
case 'VCALENDAR': case 'VCALENDAR' :
$this->log('iCalendar: '.(string) $vObj->VERSION); $this->log("iCalendar: " . (string)$vObj->VERSION);
break; break;
case 'VCARD': case 'VCARD' :
$this->log('vCard: '.(string) $vObj->VERSION); $this->log("vCard: " . (string)$vObj->VERSION);
break; break;
} }
$warnings = $vObj->validate(Node::REPAIR); $warnings = $vObj->validate(Node::REPAIR);
if (!count($warnings)) { if (!count($warnings)) {
$this->log(' No warnings!'); $this->log(" No warnings!");
} else { } else {
$levels = [ $levels = [
1 => 'REPAIRED', 1 => 'REPAIRED',
2 => 'WARNING', 2 => 'WARNING',
@ -382,16 +401,20 @@ HELP
]; ];
$returnCode = 2; $returnCode = 2;
foreach ($warnings as $warn) { foreach ($warnings as $warn) {
$extra = ''; $extra = '';
if ($warn['node'] instanceof Property) { if ($warn['node'] instanceof Property) {
$extra = ' (property: "'.$warn['node']->name.'")'; $extra = ' (property: "' . $warn['node']->name . '")';
} }
$this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra); $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
} }
} }
fwrite($this->stdout, $vObj->serialize()); fwrite($this->stdout, $vObj->serialize());
return $returnCode; return $returnCode;
} }
/** /**
@ -401,46 +424,47 @@ HELP
* *
* @return int * @return int
*/ */
protected function convert($vObj) protected function convert($vObj) {
{
$json = false; $json = false;
$convertVersion = null; $convertVersion = null;
$forceInput = null; $forceInput = null;
switch ($this->format) { switch ($this->format) {
case 'json': case 'json' :
$json = true; $json = true;
if ('VCARD' === $vObj->name) { if ($vObj->name === 'VCARD') {
$convertVersion = Document::VCARD40; $convertVersion = Document::VCARD40;
} }
break; break;
case 'jcard': case 'jcard' :
$json = true; $json = true;
$forceInput = 'VCARD'; $forceInput = 'VCARD';
$convertVersion = Document::VCARD40; $convertVersion = Document::VCARD40;
break; break;
case 'jcal': case 'jcal' :
$json = true; $json = true;
$forceInput = 'VCALENDAR'; $forceInput = 'VCALENDAR';
break; break;
case 'mimedir': case 'mimedir' :
case 'icalendar': case 'icalendar' :
case 'icalendar20': case 'icalendar20' :
case 'vcard': case 'vcard' :
break; break;
case 'vcard21': case 'vcard21' :
$convertVersion = Document::VCARD21; $convertVersion = Document::VCARD21;
break; break;
case 'vcard30': case 'vcard30' :
$convertVersion = Document::VCARD30; $convertVersion = Document::VCARD30;
break; break;
case 'vcard40': case 'vcard40' :
$convertVersion = Document::VCARD40; $convertVersion = Document::VCARD40;
break; break;
} }
if ($forceInput && $vObj->name !== $forceInput) { if ($forceInput && $vObj->name !== $forceInput) {
throw new \Exception('You cannot convert a '.strtolower($vObj->name).' to '.$this->format); throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
} }
if ($convertVersion) { if ($convertVersion) {
$vObj = $vObj->convert($convertVersion); $vObj = $vObj->convert($convertVersion);
@ -456,6 +480,7 @@ HELP
} }
return 0; return 0;
} }
/** /**
@ -465,9 +490,10 @@ HELP
* *
* @return int * @return int
*/ */
protected function color($vObj) protected function color($vObj) {
{
fwrite($this->stdout, $this->serializeComponent($vObj)); fwrite($this->stdout, $this->serializeComponent($vObj));
} }
/** /**
@ -477,19 +503,19 @@ HELP
* *
* @return string * @return string
*/ */
protected function colorize($color, $str, $resetTo = 'default') protected function colorize($color, $str, $resetTo = 'default') {
{
$colors = [ $colors = [
'cyan' => '1;36', 'cyan' => '1;36',
'red' => '1;31', 'red' => '1;31',
'yellow' => '1;33', 'yellow' => '1;33',
'blue' => '0;34', 'blue' => '0;34',
'green' => '0;32', 'green' => '0;32',
'default' => '0', 'default' => '0',
'purple' => '0;35', 'purple' => '0;35',
]; ];
return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m";
return "\033[".$colors[$color].'m'.$str."\033[".$colors[$resetTo].'m';
} }
/** /**
@ -497,17 +523,20 @@ HELP
* *
* @param string $color * @param string $color
* @param string $str * @param string $str
*
* @return void
*/ */
protected function cWrite($color, $str) protected function cWrite($color, $str) {
{
fwrite($this->stdout, $this->colorize($color, $str)); fwrite($this->stdout, $this->colorize($color, $str));
} }
protected function serializeComponent(Component $vObj) protected function serializeComponent(Component $vObj) {
{
$this->cWrite('cyan', 'BEGIN'); $this->cWrite('cyan', 'BEGIN');
$this->cWrite('red', ':'); $this->cWrite('red', ':');
$this->cWrite('yellow', $vObj->name."\n"); $this->cWrite('yellow', $vObj->name . "\n");
/** /**
* Gives a component a 'score' for sorting purposes. * Gives a component a 'score' for sorting purposes.
@ -519,51 +548,52 @@ HELP
* space to accomodate elements. The $key is added to the $score to * space to accomodate elements. The $key is added to the $score to
* preserve the original relative order of elements. * preserve the original relative order of elements.
* *
* @param int $key * @param int $key
* @param array $array * @param array $array
* *
* @return int * @return int
*/ */
$sortScore = function ($key, $array) { $sortScore = function($key, $array) {
if ($array[$key] instanceof Component) { if ($array[$key] instanceof Component) {
// We want to encode VTIMEZONE first, this is a personal // We want to encode VTIMEZONE first, this is a personal
// preference. // preference.
if ('VTIMEZONE' === $array[$key]->name) { if ($array[$key]->name === 'VTIMEZONE') {
$score = 300000000; $score = 300000000;
return $score + $key; return $score + $key;
} else { } else {
$score = 400000000; $score = 400000000;
return $score + $key; return $score + $key;
} }
} else { } else {
// Properties get encoded first // Properties get encoded first
// VCARD version 4.0 wants the VERSION property to appear first // VCARD version 4.0 wants the VERSION property to appear first
if ($array[$key] instanceof Property) { if ($array[$key] instanceof Property) {
if ('VERSION' === $array[$key]->name) { if ($array[$key]->name === 'VERSION') {
$score = 100000000; $score = 100000000;
return $score + $key; return $score + $key;
} else { } else {
// All other properties // All other properties
$score = 200000000; $score = 200000000;
return $score + $key; return $score + $key;
} }
} }
} }
}; };
$children = $vObj->children(); $children = $vObj->children();
$tmp = $children; $tmp = $children;
uksort( uksort(
$children, $children,
function ($a, $b) use ($sortScore, $tmp) { function($a, $b) use ($sortScore, $tmp) {
$sA = $sortScore($a, $tmp); $sA = $sortScore($a, $tmp);
$sB = $sortScore($b, $tmp); $sB = $sortScore($b, $tmp);
return $sA - $sB; return $sA - $sB;
} }
); );
@ -577,16 +607,19 @@ HELP
$this->cWrite('cyan', 'END'); $this->cWrite('cyan', 'END');
$this->cWrite('red', ':'); $this->cWrite('red', ':');
$this->cWrite('yellow', $vObj->name."\n"); $this->cWrite('yellow', $vObj->name . "\n");
} }
/** /**
* Colorizes a property. * Colorizes a property.
* *
* @param Property $property * @param Property $property
*
* @return void
*/ */
protected function serializeProperty(Property $property) protected function serializeProperty(Property $property) {
{
if ($property->group) { if ($property->group) {
$this->cWrite('default', $property->group); $this->cWrite('default', $property->group);
$this->cWrite('red', '.'); $this->cWrite('red', '.');
@ -595,14 +628,19 @@ HELP
$this->cWrite('yellow', $property->name); $this->cWrite('yellow', $property->name);
foreach ($property->parameters as $param) { foreach ($property->parameters as $param) {
$this->cWrite('red', ';'); $this->cWrite('red', ';');
$this->cWrite('blue', $param->serialize()); $this->cWrite('blue', $param->serialize());
} }
$this->cWrite('red', ':'); $this->cWrite('red', ':');
if ($property instanceof Property\Binary) { if ($property instanceof Property\Binary) {
$this->cWrite('default', 'embedded binary stripped. ('.strlen($property->getValue()).' bytes)');
$this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
} else { } else {
$parts = $property->getParts(); $parts = $property->getParts();
$first1 = true; $first1 = true;
// Looping through property values // Looping through property values
@ -614,7 +652,7 @@ HELP
} }
$first2 = true; $first2 = true;
// Looping through property sub-values // Looping through property sub-values
foreach ((array) $part as $subPart) { foreach ((array)$part as $subPart) {
if ($first2) { if ($first2) {
$first2 = false; $first2 = false;
} else { } else {
@ -626,39 +664,42 @@ HELP
$subPart, $subPart,
[ [
'\\' => $this->colorize('purple', '\\\\', 'green'), '\\' => $this->colorize('purple', '\\\\', 'green'),
';' => $this->colorize('purple', '\;', 'green'), ';' => $this->colorize('purple', '\;', 'green'),
',' => $this->colorize('purple', '\,', 'green'), ',' => $this->colorize('purple', '\,', 'green'),
"\n" => $this->colorize('purple', "\\n\n\t", 'green'), "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
"\r" => '', "\r" => "",
] ]
); );
$this->cWrite('green', $subPart); $this->cWrite('green', $subPart);
} }
} }
} }
$this->cWrite('default', "\n"); $this->cWrite("default", "\n");
} }
/** /**
* Parses the list of arguments. * Parses the list of arguments.
* *
* @param array $argv * @param array $argv
*
* @return void
*/ */
protected function parseArguments(array $argv) protected function parseArguments(array $argv) {
{
$positional = []; $positional = [];
$options = []; $options = [];
for ($ii = 0; $ii < count($argv); ++$ii) { for ($ii = 0; $ii < count($argv); $ii++) {
// Skipping the first argument. // Skipping the first argument.
if (0 === $ii) { if ($ii === 0) continue;
continue;
}
$v = $argv[$ii]; $v = $argv[$ii];
if ('--' === substr($v, 0, 2)) { if (substr($v, 0, 2) === '--') {
// This is a long-form option. // This is a long-form option.
$optionName = substr($v, 2); $optionName = substr($v, 2);
$optionValue = true; $optionValue = true;
@ -666,17 +707,22 @@ HELP
list($optionName, $optionValue) = explode('=', $optionName); list($optionName, $optionValue) = explode('=', $optionName);
} }
$options[$optionName] = $optionValue; $options[$optionName] = $optionValue;
} elseif ('-' === substr($v, 0, 1) && strlen($v) > 1) { } elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) {
// This is a short-form option. // This is a short-form option.
foreach (str_split(substr($v, 1)) as $option) { foreach (str_split(substr($v, 1)) as $option) {
$options[$option] = true; $options[$option] = true;
} }
} else { } else {
$positional[] = $v; $positional[] = $v;
} }
} }
return [$options, $positional]; return [$options, $positional];
} }
protected $parser; protected $parser;
@ -686,14 +732,14 @@ HELP
* *
* @return Component * @return Component
*/ */
protected function readInput() protected function readInput() {
{
if (!$this->parser) { if (!$this->parser) {
if ('-' !== $this->inputPath) { if ($this->inputPath !== '-') {
$this->stdin = fopen($this->inputPath, 'r'); $this->stdin = fopen($this->inputPath, 'r');
} }
if ('mimedir' === $this->inputFormat) { if ($this->inputFormat === 'mimedir') {
$this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0)); $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
} else { } else {
$this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0)); $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
@ -701,20 +747,25 @@ HELP
} }
return $this->parser->parse(); return $this->parser->parse();
} }
/** /**
* Sends a message to STDERR. * Sends a message to STDERR.
* *
* @param string $msg * @param string $msg
*
* @return void
*/ */
protected function log($msg, $color = 'default') protected function log($msg, $color = 'default') {
{
if (!$this->quiet) { if (!$this->quiet) {
if ('default' !== $color) { if ($color !== 'default') {
$msg = $this->colorize($color, $msg); $msg = $this->colorize($color, $msg);
} }
fwrite($this->stderr, $msg."\n"); fwrite($this->stderr, $msg . "\n");
} }
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Component extends Node class Component extends Node {
{
/** /**
* Component name. * Component name.
* *
@ -44,12 +44,14 @@ class Component extends Node
* ensure that this does not happen, set $defaults to false. * ensure that this does not happen, set $defaults to false.
* *
* @param Document $root * @param Document $root
* @param string $name such as VCALENDAR, VEVENT * @param string $name such as VCALENDAR, VEVENT.
* @param array $children * @param array $children
* @param bool $defaults * @param bool $defaults
*
* @return void
*/ */
public function __construct(Document $root, $name, array $children = [], $defaults = true) function __construct(Document $root, $name, array $children = [], $defaults = true) {
{
$this->name = strtoupper($name); $this->name = strtoupper($name);
$this->root = $root; $this->root = $root;
@ -82,11 +84,13 @@ class Component extends Node
// Component or Property // Component or Property
$this->add($child); $this->add($child);
} else { } else {
// Property key=>value // Property key=>value
$this->add($k, $child); $this->add($k, $child);
} }
} }
} }
} }
/** /**
@ -102,8 +106,8 @@ class Component extends Node
* *
* @return Node * @return Node
*/ */
public function add() function add() {
{
$arguments = func_get_args(); $arguments = func_get_args();
if ($arguments[0] instanceof Node) { if ($arguments[0] instanceof Node) {
@ -112,10 +116,15 @@ class Component extends Node
} }
$arguments[0]->parent = $this; $arguments[0]->parent = $this;
$newNode = $arguments[0]; $newNode = $arguments[0];
} elseif (is_string($arguments[0])) { } elseif (is_string($arguments[0])) {
$newNode = call_user_func_array([$this->root, 'create'], $arguments); $newNode = call_user_func_array([$this->root, 'create'], $arguments);
} else { } else {
throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string'); throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
} }
$name = $newNode->name; $name = $newNode->name;
@ -124,8 +133,8 @@ class Component extends Node
} else { } else {
$this->children[$name] = [$newNode]; $this->children[$name] = [$newNode];
} }
return $newNode; return $newNode;
} }
/** /**
@ -137,34 +146,36 @@ class Component extends Node
* exact item will be removed. * exact item will be removed.
* *
* @param string|Property|Component $item * @param string|Property|Component $item
* @return void
*/ */
public function remove($item) function remove($item) {
{
if (is_string($item)) { if (is_string($item)) {
// If there's no dot in the name, it's an exact property name and // If there's no dot in the name, it's an exact property name and
// we can just wipe out all those properties. // we can just wipe out all those properties.
// //
if (false === strpos($item, '.')) { if (strpos($item, '.') === false) {
unset($this->children[strtoupper($item)]); unset($this->children[strtoupper($item)]);
return; return;
} }
// If there was a dot, we need to ask select() to help us out and // If there was a dot, we need to ask select() to help us out and
// then we just call remove recursively. // then we just call remove recursively.
foreach ($this->select($item) as $child) { foreach ($this->select($item) as $child) {
$this->remove($child); $this->remove($child);
} }
} else { } else {
foreach ($this->select($item->name) as $k => $child) { foreach ($this->select($item->name) as $k => $child) {
if ($child === $item) { if ($child === $item) {
unset($this->children[$item->name][$k]); unset($this->children[$item->name][$k]);
return; return;
} }
} }
} }
throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
} }
/** /**
@ -173,14 +184,14 @@ class Component extends Node
* *
* @return array * @return array
*/ */
public function children() function children() {
{
$result = []; $result = [];
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
$result = array_merge($result, $childGroup); $result = array_merge($result, $childGroup);
} }
return $result; return $result;
} }
/** /**
@ -189,8 +200,8 @@ class Component extends Node
* *
* @return array * @return array
*/ */
public function getComponents() function getComponents() {
{
$result = []; $result = [];
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
@ -200,8 +211,8 @@ class Component extends Node
} }
} }
} }
return $result; return $result;
} }
/** /**
@ -215,21 +226,19 @@ class Component extends Node
* has not been assigned a group, specify ".EMAIL". * has not been assigned a group, specify ".EMAIL".
* *
* @param string $name * @param string $name
*
* @return array * @return array
*/ */
public function select($name) function select($name) {
{
$group = null; $group = null;
$name = strtoupper($name); $name = strtoupper($name);
if (false !== strpos($name, '.')) { if (strpos($name, '.') !== false) {
list($group, $name) = explode('.', $name, 2); list($group, $name) = explode('.', $name, 2);
} }
if ('' === $name) { if ($name === '') $name = null;
$name = null;
}
if (!is_null($name)) { if (!is_null($name)) {
$result = isset($this->children[$name]) ? $this->children[$name] : []; $result = isset($this->children[$name]) ? $this->children[$name] : [];
if (is_null($group)) { if (is_null($group)) {
@ -239,25 +248,32 @@ class Component extends Node
// more. // more.
return array_filter( return array_filter(
$result, $result,
function ($child) use ($group) { function($child) use ($group) {
return $child instanceof Property && strtoupper($child->group) === $group; return $child instanceof Property && strtoupper($child->group) === $group;
} }
); );
} }
} }
// If we got to this point, it means there was no 'name' specified for // If we got to this point, it means there was no 'name' specified for
// searching, implying that this is a group-only search. // searching, implying that this is a group-only search.
$result = []; $result = [];
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
foreach ($childGroup as $child) { foreach ($childGroup as $child) {
if ($child instanceof Property && strtoupper($child->group) === $group) { if ($child instanceof Property && strtoupper($child->group) === $group) {
$result[] = $child; $result[] = $child;
} }
}
}
}
}
return $result; return $result;
} }
/** /**
@ -265,9 +281,9 @@ class Component extends Node
* *
* @return string * @return string
*/ */
public function serialize() function serialize() {
{
$str = 'BEGIN:'.$this->name."\r\n"; $str = "BEGIN:" . $this->name . "\r\n";
/** /**
* Gives a component a 'score' for sorting purposes. * Gives a component a 'score' for sorting purposes.
@ -279,60 +295,60 @@ class Component extends Node
* space to accomodate elements. The $key is added to the $score to * space to accomodate elements. The $key is added to the $score to
* preserve the original relative order of elements. * preserve the original relative order of elements.
* *
* @param int $key * @param int $key
* @param array $array * @param array $array
* *
* @return int * @return int
*/ */
$sortScore = function ($key, $array) { $sortScore = function($key, $array) {
if ($array[$key] instanceof Component) { if ($array[$key] instanceof Component) {
// We want to encode VTIMEZONE first, this is a personal // We want to encode VTIMEZONE first, this is a personal
// preference. // preference.
if ('VTIMEZONE' === $array[$key]->name) { if ($array[$key]->name === 'VTIMEZONE') {
$score = 300000000; $score = 300000000;
return $score + $key; return $score + $key;
} else { } else {
$score = 400000000; $score = 400000000;
return $score + $key; return $score + $key;
} }
} else { } else {
// Properties get encoded first // Properties get encoded first
// VCARD version 4.0 wants the VERSION property to appear first // VCARD version 4.0 wants the VERSION property to appear first
if ($array[$key] instanceof Property) { if ($array[$key] instanceof Property) {
if ('VERSION' === $array[$key]->name) { if ($array[$key]->name === 'VERSION') {
$score = 100000000; $score = 100000000;
return $score + $key; return $score + $key;
} else { } else {
// All other properties // All other properties
$score = 200000000; $score = 200000000;
return $score + $key; return $score + $key;
} }
} }
} }
}; };
$children = $this->children(); $children = $this->children();
$tmp = $children; $tmp = $children;
uksort( uksort(
$children, $children,
function ($a, $b) use ($sortScore, $tmp) { function($a, $b) use ($sortScore, $tmp) {
$sA = $sortScore($a, $tmp); $sA = $sortScore($a, $tmp);
$sB = $sortScore($b, $tmp); $sB = $sortScore($b, $tmp);
return $sA - $sB; return $sA - $sB;
} }
); );
foreach ($children as $child) { foreach ($children as $child) $str .= $child->serialize();
$str .= $child->serialize(); $str .= "END:" . $this->name . "\r\n";
}
$str .= 'END:'.$this->name."\r\n";
return $str; return $str;
} }
/** /**
@ -341,8 +357,8 @@ class Component extends Node
* *
* @return array * @return array
*/ */
public function jsonSerialize() function jsonSerialize() {
{
$components = []; $components = [];
$properties = []; $properties = [];
@ -359,18 +375,21 @@ class Component extends Node
return [ return [
strtolower($this->name), strtolower($this->name),
$properties, $properties,
$components, $components
]; ];
} }
/** /**
* This method serializes the data into XML. This is used to create xCard or * This method serializes the data into XML. This is used to create xCard or
* xCal documents. * xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
public function xmlSerialize(Xml\Writer $writer) function xmlSerialize(Xml\Writer $writer) {
{
$components = []; $components = [];
$properties = []; $properties = [];
@ -387,6 +406,7 @@ class Component extends Node
$writer->startElement(strtolower($this->name)); $writer->startElement(strtolower($this->name));
if (!empty($properties)) { if (!empty($properties)) {
$writer->startElement('properties'); $writer->startElement('properties');
foreach ($properties as $property) { foreach ($properties as $property) {
@ -394,9 +414,11 @@ class Component extends Node
} }
$writer->endElement(); $writer->endElement();
} }
if (!empty($components)) { if (!empty($components)) {
$writer->startElement('components'); $writer->startElement('components');
foreach ($components as $component) { foreach ($components as $component) {
@ -407,6 +429,7 @@ class Component extends Node
} }
$writer->endElement(); $writer->endElement();
} }
/** /**
@ -414,9 +437,10 @@ class Component extends Node
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return []; return [];
} }
/* Magic property accessors {{{ */ /* Magic property accessors {{{ */
@ -435,22 +459,24 @@ class Component extends Node
* *
* @return Property * @return Property
*/ */
public function __get($name) function __get($name) {
{
if ('children' === $name) { if ($name === 'children') {
throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead'); throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');
} }
$matches = $this->select($name); $matches = $this->select($name);
if (0 === count($matches)) { if (count($matches) === 0) {
return; return;
} else { } else {
$firstMatch = current($matches); $firstMatch = current($matches);
/* @var $firstMatch Property */ /** @var $firstMatch Property */
$firstMatch->setIterator(new ElementList(array_values($matches))); $firstMatch->setIterator(new ElementList(array_values($matches)));
return $firstMatch; return $firstMatch;
} }
} }
/** /**
@ -460,11 +486,11 @@ class Component extends Node
* *
* @return bool * @return bool
*/ */
public function __isset($name) function __isset($name) {
{
$matches = $this->select($name);
$matches = $this->select($name);
return count($matches) > 0; return count($matches) > 0;
} }
/** /**
@ -477,10 +503,12 @@ class Component extends Node
* a new item with the same name, always use the add() method. * a new item with the same name, always use the add() method.
* *
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
public function __set($name, $value) function __set($name, $value) {
{
$name = strtoupper($name); $name = strtoupper($name);
$this->remove($name); $this->remove($name);
if ($value instanceof self || $value instanceof Property) { if ($value instanceof self || $value instanceof Property) {
@ -495,10 +523,13 @@ class Component extends Node
* specified name. * specified name.
* *
* @param string $name * @param string $name
*
* @return void
*/ */
public function __unset($name) function __unset($name) {
{
$this->remove($name); $this->remove($name);
} }
/* }}} */ /* }}} */
@ -506,9 +537,11 @@ class Component extends Node
/** /**
* This method is automatically called when the object is cloned. * This method is automatically called when the object is cloned.
* Specifically, this will ensure all child elements are also cloned. * Specifically, this will ensure all child elements are also cloned.
*
* @return void
*/ */
public function __clone() function __clone() {
{
foreach ($this->children as $childName => $childGroup) { foreach ($this->children as $childName => $childGroup) {
foreach ($childGroup as $key => $child) { foreach ($childGroup as $key => $child) {
$clonedChild = clone $child; $clonedChild = clone $child;
@ -517,6 +550,7 @@ class Component extends Node
$this->children[$childName][$key] = $clonedChild; $this->children[$childName][$key] = $clonedChild;
} }
} }
} }
/** /**
@ -540,9 +574,10 @@ class Component extends Node
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return []; return [];
} }
/** /**
@ -569,8 +604,8 @@ class Component extends Node
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$rules = $this->getValidationRules(); $rules = $this->getValidationRules();
$defaults = $this->getDefaults(); $defaults = $this->getDefaults();
@ -583,48 +618,49 @@ class Component extends Node
if (!isset($propertyCounters[$name])) { if (!isset($propertyCounters[$name])) {
$propertyCounters[$name] = 1; $propertyCounters[$name] = 1;
} else { } else {
++$propertyCounters[$name]; $propertyCounters[$name]++;
} }
$messages = array_merge($messages, $child->validate($options)); $messages = array_merge($messages, $child->validate($options));
} }
foreach ($rules as $propName => $rule) { foreach ($rules as $propName => $rule) {
switch ($rule) { switch ($rule) {
case '0': case '0' :
if (isset($propertyCounters[$propName])) { if (isset($propertyCounters[$propName])) {
$messages[] = [ $messages[] = [
'level' => 3, 'level' => 3,
'message' => $propName.' MUST NOT appear in a '.$this->name.' component', 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
'node' => $this, 'node' => $this,
]; ];
} }
break; break;
case '1': case '1' :
if (!isset($propertyCounters[$propName]) || 1 !== $propertyCounters[$propName]) { if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] !== 1) {
$repaired = false; $repaired = false;
if ($options & self::REPAIR && isset($defaults[$propName])) { if ($options & self::REPAIR && isset($defaults[$propName])) {
$this->add($propName, $defaults[$propName]); $this->add($propName, $defaults[$propName]);
$repaired = true; $repaired = true;
} }
$messages[] = [ $messages[] = [
'level' => $repaired ? 1 : 3, 'level' => $repaired ? 1 : 3,
'message' => $propName.' MUST appear exactly once in a '.$this->name.' component', 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
'node' => $this, 'node' => $this,
]; ];
} }
break; break;
case '+': case '+' :
if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) { if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
$messages[] = [ $messages[] = [
'level' => 3, 'level' => 3,
'message' => $propName.' MUST appear at least once in a '.$this->name.' component', 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
'node' => $this, 'node' => $this,
]; ];
} }
break; break;
case '*': case '*' :
break; break;
case '?': case '?' :
if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) { if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
$level = 3; $level = 3;
@ -633,7 +669,7 @@ class Component extends Node
if ($options & self::REPAIR) { if ($options & self::REPAIR) {
$properties = array_unique($this->select($propName), SORT_REGULAR); $properties = array_unique($this->select($propName), SORT_REGULAR);
if (1 === count($properties)) { if (count($properties) === 1) {
$this->remove($propName); $this->remove($propName);
$this->add($properties[0]); $this->add($properties[0]);
@ -642,16 +678,18 @@ class Component extends Node
} }
$messages[] = [ $messages[] = [
'level' => $level, 'level' => $level,
'message' => $propName.' MUST NOT appear more than once in a '.$this->name.' component', 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
'node' => $this, 'node' => $this,
]; ];
} }
break; break;
}
}
}
}
return $messages; return $messages;
} }
/** /**
@ -659,9 +697,11 @@ class Component extends Node
* *
* It's intended to remove all circular references, so PHP can easily clean * It's intended to remove all circular references, so PHP can easily clean
* it up. * it up.
*
* @return void
*/ */
public function destroy() function destroy() {
{
parent::destroy(); parent::destroy();
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
foreach ($childGroup as $child) { foreach ($childGroup as $child) {
@ -669,5 +709,7 @@ class Component extends Node
} }
} }
$this->children = []; $this->children = [];
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\VObject;
* @author Ivan Enderlin * @author Ivan Enderlin
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Available extends VObject\Component class Available extends VObject\Component {
{
/** /**
* Returns the 'effective start' and 'effective end' of this VAVAILABILITY * Returns the 'effective start' and 'effective end' of this VAVAILABILITY
* component. * component.
@ -28,8 +28,8 @@ class Available extends VObject\Component
* *
* @return array * @return array
*/ */
public function getEffectiveStartEnd() function getEffectiveStartEnd() {
{
$effectiveStart = $this->DTSTART->getDateTime(); $effectiveStart = $this->DTSTART->getDateTime();
if (isset($this->DTEND)) { if (isset($this->DTEND)) {
$effectiveEnd = $this->DTEND->getDateTime(); $effectiveEnd = $this->DTEND->getDateTime();
@ -38,6 +38,7 @@ class Available extends VObject\Component
} }
return [$effectiveStart, $effectiveEnd]; return [$effectiveStart, $effectiveEnd];
} }
/** /**
@ -55,31 +56,32 @@ class Available extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTART' => 1, 'DTSTART' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'DTEND' => '?', 'DTEND' => '?',
'DURATION' => '?', 'DURATION' => '?',
'CREATED' => '?', 'CREATED' => '?',
'DESCRIPTION' => '?', 'DESCRIPTION' => '?',
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'RECURRENCE-ID' => '?', 'RECURRENCE-ID' => '?',
'RRULE' => '?', 'RRULE' => '?',
'SUMMARY' => '?', 'SUMMARY' => '?',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'CONTACT' => '*', 'CONTACT' => '*',
'EXDATE' => '*', 'EXDATE' => '*',
'RDATE' => '*', 'RDATE' => '*',
'AVAILABLE' => '*', 'AVAILABLE' => '*',
]; ];
} }
/** /**
@ -106,18 +108,19 @@ class Available extends VObject\Component
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$result = parent::validate($options); $result = parent::validate($options);
if (isset($this->DTEND) && isset($this->DURATION)) { if (isset($this->DTEND) && isset($this->DURATION)) {
$result[] = [ $result[] = [
'level' => 3, 'level' => 3,
'message' => 'DTEND and DURATION cannot both be present', 'message' => 'DTEND and DURATION cannot both be present',
'node' => $this, 'node' => $this
]; ];
} }
return $result; return $result;
} }
} }

View File

@ -16,8 +16,8 @@ use Sabre\VObject\InvalidDataException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VAlarm extends VObject\Component class VAlarm extends VObject\Component {
{
/** /**
* Returns a DateTime object when this alarm is going to trigger. * Returns a DateTime object when this alarm is going to trigger.
* *
@ -25,16 +25,17 @@ class VAlarm extends VObject\Component
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public function getEffectiveTriggerTime() function getEffectiveTriggerTime() {
{
$trigger = $this->TRIGGER; $trigger = $this->TRIGGER;
if (!isset($trigger['VALUE']) || 'DURATION' === strtoupper($trigger['VALUE'])) { if (!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
$related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START'; $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
$parentComponent = $this->parent; $parentComponent = $this->parent;
if ('START' === $related) { if ($related === 'START') {
if ('VTODO' === $parentComponent->name) {
if ($parentComponent->name === 'VTODO') {
$propName = 'DUE'; $propName = 'DUE';
} else { } else {
$propName = 'DTSTART'; $propName = 'DTSTART';
@ -43,9 +44,9 @@ class VAlarm extends VObject\Component
$effectiveTrigger = $parentComponent->$propName->getDateTime(); $effectiveTrigger = $parentComponent->$propName->getDateTime();
$effectiveTrigger = $effectiveTrigger->add($triggerDuration); $effectiveTrigger = $effectiveTrigger->add($triggerDuration);
} else { } else {
if ('VTODO' === $parentComponent->name) { if ($parentComponent->name === 'VTODO') {
$endProp = 'DUE'; $endProp = 'DUE';
} elseif ('VEVENT' === $parentComponent->name) { } elseif ($parentComponent->name === 'VEVENT') {
$endProp = 'DTEND'; $endProp = 'DTEND';
} else { } else {
throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
@ -67,8 +68,8 @@ class VAlarm extends VObject\Component
} else { } else {
$effectiveTrigger = $trigger->getDateTime(); $effectiveTrigger = $trigger->getDateTime();
} }
return $effectiveTrigger; return $effectiveTrigger;
} }
/** /**
@ -83,29 +84,30 @@ class VAlarm extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {
{
$effectiveTrigger = $this->getEffectiveTriggerTime(); $effectiveTrigger = $this->getEffectiveTriggerTime();
if (isset($this->DURATION)) { if (isset($this->DURATION)) {
$duration = VObject\DateTimeParser::parseDuration($this->DURATION); $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
$repeat = (string) $this->REPEAT; $repeat = (string)$this->REPEAT;
if (!$repeat) { if (!$repeat) {
$repeat = 1; $repeat = 1;
} }
$period = new \DatePeriod($effectiveTrigger, $duration, (int) $repeat); $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
foreach ($period as $occurrence) { foreach ($period as $occurrence) {
if ($start <= $occurrence && $end > $occurrence) { if ($start <= $occurrence && $end > $occurrence) {
return true; return true;
} }
} }
return false; return false;
} else { } else {
return $start <= $effectiveTrigger && $end > $effectiveTrigger; return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
} }
} }
/** /**
@ -123,16 +125,18 @@ class VAlarm extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'ACTION' => 1, 'ACTION' => 1,
'TRIGGER' => 1, 'TRIGGER' => 1,
'DURATION' => '?', 'DURATION' => '?',
'REPEAT' => '?', 'REPEAT' => '?',
'ATTACH' => '?', 'ATTACH' => '?',
]; ];
} }
} }

View File

@ -15,8 +15,8 @@ use Sabre\VObject;
* @author Ivan Enderlin * @author Ivan Enderlin
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VAvailability extends VObject\Component class VAvailability extends VObject\Component {
{
/** /**
* Returns true or false depending on if the event falls in the specified * Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes. * time-range. This is used for filtering purposes.
@ -31,14 +31,14 @@ class VAvailability extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {
{
list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
return list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
return (
(is_null($effectiveStart) || $start < $effectiveEnd) && (is_null($effectiveStart) || $start < $effectiveEnd) &&
(is_null($effectiveEnd) || $end > $effectiveStart) (is_null($effectiveEnd) || $end > $effectiveStart)
; );
} }
/** /**
@ -53,8 +53,8 @@ class VAvailability extends VObject\Component
* *
* @return array * @return array
*/ */
public function getEffectiveStartEnd() function getEffectiveStartEnd() {
{
$effectiveStart = null; $effectiveStart = null;
$effectiveEnd = null; $effectiveEnd = null;
@ -68,8 +68,10 @@ class VAvailability extends VObject\Component
} }
return [$effectiveStart, $effectiveEnd]; return [$effectiveStart, $effectiveEnd];
} }
/** /**
* A simple list of validation rules. * A simple list of validation rules.
* *
@ -85,30 +87,31 @@ class VAvailability extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'BUSYTYPE' => '?', 'BUSYTYPE' => '?',
'CLASS' => '?', 'CLASS' => '?',
'CREATED' => '?', 'CREATED' => '?',
'DESCRIPTION' => '?', 'DESCRIPTION' => '?',
'DTSTART' => '?', 'DTSTART' => '?',
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'ORGANIZER' => '?', 'ORGANIZER' => '?',
'PRIORITY' => '?', 'PRIORITY' => '?',
'SEQUENCE' => '?', 'SEQUENCE' => '?',
'SUMMARY' => '?', 'SUMMARY' => '?',
'URL' => '?', 'URL' => '?',
'DTEND' => '?', 'DTEND' => '?',
'DURATION' => '?', 'DURATION' => '?',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'CONTACT' => '*', 'CONTACT' => '*',
]; ];
} }
/** /**
@ -135,18 +138,19 @@ class VAvailability extends VObject\Component
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$result = parent::validate($options); $result = parent::validate($options);
if (isset($this->DTEND) && isset($this->DURATION)) { if (isset($this->DTEND) && isset($this->DURATION)) {
$result[] = [ $result[] = [
'level' => 3, 'level' => 3,
'message' => 'DTEND and DURATION cannot both be present', 'message' => 'DTEND and DURATION cannot both be present',
'node' => $this, 'node' => $this
]; ];
} }
return $result; return $result;
} }
} }

View File

@ -20,8 +20,8 @@ use Sabre\VObject\Recur\NoInstancesException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VCalendar extends VObject\Document class VCalendar extends VObject\Document {
{
/** /**
* The default name for this component. * The default name for this component.
* *
@ -29,23 +29,23 @@ class VCalendar extends VObject\Document
* *
* @var string * @var string
*/ */
public static $defaultName = 'VCALENDAR'; static $defaultName = 'VCALENDAR';
/** /**
* This is a list of components, and which classes they should map to. * This is a list of components, and which classes they should map to.
* *
* @var array * @var array
*/ */
public static $componentMap = [ static $componentMap = [
'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar', 'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar',
'VALARM' => 'Sabre\\VObject\\Component\\VAlarm', 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm',
'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability', 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
'AVAILABLE' => 'Sabre\\VObject\\Component\\Available', 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available',
'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone',
'VTODO' => 'Sabre\\VObject\\Component\\VTodo', 'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
]; ];
/** /**
@ -53,22 +53,22 @@ class VCalendar extends VObject\Document
* *
* @var array * @var array
*/ */
public static $valueMap = [ static $valueMap = [
'BINARY' => 'Sabre\\VObject\\Property\\Binary', 'BINARY' => 'Sabre\\VObject\\Property\\Binary',
'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date',
'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
'TEXT' => 'Sabre\\VObject\\Property\\Text', 'TEXT' => 'Sabre\\VObject\\Property\\Text',
'TIME' => 'Sabre\\VObject\\Property\\Time', 'TIME' => 'Sabre\\VObject\\Property\\Time',
'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
'URI' => 'Sabre\\VObject\\Property\\Uri', 'URI' => 'Sabre\\VObject\\Property\\Uri',
'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
]; ];
/** /**
@ -76,80 +76,81 @@ class VCalendar extends VObject\Document
* *
* @var array * @var array
*/ */
public static $propertyMap = [ static $propertyMap = [
// Calendar properties // Calendar properties
'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
'METHOD' => 'Sabre\\VObject\\Property\\FlatText', 'METHOD' => 'Sabre\\VObject\\Property\\FlatText',
'PRODID' => 'Sabre\\VObject\\Property\\FlatText', 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
'VERSION' => 'Sabre\\VObject\\Property\\FlatText', 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
// Component properties // Component properties
'ATTACH' => 'Sabre\\VObject\\Property\\Uri', 'ATTACH' => 'Sabre\\VObject\\Property\\Uri',
'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
'CLASS' => 'Sabre\\VObject\\Property\\FlatText', 'CLASS' => 'Sabre\\VObject\\Property\\FlatText',
'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText',
'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText',
'GEO' => 'Sabre\\VObject\\Property\\FloatValue', 'GEO' => 'Sabre\\VObject\\Property\\FloatValue',
'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText',
'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue', 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue',
'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue', 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue',
'RESOURCES' => 'Sabre\\VObject\\Property\\Text', 'RESOURCES' => 'Sabre\\VObject\\Property\\Text',
'STATUS' => 'Sabre\\VObject\\Property\\FlatText', 'STATUS' => 'Sabre\\VObject\\Property\\FlatText',
'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText',
// Date and Time Component Properties // Date and Time Component Properties
'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText',
// Time Zone Component Properties // Time Zone Component Properties
'TZID' => 'Sabre\\VObject\\Property\\FlatText', 'TZID' => 'Sabre\\VObject\\Property\\FlatText',
'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText',
'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset',
'TZURL' => 'Sabre\\VObject\\Property\\Uri', 'TZURL' => 'Sabre\\VObject\\Property\\Uri',
// Relationship Component Properties // Relationship Component Properties
'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText',
'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText',
'URL' => 'Sabre\\VObject\\Property\\Uri', 'URL' => 'Sabre\\VObject\\Property\\Uri',
'UID' => 'Sabre\\VObject\\Property\\FlatText', 'UID' => 'Sabre\\VObject\\Property\\FlatText',
// Recurrence Component Properties // Recurrence Component Properties
'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
// Alarm Component Properties // Alarm Component Properties
'ACTION' => 'Sabre\\VObject\\Property\\FlatText', 'ACTION' => 'Sabre\\VObject\\Property\\FlatText',
'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue', 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue',
'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
// Change Management Component Properties // Change Management Component Properties
'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue', 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue',
// Request Status // Request Status
'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
// Additions from draft-daboo-valarm-extensions-04 // Additions from draft-daboo-valarm-extensions-04
'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text',
'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text',
'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',
// Additions from draft-daboo-calendar-availability-05 // Additions from draft-daboo-calendar-availability-05
'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text', 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text',
]; ];
/** /**
@ -157,9 +158,10 @@ class VCalendar extends VObject\Document
* *
* @return int * @return int
*/ */
public function getDocumentType() function getDocumentType() {
{
return self::ICALENDAR20; return self::ICALENDAR20;
} }
/** /**
@ -173,20 +175,21 @@ class VCalendar extends VObject\Document
* *
* @return VObject\Component[] * @return VObject\Component[]
*/ */
public function getBaseComponents($componentName = null) function getBaseComponents($componentName = null) {
{
$isBaseComponent = function ($component) { $isBaseComponent = function($component) {
if (!$component instanceof VObject\Component) { if (!$component instanceof VObject\Component) {
return false; return false;
} }
if ('VTIMEZONE' === $component->name) { if ($component->name === 'VTIMEZONE') {
return false; return false;
} }
if (isset($component->{'RECURRENCE-ID'})) { if (isset($component->{'RECURRENCE-ID'})) {
return false; return false;
} }
return true; return true;
}; };
if ($componentName) { if ($componentName) {
@ -199,7 +202,9 @@ class VCalendar extends VObject\Document
$components = []; $components = [];
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
foreach ($childGroup as $child) { foreach ($childGroup as $child) {
if (!$child instanceof Component) { if (!$child instanceof Component) {
// If one child is not a component, they all are so we skip // If one child is not a component, they all are so we skip
// the entire group. // the entire group.
@ -208,10 +213,12 @@ class VCalendar extends VObject\Document
if ($isBaseComponent($child)) { if ($isBaseComponent($child)) {
$components[] = $child; $components[] = $child;
} }
}
}
}
}
return $components; return $components;
} }
/** /**
@ -224,20 +231,21 @@ class VCalendar extends VObject\Document
* *
* @return VObject\Component|null * @return VObject\Component|null
*/ */
public function getBaseComponent($componentName = null) function getBaseComponent($componentName = null) {
{
$isBaseComponent = function ($component) { $isBaseComponent = function($component) {
if (!$component instanceof VObject\Component) { if (!$component instanceof VObject\Component) {
return false; return false;
} }
if ('VTIMEZONE' === $component->name) { if ($component->name === 'VTIMEZONE') {
return false; return false;
} }
if (isset($component->{'RECURRENCE-ID'})) { if (isset($component->{'RECURRENCE-ID'})) {
return false; return false;
} }
return true; return true;
}; };
if ($componentName) { if ($componentName) {
@ -246,7 +254,6 @@ class VCalendar extends VObject\Document
return $child; return $child;
} }
} }
return null; return null;
} }
@ -257,9 +264,10 @@ class VCalendar extends VObject\Document
return $child; return $child;
} }
} }
}
}
return null; return null;
} }
/** /**
@ -269,7 +277,7 @@ class VCalendar extends VObject\Document
* If this calendar object, has events with recurrence rules, this method * If this calendar object, has events with recurrence rules, this method
* can be used to expand the event into multiple sub-events. * can be used to expand the event into multiple sub-events.
* *
* Each event will be stripped from its recurrence information, and only * Each event will be stripped from it's recurrence information, and only
* the instances of the event in the specified timerange will be left * the instances of the event in the specified timerange will be left
* alone. * alone.
* *
@ -278,13 +286,12 @@ class VCalendar extends VObject\Document
* *
* @param DateTimeInterface $start * @param DateTimeInterface $start
* @param DateTimeInterface $end * @param DateTimeInterface $end
* @param DateTimeZone $timeZone reference timezone for floating dates and * @param DateTimeZone $timeZone reference timezone for floating dates and
* times * times.
*
* @return VCalendar * @return VCalendar
*/ */
public function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) {
{
$newChildren = []; $newChildren = [];
$recurringEvents = []; $recurringEvents = [];
@ -292,9 +299,11 @@ class VCalendar extends VObject\Document
$timeZone = new DateTimeZone('UTC'); $timeZone = new DateTimeZone('UTC');
} }
$stripTimezones = function (Component $component) use ($timeZone, &$stripTimezones) { $stripTimezones = function(Component $component) use ($timeZone, &$stripTimezones) {
foreach ($component->children() as $componentChild) { foreach ($component->children() as $componentChild) {
if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) { if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {
$dt = $componentChild->getDateTimes($timeZone); $dt = $componentChild->getDateTimes($timeZone);
// We only need to update the first timezone, because // We only need to update the first timezone, because
// setDateTimes will match all other timezones to the // setDateTimes will match all other timezones to the
@ -304,22 +313,25 @@ class VCalendar extends VObject\Document
} elseif ($componentChild instanceof Component) { } elseif ($componentChild instanceof Component) {
$stripTimezones($componentChild); $stripTimezones($componentChild);
} }
}
}
return $component; return $component;
}; };
foreach ($this->children() as $child) { foreach ($this->children() as $child) {
if ($child instanceof Property && 'PRODID' !== $child->name) {
if ($child instanceof Property && $child->name !== 'PRODID') {
// We explictly want to ignore PRODID, because we want to // We explictly want to ignore PRODID, because we want to
// overwrite it with our own. // overwrite it with our own.
$newChildren[] = clone $child; $newChildren[] = clone $child;
} elseif ($child instanceof Component && 'VTIMEZONE' !== $child->name) { } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') {
// We're also stripping all VTIMEZONE objects because we're // We're also stripping all VTIMEZONE objects because we're
// converting everything to UTC. // converting everything to UTC.
if ('VEVENT' === $child->name && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) { if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
// Handle these a bit later. // Handle these a bit later.
$uid = (string) $child->UID; $uid = (string)$child->UID;
if (!$uid) { if (!$uid) {
throw new InvalidDataException('Every VEVENT object must have a UID property'); throw new InvalidDataException('Every VEVENT object must have a UID property');
} }
@ -328,15 +340,19 @@ class VCalendar extends VObject\Document
} else { } else {
$recurringEvents[$uid] = [clone $child]; $recurringEvents[$uid] = [clone $child];
} }
} elseif ('VEVENT' === $child->name && $child->isInTimeRange($start, $end)) { } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) {
$newChildren[] = $stripTimezones(clone $child); $newChildren[] = $stripTimezones(clone $child);
} }
} }
} }
foreach ($recurringEvents as $events) { foreach ($recurringEvents as $events) {
try { try {
$it = new EventIterator($events, $timeZone); $it = new EventIterator($events, $timeZone);
} catch (NoInstancesException $e) { } catch (NoInstancesException $e) {
// This event is recurring, but it doesn't have a single // This event is recurring, but it doesn't have a single
// instance. We are skipping this event from the output // instance. We are skipping this event from the output
@ -346,14 +362,20 @@ class VCalendar extends VObject\Document
$it->fastForward($start); $it->fastForward($start);
while ($it->valid() && $it->getDTStart() < $end) { while ($it->valid() && $it->getDTStart() < $end) {
if ($it->getDTEnd() > $start) { if ($it->getDTEnd() > $start) {
$newChildren[] = $stripTimezones($it->getEventObject()); $newChildren[] = $stripTimezones($it->getEventObject());
} }
$it->next(); $it->next();
} }
} }
return new self($newChildren); return new self($newChildren);
} }
/** /**
@ -361,13 +383,14 @@ class VCalendar extends VObject\Document
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return [ return [
'VERSION' => '2.0', 'VERSION' => '2.0',
'PRODID' => '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN', 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
'CALSCALE' => 'GREGORIAN', 'CALSCALE' => 'GREGORIAN',
]; ];
} }
/** /**
@ -385,15 +408,16 @@ class VCalendar extends VObject\Document
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'PRODID' => 1, 'PRODID' => 1,
'VERSION' => 1, 'VERSION' => 1,
'CALSCALE' => '?', 'CALSCALE' => '?',
'METHOD' => '?', 'METHOD' => '?',
]; ];
} }
/** /**
@ -420,18 +444,19 @@ class VCalendar extends VObject\Document
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$warnings = parent::validate($options); $warnings = parent::validate($options);
if ($ver = $this->VERSION) { if ($ver = $this->VERSION) {
if ('2.0' !== (string) $ver) { if ((string)$ver !== '2.0') {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
'node' => $this, 'node' => $this,
]; ];
} }
} }
$uidList = []; $uidList = [];
@ -440,75 +465,77 @@ class VCalendar extends VObject\Document
foreach ($this->children() as $child) { foreach ($this->children() as $child) {
if ($child instanceof Component) { if ($child instanceof Component) {
++$componentsFound; $componentsFound++;
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) { if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
continue; continue;
} }
$componentTypes[] = $child->name; $componentTypes[] = $child->name;
$uid = (string) $child->UID; $uid = (string)$child->UID;
$isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1; $isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1;
if (isset($uidList[$uid])) { if (isset($uidList[$uid])) {
++$uidList[$uid]['count']; $uidList[$uid]['count']++;
if ($isMaster && $uidList[$uid]['hasMaster']) { if ($isMaster && $uidList[$uid]['hasMaster']) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'More than one master object was found for the object with UID '.$uid, 'message' => 'More than one master object was found for the object with UID ' . $uid,
'node' => $this, 'node' => $this,
]; ];
} }
$uidList[$uid]['hasMaster'] += $isMaster; $uidList[$uid]['hasMaster'] += $isMaster;
} else { } else {
$uidList[$uid] = [ $uidList[$uid] = [
'count' => 1, 'count' => 1,
'hasMaster' => $isMaster, 'hasMaster' => $isMaster,
]; ];
} }
} }
} }
if (0 === $componentsFound) { if ($componentsFound === 0) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'An iCalendar object must have at least 1 component.', 'message' => 'An iCalendar object must have at least 1 component.',
'node' => $this, 'node' => $this,
]; ];
} }
if ($options & self::PROFILE_CALDAV) { if ($options & self::PROFILE_CALDAV) {
if (count($uidList) > 1) { if (count($uidList) > 1) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
'node' => $this, 'node' => $this,
]; ];
} }
if (0 === count($componentTypes)) { if (count($componentTypes) === 0) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
'node' => $this, 'node' => $this,
]; ];
} }
if (count(array_unique($componentTypes)) > 1) { if (count(array_unique($componentTypes)) > 1) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
'node' => $this, 'node' => $this,
]; ];
} }
if (isset($this->METHOD)) { if (isset($this->METHOD)) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
'node' => $this, 'node' => $this,
]; ];
} }
} }
return $warnings; return $warnings;
} }
/** /**
@ -516,15 +543,19 @@ class VCalendar extends VObject\Document
* *
* @return array * @return array
*/ */
public function getByUID($uid) function getByUID($uid) {
{
return array_filter($this->getComponents(), function ($item) use ($uid) { return array_filter($this->getComponents(), function($item) use ($uid) {
if (!$itemUid = $item->select('UID')) { if (!$itemUid = $item->select('UID')) {
return false; return false;
} }
$itemUid = current($itemUid)->getValue(); $itemUid = current($itemUid)->getValue();
return $uid === $itemUid; return $uid === $itemUid;
}); });
} }
} }

View File

@ -15,8 +15,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VCard extends VObject\Document class VCard extends VObject\Document {
{
/** /**
* The default name for this component. * The default name for this component.
* *
@ -24,7 +24,7 @@ class VCard extends VObject\Document
* *
* @var string * @var string
*/ */
public static $defaultName = 'VCARD'; static $defaultName = 'VCARD';
/** /**
* Caching the version number. * Caching the version number.
@ -38,7 +38,7 @@ class VCard extends VObject\Document
* *
* @var array * @var array
*/ */
public static $componentMap = [ static $componentMap = [
'VCARD' => 'Sabre\\VObject\\Component\\VCard', 'VCARD' => 'Sabre\\VObject\\Component\\VCard',
]; ];
@ -47,23 +47,23 @@ class VCard extends VObject\Document
* *
* @var array * @var array
*/ */
public static $valueMap = [ static $valueMap = [
'BINARY' => 'Sabre\\VObject\\Property\\Binary', 'BINARY' => 'Sabre\\VObject\\Property\\Binary',
'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only
'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date', 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date',
'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime', 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime',
'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
'TEXT' => 'Sabre\\VObject\\Property\\Text', 'TEXT' => 'Sabre\\VObject\\Property\\Text',
'TIME' => 'Sabre\\VObject\\Property\\Time', 'TIME' => 'Sabre\\VObject\\Property\\Time',
'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
'URI' => 'Sabre\\VObject\\Property\\Uri', 'URI' => 'Sabre\\VObject\\Property\\Uri',
'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
]; ];
/** /**
@ -71,70 +71,72 @@ class VCard extends VObject\Document
* *
* @var array * @var array
*/ */
public static $propertyMap = [ static $propertyMap = [
// vCard 2.1 properties and up // vCard 2.1 properties and up
'N' => 'Sabre\\VObject\\Property\\Text', 'N' => 'Sabre\\VObject\\Property\\Text',
'FN' => 'Sabre\\VObject\\Property\\FlatText', 'FN' => 'Sabre\\VObject\\Property\\FlatText',
'PHOTO' => 'Sabre\\VObject\\Property\\Binary', 'PHOTO' => 'Sabre\\VObject\\Property\\Binary',
'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
'ADR' => 'Sabre\\VObject\\Property\\Text', 'ADR' => 'Sabre\\VObject\\Property\\Text',
'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
'TEL' => 'Sabre\\VObject\\Property\\FlatText', 'TEL' => 'Sabre\\VObject\\Property\\FlatText',
'EMAIL' => 'Sabre\\VObject\\Property\\FlatText', 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText',
'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
'GEO' => 'Sabre\\VObject\\Property\\FlatText', 'GEO' => 'Sabre\\VObject\\Property\\FlatText',
'TITLE' => 'Sabre\\VObject\\Property\\FlatText', 'TITLE' => 'Sabre\\VObject\\Property\\FlatText',
'ROLE' => 'Sabre\\VObject\\Property\\FlatText', 'ROLE' => 'Sabre\\VObject\\Property\\FlatText',
'LOGO' => 'Sabre\\VObject\\Property\\Binary', 'LOGO' => 'Sabre\\VObject\\Property\\Binary',
// 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so
// not supported at the moment // not supported at the moment
'ORG' => 'Sabre\\VObject\\Property\\Text', 'ORG' => 'Sabre\\VObject\\Property\\Text',
'NOTE' => 'Sabre\\VObject\\Property\\FlatText', 'NOTE' => 'Sabre\\VObject\\Property\\FlatText',
'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
'SOUND' => 'Sabre\\VObject\\Property\\FlatText', 'SOUND' => 'Sabre\\VObject\\Property\\FlatText',
'URL' => 'Sabre\\VObject\\Property\\Uri', 'URL' => 'Sabre\\VObject\\Property\\Uri',
'UID' => 'Sabre\\VObject\\Property\\FlatText', 'UID' => 'Sabre\\VObject\\Property\\FlatText',
'VERSION' => 'Sabre\\VObject\\Property\\FlatText', 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
'KEY' => 'Sabre\\VObject\\Property\\FlatText', 'KEY' => 'Sabre\\VObject\\Property\\FlatText',
'TZ' => 'Sabre\\VObject\\Property\\Text', 'TZ' => 'Sabre\\VObject\\Property\\Text',
// vCard 3.0 properties // vCard 3.0 properties
'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText', 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
'PRODID' => 'Sabre\\VObject\\Property\\FlatText', 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
'NICKNAME' => 'Sabre\\VObject\\Property\\Text', 'NICKNAME' => 'Sabre\\VObject\\Property\\Text',
'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
// rfc2739 properties // rfc2739 properties
'FBURL' => 'Sabre\\VObject\\Property\\Uri', 'FBURL' => 'Sabre\\VObject\\Property\\Uri',
'CAPURI' => 'Sabre\\VObject\\Property\\Uri', 'CAPURI' => 'Sabre\\VObject\\Property\\Uri',
'CALURI' => 'Sabre\\VObject\\Property\\Uri', 'CALURI' => 'Sabre\\VObject\\Property\\Uri',
'CALADRURI' => 'Sabre\\VObject\\Property\\Uri', 'CALADRURI' => 'Sabre\\VObject\\Property\\Uri',
// rfc4770 properties // rfc4770 properties
'IMPP' => 'Sabre\\VObject\\Property\\Uri', 'IMPP' => 'Sabre\\VObject\\Property\\Uri',
// vCard 4.0 properties // vCard 4.0 properties
'SOURCE' => 'Sabre\\VObject\\Property\\Uri', 'SOURCE' => 'Sabre\\VObject\\Property\\Uri',
'XML' => 'Sabre\\VObject\\Property\\FlatText', 'XML' => 'Sabre\\VObject\\Property\\FlatText',
'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text', 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
'GENDER' => 'Sabre\\VObject\\Property\\Text', 'GENDER' => 'Sabre\\VObject\\Property\\Text',
'KIND' => 'Sabre\\VObject\\Property\\FlatText', 'KIND' => 'Sabre\\VObject\\Property\\FlatText',
'MEMBER' => 'Sabre\\VObject\\Property\\Uri', 'MEMBER' => 'Sabre\\VObject\\Property\\Uri',
'RELATED' => 'Sabre\\VObject\\Property\\Uri', 'RELATED' => 'Sabre\\VObject\\Property\\Uri',
// rfc6474 properties // rfc6474 properties
'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText', 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText', 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
// rfc6715 properties // rfc6715 properties
'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText', 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText',
'HOBBY' => 'Sabre\\VObject\\Property\\FlatText', 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText',
'INTEREST' => 'Sabre\\VObject\\Property\\FlatText', 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText',
'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText', 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText',
]; ];
/** /**
@ -142,22 +144,23 @@ class VCard extends VObject\Document
* *
* @return int * @return int
*/ */
public function getDocumentType() function getDocumentType() {
{
if (!$this->version) { if (!$this->version) {
$version = (string) $this->VERSION;
$version = (string)$this->VERSION;
switch ($version) { switch ($version) {
case '2.1': case '2.1' :
$this->version = self::VCARD21; $this->version = self::VCARD21;
break; break;
case '3.0': case '3.0' :
$this->version = self::VCARD30; $this->version = self::VCARD30;
break; break;
case '4.0': case '4.0' :
$this->version = self::VCARD40; $this->version = self::VCARD40;
break; break;
default: default :
// We don't want to cache the version if it's unknown, // We don't want to cache the version if it's unknown,
// because we might get a version property in a bit. // because we might get a version property in a bit.
return self::UNKNOWN; return self::UNKNOWN;
@ -165,6 +168,7 @@ class VCard extends VObject\Document
} }
return $this->version; return $this->version;
} }
/** /**
@ -181,11 +185,11 @@ class VCard extends VObject\Document
* *
* @return VCard * @return VCard
*/ */
public function convert($target) function convert($target) {
{
$converter = new VObject\VCardConverter();
$converter = new VObject\VCardConverter();
return $converter->convert($this, $target); return $converter->convert($this, $target);
} }
/** /**
@ -217,8 +221,8 @@ class VCard extends VObject\Document
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$warnings = []; $warnings = [];
$versionMap = [ $versionMap = [
@ -228,28 +232,29 @@ class VCard extends VObject\Document
]; ];
$version = $this->select('VERSION'); $version = $this->select('VERSION');
if (1 === count($version)) { if (count($version) === 1) {
$version = (string) $this->VERSION; $version = (string)$this->VERSION;
if ('2.1' !== $version && '3.0' !== $version && '4.0' !== $version) { if ($version !== '2.1' && $version !== '3.0' && $version !== '4.0') {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
'node' => $this, 'node' => $this,
]; ];
if ($options & self::REPAIR) { if ($options & self::REPAIR) {
$this->VERSION = $versionMap[self::DEFAULT_VERSION]; $this->VERSION = $versionMap[self::DEFAULT_VERSION];
} }
} }
if ('2.1' === $version && ($options & self::PROFILE_CARDDAV)) { if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'CardDAV servers are not allowed to accept vCard 2.1.', 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
'node' => $this, 'node' => $this,
]; ];
} }
} }
$uid = $this->select('UID'); $uid = $this->select('UID');
if (0 === count($uid)) { if (count($uid) === 0) {
if ($options & self::PROFILE_CARDDAV) { if ($options & self::PROFILE_CARDDAV) {
// Required for CardDAV // Required for CardDAV
$warningLevel = 3; $warningLevel = 3;
@ -264,22 +269,23 @@ class VCard extends VObject\Document
$warningLevel = 1; $warningLevel = 1;
} }
$warnings[] = [ $warnings[] = [
'level' => $warningLevel, 'level' => $warningLevel,
'message' => $message, 'message' => $message,
'node' => $this, 'node' => $this,
]; ];
} }
$fn = $this->select('FN'); $fn = $this->select('FN');
if (1 !== count($fn)) { if (count($fn) !== 1) {
$repaired = false; $repaired = false;
if (($options & self::REPAIR) && 0 === count($fn)) { if (($options & self::REPAIR) && count($fn) === 0) {
// We're going to try to see if we can use the contents of the // We're going to try to see if we can use the contents of the
// N property. // N property.
if (isset($this->N)) { if (isset($this->N)) {
$value = explode(';', (string) $this->N); $value = explode(';', (string)$this->N);
if (isset($value[1]) && $value[1]) { if (isset($value[1]) && $value[1]) {
$this->FN = $value[1].' '.$value[0]; $this->FN = $value[1] . ' ' . $value[0];
} else { } else {
$this->FN = $value[0]; $this->FN = $value[0];
} }
@ -287,19 +293,20 @@ class VCard extends VObject\Document
// Otherwise, the ORG property may work // Otherwise, the ORG property may work
} elseif (isset($this->ORG)) { } elseif (isset($this->ORG)) {
$this->FN = (string) $this->ORG; $this->FN = (string)$this->ORG;
$repaired = true; $repaired = true;
// Otherwise, the EMAIL property may work // Otherwise, the EMAIL property may work
} elseif (isset($this->EMAIL)) { } elseif (isset($this->EMAIL)) {
$this->FN = (string) $this->EMAIL; $this->FN = (string)$this->EMAIL;
$repaired = true; $repaired = true;
} }
} }
$warnings[] = [ $warnings[] = [
'level' => $repaired ? 1 : 3, 'level' => $repaired ? 1 : 3,
'message' => 'The FN property must appear in the VCARD component exactly 1 time', 'message' => 'The FN property must appear in the VCARD component exactly 1 time',
'node' => $this, 'node' => $this,
]; ];
} }
@ -307,6 +314,7 @@ class VCard extends VObject\Document
parent::validate($options), parent::validate($options),
$warnings $warnings
); );
} }
/** /**
@ -324,49 +332,50 @@ class VCard extends VObject\Document
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'ADR' => '*', 'ADR' => '*',
'ANNIVERSARY' => '?', 'ANNIVERSARY' => '?',
'BDAY' => '?', 'BDAY' => '?',
'CALADRURI' => '*', 'CALADRURI' => '*',
'CALURI' => '*', 'CALURI' => '*',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'CLIENTPIDMAP' => '*', 'CLIENTPIDMAP' => '*',
'EMAIL' => '*', 'EMAIL' => '*',
'FBURL' => '*', 'FBURL' => '*',
'IMPP' => '*', 'IMPP' => '*',
'GENDER' => '?', 'GENDER' => '?',
'GEO' => '*', 'GEO' => '*',
'KEY' => '*', 'KEY' => '*',
'KIND' => '?', 'KIND' => '?',
'LANG' => '*', 'LANG' => '*',
'LOGO' => '*', 'LOGO' => '*',
'MEMBER' => '*', 'MEMBER' => '*',
'N' => '?', 'N' => '?',
'NICKNAME' => '*', 'NICKNAME' => '*',
'NOTE' => '*', 'NOTE' => '*',
'ORG' => '*', 'ORG' => '*',
'PHOTO' => '*', 'PHOTO' => '*',
'PRODID' => '?', 'PRODID' => '?',
'RELATED' => '*', 'RELATED' => '*',
'REV' => '?', 'REV' => '?',
'ROLE' => '*', 'ROLE' => '*',
'SOUND' => '*', 'SOUND' => '*',
'SOURCE' => '*', 'SOURCE' => '*',
'TEL' => '*', 'TEL' => '*',
'TITLE' => '*', 'TITLE' => '*',
'TZ' => '*', 'TZ' => '*',
'URL' => '*', 'URL' => '*',
'VERSION' => '1', 'VERSION' => '1',
'XML' => '*', 'XML' => '*',
// FN is commented out, because it's already handled by the // FN is commented out, because it's already handled by the
// validate function, which may also try to repair it. // validate function, which may also try to repair it.
// 'FN' => '+', // 'FN' => '+',
'UID' => '?', 'UID' => '?',
]; ];
} }
/** /**
@ -383,11 +392,12 @@ class VCard extends VObject\Document
* *
* @return VObject\Property|null * @return VObject\Property|null
*/ */
public function preferred($propertyName) function preferred($propertyName) {
{
$preferred = null; $preferred = null;
$lastPref = 101; $lastPref = 101;
foreach ($this->select($propertyName) as $field) { foreach ($this->select($propertyName) as $field) {
$pref = 101; $pref = 101;
if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) { if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
$pref = 1; $pref = 1;
@ -399,9 +409,10 @@ class VCard extends VObject\Document
$preferred = $field; $preferred = $field;
$lastPref = $pref; $lastPref = $pref;
} }
}
}
return $preferred; return $preferred;
} }
/** /**
@ -415,8 +426,7 @@ class VCard extends VObject\Document
* *
* @return VObject\Property|null * @return VObject\Property|null
*/ */
public function getByType($propertyName, $type) function getByType($propertyName, $type) {
{
foreach ($this->select($propertyName) as $field) { foreach ($this->select($propertyName) as $field) {
if (isset($field['TYPE']) && $field['TYPE']->has($type)) { if (isset($field['TYPE']) && $field['TYPE']->has($type)) {
return $field; return $field;
@ -429,13 +439,14 @@ class VCard extends VObject\Document
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return [ return [
'VERSION' => '4.0', 'VERSION' => '4.0',
'PRODID' => '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN', 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
]; ];
} }
/** /**
@ -444,8 +455,8 @@ class VCard extends VObject\Document
* *
* @return array * @return array
*/ */
public function jsonSerialize() function jsonSerialize() {
{
// A vcard does not have sub-components, so we're overriding this // A vcard does not have sub-components, so we're overriding this
// method to remove that array element. // method to remove that array element.
$properties = []; $properties = [];
@ -458,19 +469,23 @@ class VCard extends VObject\Document
strtolower($this->name), strtolower($this->name),
$properties, $properties,
]; ];
} }
/** /**
* This method serializes the data into XML. This is used to create xCard or * This method serializes the data into XML. This is used to create xCard or
* xCal documents. * xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
public function xmlSerialize(Xml\Writer $writer) function xmlSerialize(Xml\Writer $writer) {
{
$propertiesByGroup = []; $propertiesByGroup = [];
foreach ($this->children() as $property) { foreach ($this->children() as $property) {
$group = $property->group; $group = $property->group;
if (!isset($propertiesByGroup[$group])) { if (!isset($propertiesByGroup[$group])) {
@ -478,20 +493,25 @@ class VCard extends VObject\Document
} }
$propertiesByGroup[$group][] = $property; $propertiesByGroup[$group][] = $property;
} }
$writer->startElement(strtolower($this->name)); $writer->startElement(strtolower($this->name));
foreach ($propertiesByGroup as $group => $properties) { foreach ($propertiesByGroup as $group => $properties) {
if (!empty($group)) { if (!empty($group)) {
$writer->startElement('group'); $writer->startElement('group');
$writer->writeAttribute('name', strtolower($group)); $writer->writeAttribute('name', strtolower($group));
} }
foreach ($properties as $property) { foreach ($properties as $property) {
switch ($property->name) { switch ($property->name) {
case 'VERSION': case 'VERSION':
break; continue;
case 'XML': case 'XML':
$value = $property->getParts(); $value = $property->getParts();
@ -502,15 +522,18 @@ class VCard extends VObject\Document
default: default:
$property->xmlSerialize($writer); $property->xmlSerialize($writer);
break; break;
} }
} }
if (!empty($group)) { if (!empty($group)) {
$writer->endElement(); $writer->endElement();
} }
} }
$writer->endElement(); $writer->endElement();
} }
/** /**
@ -520,15 +543,16 @@ class VCard extends VObject\Document
* *
* @return string * @return string
*/ */
public function getClassNameForPropertyName($propertyName) function getClassNameForPropertyName($propertyName) {
{
$className = parent::getClassNameForPropertyName($propertyName); $className = parent::getClassNameForPropertyName($propertyName);
// In vCard 4, BINARY no longer exists, and we need URI instead. // In vCard 4, BINARY no longer exists, and we need URI instead.
if ('Sabre\\VObject\\Property\\Binary' == $className && self::VCARD40 === $this->getDocumentType()) { if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType() === self::VCARD40) {
return 'Sabre\\VObject\\Property\\Uri'; return 'Sabre\\VObject\\Property\\Uri';
} }
return $className; return $className;
} }
} }

View File

@ -16,8 +16,8 @@ use Sabre\VObject\Recur\NoInstancesException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VEvent extends VObject\Component class VEvent extends VObject\Component {
{
/** /**
* Returns true or false depending on if the event falls in the specified * Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes. * time-range. This is used for filtering purposes.
@ -30,15 +30,20 @@ class VEvent extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {
{
if ($this->RRULE) { if ($this->RRULE) {
try { try {
$it = new EventIterator($this, null, $start->getTimezone()); $it = new EventIterator($this, null, $start->getTimezone());
} catch (NoInstancesException $e) { } catch (NoInstancesException $e) {
// If we've catched this exception, there are no instances // If we've catched this exception, there are no instances
// for the event that fall into the specified time-range. // for the event that fall into the specified time-range.
return false; return false;
} }
$it->fastForward($start); $it->fastForward($start);
@ -49,11 +54,13 @@ class VEvent extends VObject\Component
// //
// If the starttime of the recurrence did not exceed the // If the starttime of the recurrence did not exceed the
// end of the time range as well, we have a match. // end of the time range as well, we have a match.
return $it->getDTStart() < $end && $it->getDTEnd() > $start; return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
} }
$effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone());
if (isset($this->DTEND)) { if (isset($this->DTEND)) {
// The DTEND property is considered non inclusive. So for a 3 day // 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 // event in july, dtstart and dtend would have to be July 1st and
// July 4th respectively. // July 4th respectively.
@ -61,6 +68,7 @@ class VEvent extends VObject\Component
// See: // See:
// http://tools.ietf.org/html/rfc5545#page-54 // http://tools.ietf.org/html/rfc5545#page-54
$effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone());
} elseif (isset($this->DURATION)) { } elseif (isset($this->DURATION)) {
$effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
} elseif (!$this->DTSTART->hasTime()) { } elseif (!$this->DTSTART->hasTime()) {
@ -68,10 +76,10 @@ class VEvent extends VObject\Component
} else { } else {
$effectiveEnd = $effectiveStart; $effectiveEnd = $effectiveStart;
} }
return (
return
($start < $effectiveEnd) && ($end > $effectiveStart) ($start < $effectiveEnd) && ($end > $effectiveStart)
; );
} }
/** /**
@ -79,12 +87,13 @@ class VEvent extends VObject\Component
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return [ return [
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
'DTSTAMP' => gmdate('Ymd\\THis\\Z'), 'DTSTAMP' => date('Ymd\\THis\\Z'),
]; ];
} }
/** /**
@ -102,42 +111,43 @@ class VEvent extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
$hasMethod = isset($this->parent->METHOD); $hasMethod = isset($this->parent->METHOD);
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'DTSTART' => $hasMethod ? '?' : '1', 'DTSTART' => $hasMethod ? '?' : '1',
'CLASS' => '?', 'CLASS' => '?',
'CREATED' => '?', 'CREATED' => '?',
'DESCRIPTION' => '?', 'DESCRIPTION' => '?',
'GEO' => '?', 'GEO' => '?',
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'LOCATION' => '?', 'LOCATION' => '?',
'ORGANIZER' => '?', 'ORGANIZER' => '?',
'PRIORITY' => '?', 'PRIORITY' => '?',
'SEQUENCE' => '?', 'SEQUENCE' => '?',
'STATUS' => '?', 'STATUS' => '?',
'SUMMARY' => '?', 'SUMMARY' => '?',
'TRANSP' => '?', 'TRANSP' => '?',
'URL' => '?', 'URL' => '?',
'RECURRENCE-ID' => '?', 'RECURRENCE-ID' => '?',
'RRULE' => '?', 'RRULE' => '?',
'DTEND' => '?', 'DTEND' => '?',
'DURATION' => '?', 'DURATION' => '?',
'ATTACH' => '*', 'ATTACH' => '*',
'ATTENDEE' => '*', 'ATTENDEE' => '*',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'CONTACT' => '*', 'CONTACT' => '*',
'EXDATE' => '*', 'EXDATE' => '*',
'REQUEST-STATUS' => '*', 'REQUEST-STATUS' => '*',
'RELATED-TO' => '*', 'RELATED-TO' => '*',
'RESOURCES' => '*', 'RESOURCES' => '*',
'RDATE' => '*', 'RDATE' => '*',
]; ];
} }
} }

View File

@ -15,8 +15,8 @@ use Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VFreeBusy extends VObject\Component class VFreeBusy extends VObject\Component {
{
/** /**
* Checks based on the contained FREEBUSY information, if a timeslot is * Checks based on the contained FREEBUSY information, if a timeslot is
* available. * available.
@ -26,18 +26,19 @@ class VFreeBusy extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isFree(DateTimeInterface $start, DatetimeInterface $end) function isFree(DateTimeInterface $start, DatetimeInterface $end) {
{
foreach ($this->select('FREEBUSY') as $freebusy) { foreach ($this->select('FREEBUSY') as $freebusy) {
// We are only interested in FBTYPE=BUSY (the default), // We are only interested in FBTYPE=BUSY (the default),
// FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
if (isset($freebusy['FBTYPE']) && 'BUSY' !== strtoupper(substr((string) $freebusy['FBTYPE'], 0, 4))) { if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'], 0, 4)) !== 'BUSY') {
continue; continue;
} }
// The freebusy component can hold more than 1 value, separated by // The freebusy component can hold more than 1 value, separated by
// commas. // commas.
$periods = explode(',', (string) $freebusy); $periods = explode(',', (string)$freebusy);
foreach ($periods as $period) { foreach ($periods as $period) {
// Every period is formatted as [start]/[end]. The start is an // Every period is formatted as [start]/[end]. The start is an
@ -54,10 +55,13 @@ class VFreeBusy extends VObject\Component
if ($start < $busyEnd && $end > $busyStart) { if ($start < $busyEnd && $end > $busyStart) {
return false; return false;
} }
} }
} }
return true; return true;
} }
/** /**
@ -75,22 +79,24 @@ class VFreeBusy extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'CONTACT' => '?', 'CONTACT' => '?',
'DTSTART' => '?', 'DTSTART' => '?',
'DTEND' => '?', 'DTEND' => '?',
'ORGANIZER' => '?', 'ORGANIZER' => '?',
'URL' => '?', 'URL' => '?',
'ATTENDEE' => '*', 'ATTENDEE' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'FREEBUSY' => '*', 'FREEBUSY' => '*',
'REQUEST-STATUS' => '*', 'REQUEST-STATUS' => '*',
]; ];
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VJournal extends VObject\Component class VJournal extends VObject\Component {
{
/** /**
* Returns true or false depending on if the event falls in the specified * Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes. * time-range. This is used for filtering purposes.
@ -28,8 +28,8 @@ class VJournal extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {
{
$dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null;
if ($dtstart) { if ($dtstart) {
$effectiveEnd = $dtstart; $effectiveEnd = $dtstart;
@ -37,10 +37,11 @@ class VJournal extends VObject\Component
$effectiveEnd = $effectiveEnd->modify('+1 day'); $effectiveEnd = $effectiveEnd->modify('+1 day');
} }
return $start <= $effectiveEnd && $end > $dtstart; return ($start <= $effectiveEnd && $end > $dtstart);
}
}
return false; return false;
} }
/** /**
@ -58,35 +59,36 @@ class VJournal extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'CLASS' => '?', 'CLASS' => '?',
'CREATED' => '?', 'CREATED' => '?',
'DTSTART' => '?', 'DTSTART' => '?',
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'ORGANIZER' => '?', 'ORGANIZER' => '?',
'RECURRENCE-ID' => '?', 'RECURRENCE-ID' => '?',
'SEQUENCE' => '?', 'SEQUENCE' => '?',
'STATUS' => '?', 'STATUS' => '?',
'SUMMARY' => '?', 'SUMMARY' => '?',
'URL' => '?', 'URL' => '?',
'RRULE' => '?', 'RRULE' => '?',
'ATTACH' => '*', 'ATTACH' => '*',
'ATTENDEE' => '*', 'ATTENDEE' => '*',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'CONTACT' => '*', 'CONTACT' => '*',
'DESCRIPTION' => '*', 'DESCRIPTION' => '*',
'EXDATE' => '*', 'EXDATE' => '*',
'RELATED-TO' => '*', 'RELATED-TO' => '*',
'RDATE' => '*', 'RDATE' => '*',
]; ];
} }
/** /**
@ -94,11 +96,12 @@ class VJournal extends VObject\Component
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return [ return [
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
'DTSTAMP' => gmdate('Ymd\\THis\\Z'), 'DTSTAMP' => date('Ymd\\THis\\Z'),
]; ];
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VTimeZone extends VObject\Component class VTimeZone extends VObject\Component {
{
/** /**
* Returns the PHP DateTimeZone for this VTIMEZONE component. * Returns the PHP DateTimeZone for this VTIMEZONE component.
* *
@ -24,9 +24,10 @@ class VTimeZone extends VObject\Component
* *
* @return \DateTimeZone * @return \DateTimeZone
*/ */
public function getTimeZone() function getTimeZone() {
{
return VObject\TimeZoneUtil::getTimeZone((string) $this->TZID, $this->root); return VObject\TimeZoneUtil::getTimeZone((string)$this->TZID, $this->root);
} }
/** /**
@ -44,13 +45,13 @@ class VTimeZone extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'TZID' => 1, 'TZID' => 1,
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'TZURL' => '?', 'TZURL' => '?',
// At least 1 STANDARD or DAYLIGHT must appear. // At least 1 STANDARD or DAYLIGHT must appear.
// //
@ -59,5 +60,7 @@ class VTimeZone extends VObject\Component
'STANDARD' => '*', 'STANDARD' => '*',
'DAYLIGHT' => '*', 'DAYLIGHT' => '*',
]; ];
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VTodo extends VObject\Component class VTodo extends VObject\Component {
{
/** /**
* Returns true or false depending on if the event falls in the specified * Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes. * time-range. This is used for filtering purposes.
@ -28,8 +28,8 @@ class VTodo extends VObject\Component
* *
* @return bool * @return bool
*/ */
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {
{
$dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null;
$duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null; $duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null;
$due = isset($this->DUE) ? $this->DUE->getDateTime() : null; $due = isset($this->DUE) ? $this->DUE->getDateTime() : null;
@ -39,7 +39,6 @@ class VTodo extends VObject\Component
if ($dtstart) { if ($dtstart) {
if ($duration) { if ($duration) {
$effectiveEnd = $dtstart->add($duration); $effectiveEnd = $dtstart->add($duration);
return $start <= $effectiveEnd && $end > $dtstart; return $start <= $effectiveEnd && $end > $dtstart;
} elseif ($due) { } elseif ($due) {
return return
@ -50,7 +49,7 @@ class VTodo extends VObject\Component
} }
} }
if ($due) { if ($due) {
return $start < $due && $end >= $due; return ($start < $due && $end >= $due);
} }
if ($completed && $created) { if ($completed && $created) {
return return
@ -58,13 +57,13 @@ class VTodo extends VObject\Component
($end >= $created || $end >= $completed); ($end >= $created || $end >= $completed);
} }
if ($completed) { if ($completed) {
return $start <= $completed && $end >= $completed; return ($start <= $completed && $end >= $completed);
} }
if ($created) { if ($created) {
return $end > $created; return ($end > $created);
} }
return true; return true;
} }
/** /**
@ -82,44 +81,45 @@ class VTodo extends VObject\Component
* *
* @var array * @var array
*/ */
public function getValidationRules() function getValidationRules() {
{
return [ return [
'UID' => 1, 'UID' => 1,
'DTSTAMP' => 1, 'DTSTAMP' => 1,
'CLASS' => '?', 'CLASS' => '?',
'COMPLETED' => '?', 'COMPLETED' => '?',
'CREATED' => '?', 'CREATED' => '?',
'DESCRIPTION' => '?', 'DESCRIPTION' => '?',
'DTSTART' => '?', 'DTSTART' => '?',
'GEO' => '?', 'GEO' => '?',
'LAST-MODIFIED' => '?', 'LAST-MODIFIED' => '?',
'LOCATION' => '?', 'LOCATION' => '?',
'ORGANIZER' => '?', 'ORGANIZER' => '?',
'PERCENT' => '?', 'PERCENT' => '?',
'PRIORITY' => '?', 'PRIORITY' => '?',
'RECURRENCE-ID' => '?', 'RECURRENCE-ID' => '?',
'SEQUENCE' => '?', 'SEQUENCE' => '?',
'STATUS' => '?', 'STATUS' => '?',
'SUMMARY' => '?', 'SUMMARY' => '?',
'URL' => '?', 'URL' => '?',
'RRULE' => '?', 'RRULE' => '?',
'DUE' => '?', 'DUE' => '?',
'DURATION' => '?', 'DURATION' => '?',
'ATTACH' => '*', 'ATTACH' => '*',
'ATTENDEE' => '*', 'ATTENDEE' => '*',
'CATEGORIES' => '*', 'CATEGORIES' => '*',
'COMMENT' => '*', 'COMMENT' => '*',
'CONTACT' => '*', 'CONTACT' => '*',
'EXDATE' => '*', 'EXDATE' => '*',
'REQUEST-STATUS' => '*', 'REQUEST-STATUS' => '*',
'RELATED-TO' => '*', 'RELATED-TO' => '*',
'RESOURCES' => '*', 'RESOURCES' => '*',
'RDATE' => '*', 'RDATE' => '*',
]; ];
} }
/** /**
@ -144,29 +144,36 @@ class VTodo extends VObject\Component
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$result = parent::validate($options); $result = parent::validate($options);
if (isset($this->DUE) && isset($this->DTSTART)) { if (isset($this->DUE) && isset($this->DTSTART)) {
$due = $this->DUE; $due = $this->DUE;
$dtStart = $this->DTSTART; $dtStart = $this->DTSTART;
if ($due->getValueType() !== $dtStart->getValueType()) { if ($due->getValueType() !== $dtStart->getValueType()) {
$result[] = [ $result[] = [
'level' => 3, 'level' => 3,
'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART',
'node' => $due, 'node' => $due,
]; ];
} elseif ($due->getDateTime() < $dtStart->getDateTime()) { } elseif ($due->getDateTime() < $dtStart->getDateTime()) {
$result[] = [ $result[] = [
'level' => 3, 'level' => 3,
'message' => 'DUE must occur after DTSTART', 'message' => 'DUE must occur after DTSTART',
'node' => $due, 'node' => $due,
]; ];
} }
} }
return $result; return $result;
} }
/** /**
@ -174,11 +181,13 @@ class VTodo extends VObject\Component
* *
* @return array * @return array
*/ */
protected function getDefaults() protected function getDefaults() {
{
return [ return [
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
'DTSTAMP' => date('Ymd\\THis\\Z'), 'DTSTAMP' => date('Ymd\\THis\\Z'),
]; ];
} }
} }

View File

@ -16,8 +16,8 @@ use DateTimeZone;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class DateTimeParser class DateTimeParser {
{
/** /**
* Parses an iCalendar (rfc5545) formatted datetime and returns a * Parses an iCalendar (rfc5545) formatted datetime and returns a
* DateTimeImmutable object. * DateTimeImmutable object.
@ -26,48 +26,49 @@ class DateTimeParser
* if the non-UTC format is used. The argument is used as a reference, the * if the non-UTC format is used. The argument is used as a reference, the
* returned DateTimeImmutable object will still be in the UTC timezone. * returned DateTimeImmutable object will still be in the UTC timezone.
* *
* @param string $dt * @param string $dt
* @param DateTimeZone $tz * @param DateTimeZone $tz
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public static function parseDateTime($dt, DateTimeZone $tz = null) static function parseDateTime($dt, DateTimeZone $tz = null) {
{
// Format is YYYYMMDD + "T" + hhmmss // Format is YYYYMMDD + "T" + hhmmss
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/', $dt, $matches); $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/', $dt, $matches);
if (!$result) { if (!$result) {
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt); throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt);
} }
if ('Z' === $matches[7] || is_null($tz)) { if ($matches[7] === 'Z' || is_null($tz)) {
$tz = new DateTimeZone('UTC'); $tz = new DateTimeZone('UTC');
} }
try { try {
$date = new DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz); $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6], $tz);
} catch (\Exception $e) { } catch (\Exception $e) {
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt); throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt);
} }
return $date; return $date;
} }
/** /**
* Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object. * Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object.
* *
* @param string $date * @param string $date
* @param DateTimeZone $tz * @param DateTimeZone $tz
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public static function parseDate($date, DateTimeZone $tz = null) static function parseDate($date, DateTimeZone $tz = null) {
{
// Format is YYYYMMDD // Format is YYYYMMDD
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches); $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches);
if (!$result) { if (!$result) {
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date); throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date);
} }
if (is_null($tz)) { if (is_null($tz)) {
@ -75,12 +76,13 @@ class DateTimeParser
} }
try { try {
$date = new DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz); $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
} catch (\Exception $e) { } catch (\Exception $e) {
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date); throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date);
} }
return $date; return $date;
} }
/** /**
@ -90,21 +92,22 @@ class DateTimeParser
* suitable for strtotime or DateTime::modify. * suitable for strtotime or DateTime::modify.
* *
* @param string $duration * @param string $duration
* @param bool $asString * @param bool $asString
* *
* @return DateInterval|string * @return DateInterval|string
*/ */
public static function parseDuration($duration, $asString = false) static function parseDuration($duration, $asString = false) {
{
$result = preg_match('/^(?<plusminus>\+|-)?P((?<week>\d+)W)?((?<day>\d+)D)?(T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?$/', $duration, $matches); $result = preg_match('/^(?<plusminus>\+|-)?P((?<week>\d+)W)?((?<day>\d+)D)?(T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?$/', $duration, $matches);
if (!$result) { if (!$result) {
throw new InvalidDataException('The supplied iCalendar duration value is incorrect: '.$duration); throw new InvalidDataException('The supplied iCalendar duration value is incorrect: ' . $duration);
} }
if (!$asString) { if (!$asString) {
$invert = false; $invert = false;
if ('-' === $matches['plusminus']) { if ($matches['plusminus'] === '-') {
$invert = true; $invert = true;
} }
@ -117,7 +120,7 @@ class DateTimeParser
]; ];
foreach ($parts as $part) { foreach ($parts as $part) {
$matches[$part] = isset($matches[$part]) && $matches[$part] ? (int) $matches[$part] : 0; $matches[$part] = isset($matches[$part]) && $matches[$part] ? (int)$matches[$part] : 0;
} }
// We need to re-construct the $duration string, because weeks and // We need to re-construct the $duration string, because weeks and
@ -130,26 +133,28 @@ class DateTimeParser
} }
if ($days) { if ($days) {
$duration .= $days.'D'; $duration .= $days . 'D';
} }
if ($matches['minute'] || $matches['second'] || $matches['hour']) { if ($matches['minute'] || $matches['second'] || $matches['hour']) {
$duration .= 'T'; $duration .= 'T';
if ($matches['hour']) { if ($matches['hour']) {
$duration .= $matches['hour'].'H'; $duration .= $matches['hour'] . 'H';
} }
if ($matches['minute']) { if ($matches['minute']) {
$duration .= $matches['minute'].'M'; $duration .= $matches['minute'] . 'M';
} }
if ($matches['second']) { if ($matches['second']) {
$duration .= $matches['second'].'S'; $duration .= $matches['second'] . 'S';
} }
} }
if ('P' === $duration) { if ($duration === 'P') {
$duration = 'PT0S'; $duration = 'PT0S';
} }
@ -160,6 +165,7 @@ class DateTimeParser
} }
return $iv; return $iv;
} }
$parts = [ $parts = [
@ -174,36 +180,38 @@ class DateTimeParser
foreach ($parts as $part) { foreach ($parts as $part) {
if (isset($matches[$part]) && $matches[$part]) { if (isset($matches[$part]) && $matches[$part]) {
$newDur .= ' '.$matches[$part].' '.$part.'s'; $newDur .= ' ' . $matches[$part] . ' ' . $part . 's';
} }
} }
$newDur = ('-' === $matches['plusminus'] ? '-' : '+').trim($newDur); $newDur = ($matches['plusminus'] === '-' ? '-' : '+') . trim($newDur);
if ('+' === $newDur) { if ($newDur === '+') {
$newDur = '+0 seconds'; $newDur = '+0 seconds';
} };
return $newDur; return $newDur;
} }
/** /**
* Parses either a Date or DateTime, or Duration value. * Parses either a Date or DateTime, or Duration value.
* *
* @param string $date * @param string $date
* @param DateTimeZone|string $referenceTz * @param DateTimeZone|string $referenceTz
* *
* @return DateTimeImmutable|DateInterval * @return DateTimeImmutable|DateInterval
*/ */
public static function parse($date, $referenceTz = null) static function parse($date, $referenceTz = null) {
{
if ('P' === $date[0] || ('-' === $date[0] && 'P' === $date[1])) { if ($date[0] === 'P' || ($date[0] === '-' && $date[1] === 'P')) {
return self::parseDuration($date); return self::parseDuration($date);
} elseif (8 === strlen($date)) { } elseif (strlen($date) === 8) {
return self::parseDate($date, $referenceTz); return self::parseDate($date, $referenceTz);
} else { } else {
return self::parseDateTime($date, $referenceTz); return self::parseDateTime($date, $referenceTz);
} }
} }
/** /**
@ -262,8 +270,8 @@ class DateTimeParser
* *
* @return array * @return array
*/ */
public static function parseVCardDateTime($date) static function parseVCardDateTime($date) {
{
$regex = '/^ $regex = '/^
(?: # date part (?: # date part
(?: (?:
@ -288,6 +296,7 @@ class DateTimeParser
$/x'; $/x';
if (!preg_match($regex, $date, $matches)) { if (!preg_match($regex, $date, $matches)) {
// Attempting to parse the extended format. // Attempting to parse the extended format.
$regex = '/^ $regex = '/^
(?: # date part (?: # date part
@ -312,8 +321,9 @@ class DateTimeParser
$/x'; $/x';
if (!preg_match($regex, $date, $matches)) { if (!preg_match($regex, $date, $matches)) {
throw new InvalidDataException('Invalid vCard date-time string: '.$date); throw new InvalidDataException('Invalid vCard date-time string: ' . $date);
} }
} }
$parts = [ $parts = [
'year', 'year',
@ -322,21 +332,24 @@ class DateTimeParser
'hour', 'hour',
'minute', 'minute',
'second', 'second',
'timezone', 'timezone'
]; ];
$result = []; $result = [];
foreach ($parts as $part) { foreach ($parts as $part) {
if (empty($matches[$part])) { if (empty($matches[$part])) {
$result[$part] = null; $result[$part] = null;
} elseif ('-' === $matches[$part] || '--' === $matches[$part]) { } elseif ($matches[$part] === '-' || $matches[$part] === '--') {
$result[$part] = null; $result[$part] = null;
} else { } else {
$result[$part] = $matches[$part]; $result[$part] = $matches[$part];
} }
} }
return $result; return $result;
} }
/** /**
@ -381,8 +394,8 @@ class DateTimeParser
* *
* @return array * @return array
*/ */
public static function parseVCardTime($date) static function parseVCardTime($date) {
{
$regex = '/^ $regex = '/^
(?<hour> [0-9]{2} | -) (?<hour> [0-9]{2} | -)
(?<minute> [0-9]{2} | -)? (?<minute> [0-9]{2} | -)?
@ -396,7 +409,9 @@ class DateTimeParser
)? )?
$/x'; $/x';
if (!preg_match($regex, $date, $matches)) { if (!preg_match($regex, $date, $matches)) {
// Attempting to parse the extended format. // Attempting to parse the extended format.
$regex = '/^ $regex = '/^
(?: (?<hour> [0-9]{2}) : | -) (?: (?<hour> [0-9]{2}) : | -)
@ -412,28 +427,32 @@ class DateTimeParser
$/x'; $/x';
if (!preg_match($regex, $date, $matches)) { if (!preg_match($regex, $date, $matches)) {
throw new InvalidDataException('Invalid vCard time string: '.$date); throw new InvalidDataException('Invalid vCard time string: ' . $date);
} }
} }
$parts = [ $parts = [
'hour', 'hour',
'minute', 'minute',
'second', 'second',
'timezone', 'timezone'
]; ];
$result = []; $result = [];
foreach ($parts as $part) { foreach ($parts as $part) {
if (empty($matches[$part])) { if (empty($matches[$part])) {
$result[$part] = null; $result[$part] = null;
} elseif ('-' === $matches[$part]) { } elseif ($matches[$part] === '-') {
$result[$part] = null; $result[$part] = null;
} else { } else {
$result[$part] = $matches[$part]; $result[$part] = $matches[$part];
} }
} }
return $result; return $result;
} }
/** /**
@ -487,32 +506,32 @@ class DateTimeParser
* *
* @return array * @return array
*/ */
public static function parseVCardDateAndOrTime($date) static function parseVCardDateAndOrTime($date) {
{
// \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d // \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d
$valueDate = '/^(?J)(?:'. $valueDate = '/^(?J)(?:' .
'(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)'. '(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)' .
'|(?<year>\d{4})-(?<month>\d\d)'. '|(?<year>\d{4})-(?<month>\d\d)' .
'|--(?<month>\d\d)(?<date>\d\d)?'. '|--(?<month>\d\d)(?<date>\d\d)?' .
'|---(?<date>\d\d)'. '|---(?<date>\d\d)' .
')$/'; ')$/';
// (\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)(Z|[+\-]\d\d(\d\d)?)? // (\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)(Z|[+\-]\d\d(\d\d)?)?
$valueTime = '/^(?J)(?:'. $valueTime = '/^(?J)(?:' .
'((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?'. '((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' .
'|-(?<minute>\d\d)(?<second>\d\d)?'. '|-(?<minute>\d\d)(?<second>\d\d)?' .
'|--(?<second>\d\d))'. '|--(?<second>\d\d))' .
'(?<timezone>(Z|[+\-]\d\d(\d\d)?))?'. '(?<timezone>(Z|[+\-]\d\d(\d\d)?))?' .
')$/'; ')$/';
// (\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?(Z|[+\-]\d\d(\d\d?)? // (\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?(Z|[+\-]\d\d(\d\d?)?
$valueDateTime = '/^(?:'. $valueDateTime = '/^(?:' .
'((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)'. '((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)' .
'|--(?<month1>\d\d)(?<date1>\d\d)'. '|--(?<month1>\d\d)(?<date1>\d\d)' .
'|---(?<date2>\d\d))'. '|---(?<date2>\d\d))' .
'T'. 'T' .
'(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?'. '(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' .
'(?<timezone>(Z|[+\-]\d\d(\d\d?)))?'. '(?<timezone>(Z|[+\-]\d\d(\d\d?)))?' .
')$/'; ')$/';
// date-and-or-time is date | date-time | time // date-and-or-time is date | date-time | time
@ -521,17 +540,17 @@ class DateTimeParser
if (0 === preg_match($valueDate, $date, $matches) if (0 === preg_match($valueDate, $date, $matches)
&& 0 === preg_match($valueDateTime, $date, $matches) && 0 === preg_match($valueDateTime, $date, $matches)
&& 0 === preg_match($valueTime, $date, $matches)) { && 0 === preg_match($valueTime, $date, $matches)) {
throw new InvalidDataException('Invalid vCard date-time string: '.$date); throw new InvalidDataException('Invalid vCard date-time string: ' . $date);
} }
$parts = [ $parts = [
'year' => null, 'year' => null,
'month' => null, 'month' => null,
'date' => null, 'date' => null,
'hour' => null, 'hour' => null,
'minute' => null, 'minute' => null,
'second' => null, 'second' => null,
'timezone' => null, 'timezone' => null
]; ];
// The $valueDateTime expression has a bug with (?J) so we simulate it. // The $valueDateTime expression has a bug with (?J) so we simulate it.
@ -556,5 +575,6 @@ class DateTimeParser
unset($parts['year0']); unset($parts['year0']);
return $parts; return $parts;
} }
} }

View File

@ -16,8 +16,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
abstract class Document extends Component abstract class Document extends Component {
{
/** /**
* Unknown document type. * Unknown document type.
*/ */
@ -55,28 +55,28 @@ abstract class Document extends Component
* *
* @var string * @var string
*/ */
public static $defaultName; static $defaultName;
/** /**
* List of properties, and which classes they map to. * List of properties, and which classes they map to.
* *
* @var array * @var array
*/ */
public static $propertyMap = []; static $propertyMap = [];
/** /**
* List of components, along with which classes they map to. * List of components, along with which classes they map to.
* *
* @var array * @var array
*/ */
public static $componentMap = []; static $componentMap = [];
/** /**
* List of value-types, and which classes they map to. * List of value-types, and which classes they map to.
* *
* @var array * @var array
*/ */
public static $valueMap = []; static $valueMap = [];
/** /**
* Creates a new document. * Creates a new document.
@ -91,20 +91,20 @@ abstract class Document extends Component
* *
* new Document(array $children = [], $defaults = true); * new Document(array $children = [], $defaults = true);
* new Document(string $name, array $children = [], $defaults = true) * new Document(string $name, array $children = [], $defaults = true)
*
* @return void
*/ */
public function __construct() function __construct() {
{
$args = func_get_args(); $args = func_get_args();
$name = static::$defaultName; if (count($args) === 0 || is_array($args[0])) {
if (0 === count($args) || is_array($args[0])) { array_unshift($args, $this, static::$defaultName);
$children = isset($args[0]) ? $args[0] : []; call_user_func_array(['parent', '__construct'], $args);
$defaults = isset($args[1]) ? $args[1] : true;
} else { } else {
$name = $args[0]; array_unshift($args, $this);
$children = isset($args[1]) ? $args[1] : []; call_user_func_array(['parent', '__construct'], $args);
$defaults = isset($args[2]) ? $args[2] : true;
} }
parent::__construct($this, $name, $children, $defaults);
} }
/** /**
@ -112,9 +112,10 @@ abstract class Document extends Component
* *
* @return int * @return int
*/ */
public function getDocumentType() function getDocumentType() {
{
return self::UNKNOWN; return self::UNKNOWN;
} }
/** /**
@ -128,13 +129,18 @@ abstract class Document extends Component
* *
* @return mixed * @return mixed
*/ */
public function create($name) function create($name) {
{
if (isset(static::$componentMap[strtoupper($name)])) { if (isset(static::$componentMap[strtoupper($name)])) {
return call_user_func_array([$this, 'createComponent'], func_get_args()); return call_user_func_array([$this, 'createComponent'], func_get_args());
} else { } else {
return call_user_func_array([$this, 'createProperty'], func_get_args()); return call_user_func_array([$this, 'createProperty'], func_get_args());
} }
} }
/** /**
@ -152,24 +158,22 @@ abstract class Document extends Component
* ensure that this does not happen, set $defaults to false. * ensure that this does not happen, set $defaults to false.
* *
* @param string $name * @param string $name
* @param array $children * @param array $children
* @param bool $defaults * @param bool $defaults
* *
* @return Component * @return Component
*/ */
public function createComponent($name, array $children = null, $defaults = true) function createComponent($name, array $children = null, $defaults = true) {
{
$name = strtoupper($name); $name = strtoupper($name);
$class = 'Sabre\\VObject\\Component'; $class = 'Sabre\\VObject\\Component';
if (isset(static::$componentMap[$name])) { if (isset(static::$componentMap[$name])) {
$class = static::$componentMap[$name]; $class = static::$componentMap[$name];
} }
if (is_null($children)) { if (is_null($children)) $children = [];
$children = [];
}
return new $class($this, $name, $children, $defaults); return new $class($this, $name, $children, $defaults);
} }
/** /**
@ -183,16 +187,16 @@ abstract class Document extends Component
* Parameter objects. * Parameter objects.
* *
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
* @param array $parameters * @param array $parameters
* @param string $valueType Force a specific valuetype, such as URI or TEXT * @param string $valueType Force a specific valuetype, such as URI or TEXT
* *
* @return Property * @return Property
*/ */
public function createProperty($name, $value = null, array $parameters = null, $valueType = null) 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 there's a . in the name, it means it's prefixed by a groupname.
if (false !== ($i = strpos($name, '.'))) { if (($i = strpos($name, '.')) !== false) {
$group = substr($name, 0, $i); $group = substr($name, 0, $i);
$name = strtoupper(substr($name, $i + 1)); $name = strtoupper(substr($name, $i + 1));
} else { } else {
@ -213,17 +217,17 @@ abstract class Document extends Component
if (isset($parameters['VALUE'])) { if (isset($parameters['VALUE'])) {
$class = $this->getClassNameForPropertyValue($parameters['VALUE']); $class = $this->getClassNameForPropertyValue($parameters['VALUE']);
if (is_null($class)) { if (is_null($class)) {
throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); throw new InvalidDataException('Unsupported VALUE parameter for ' . $name . ' property. You supplied "' . $parameters['VALUE'] . '"');
} }
} else { }
else {
$class = $this->getClassNameForPropertyName($name); $class = $this->getClassNameForPropertyName($name);
} }
} }
if (is_null($parameters)) { if (is_null($parameters)) $parameters = [];
$parameters = [];
}
return new $class($this, $name, $value, $parameters, $group); return new $class($this, $name, $value, $parameters, $group);
} }
/** /**
@ -235,15 +239,15 @@ abstract class Document extends Component
* This method returns null if we don't have a specialized class. * This method returns null if we don't have a specialized class.
* *
* @param string $valueParam * @param string $valueParam
*
* @return string|null * @return string|null
*/ */
public function getClassNameForPropertyValue($valueParam) function getClassNameForPropertyValue($valueParam) {
{
$valueParam = strtoupper($valueParam); $valueParam = strtoupper($valueParam);
if (isset(static::$valueMap[$valueParam])) { if (isset(static::$valueMap[$valueParam])) {
return static::$valueMap[$valueParam]; return static::$valueMap[$valueParam];
} }
} }
/** /**
@ -253,12 +257,14 @@ abstract class Document extends Component
* *
* @return string * @return string
*/ */
public function getClassNameForPropertyName($propertyName) function getClassNameForPropertyName($propertyName) {
{
if (isset(static::$propertyMap[$propertyName])) { if (isset(static::$propertyMap[$propertyName])) {
return static::$propertyMap[$propertyName]; return static::$propertyMap[$propertyName];
} else { } else {
return 'Sabre\\VObject\\Property\\Unknown'; return 'Sabre\\VObject\\Property\\Unknown';
} }
} }
} }

View File

@ -15,19 +15,23 @@ use LogicException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class ElementList extends ArrayIterator class ElementList extends ArrayIterator {
{
/* {{{ ArrayAccess Interface */ /* {{{ ArrayAccess Interface */
/** /**
* Sets an item through ArrayAccess. * Sets an item through ArrayAccess.
* *
* @param int $offset * @param int $offset
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
public function offsetSet($offset, $value) function offsetSet($offset, $value) {
{
throw new LogicException('You can not add new objects to an ElementList'); throw new LogicException('You can not add new objects to an ElementList');
} }
/** /**
@ -36,11 +40,15 @@ class ElementList extends ArrayIterator
* This method just forwards the request to the inner iterator * This method just forwards the request to the inner iterator
* *
* @param int $offset * @param int $offset
*
* @return void
*/ */
public function offsetUnset($offset) function offsetUnset($offset) {
{
throw new LogicException('You can not remove objects from an ElementList'); throw new LogicException('You can not remove objects from an ElementList');
} }
/* }}} */ /* }}} */
} }

View File

@ -10,6 +10,6 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class EofException extends ParseException class EofException extends ParseException {
{
} }

View File

@ -9,17 +9,17 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class FreeBusyData class FreeBusyData {
{
/** /**
* Start timestamp. * Start timestamp
* *
* @var int * @var int
*/ */
protected $start; protected $start;
/** /**
* End timestamp. * End timestamp
* *
* @var int * @var int
*/ */
@ -32,31 +32,35 @@ class FreeBusyData
*/ */
protected $data; protected $data;
public function __construct($start, $end) function __construct($start, $end) {
{
$this->start = $start; $this->start = $start;
$this->end = $end; $this->end = $end;
$this->data = []; $this->data = [];
$this->data[] = [ $this->data[] = [
'start' => $this->start, 'start' => $this->start,
'end' => $this->end, 'end' => $this->end,
'type' => 'FREE', 'type' => 'FREE',
]; ];
} }
/** /**
* Adds free or busytime to the data. * Adds free or busytime to the data.
* *
* @param int $start * @param int $start
* @param int $end * @param int $end
* @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE
* @return void
*/ */
public function add($start, $end, $type) function add($start, $end, $type) {
{
if ($start > $this->end || $end < $this->start) { if ($start > $this->end || $end < $this->start) {
// This new data is outside our timerange. // This new data is outside our timerange.
return; return;
} }
if ($start < $this->start) { if ($start < $this->start) {
@ -71,7 +75,7 @@ class FreeBusyData
// Finding out where we need to insert the new item. // Finding out where we need to insert the new item.
$currentIndex = 0; $currentIndex = 0;
while ($start > $this->data[$currentIndex]['end']) { while ($start > $this->data[$currentIndex]['end']) {
++$currentIndex; $currentIndex++;
} }
// The standard insertion point will be one _after_ the first // The standard insertion point will be one _after_ the first
@ -80,14 +84,14 @@ class FreeBusyData
$newItem = [ $newItem = [
'start' => $start, 'start' => $start,
'end' => $end, 'end' => $end,
'type' => $type, 'type' => $type,
]; ];
$preceedingItem = $this->data[$insertStartIndex - 1]; $preceedingItem = $this->data[$insertStartIndex - 1];
if ($this->data[$insertStartIndex - 1]['start'] === $start) { if ($this->data[$insertStartIndex - 1]['start'] === $start) {
// The old item starts at the exact same point as the new item. // The old item starts at the exact same point as the new item.
--$insertStartIndex; $insertStartIndex--;
} }
// Now we know where to insert the item, we need to know where it // Now we know where to insert the item, we need to know where it
@ -101,32 +105,32 @@ class FreeBusyData
} }
while ($end > $this->data[$currentIndex]['end']) { while ($end > $this->data[$currentIndex]['end']) {
++$currentIndex;
$currentIndex++;
} }
// What we are about to insert into the array // What we are about to insert into the array
$newItems = [ $newItems = [
$newItem, $newItem
]; ];
// This is the amount of items that are completely overwritten by the // This is the amount of items that are completely overwritten by the
// new item. // new item.
$itemsToDelete = $currentIndex - $insertStartIndex; $itemsToDelete = $currentIndex - $insertStartIndex;
if ($this->data[$currentIndex]['end'] <= $end) { if ($this->data[$currentIndex]['end'] <= $end) $itemsToDelete++;
++$itemsToDelete;
}
// If itemsToDelete was -1, it means that the newly inserted item is // If itemsToDelete was -1, it means that the newly inserted item is
// actually sitting inside an existing one. This means we need to split // actually sitting inside an existing one. This means we need to split
// the item at the current position in two and insert the new item in // the item at the current position in two and insert the new item in
// between. // between.
if (-1 === $itemsToDelete) { if ($itemsToDelete === -1) {
$itemsToDelete = 0; $itemsToDelete = 0;
if ($newItem['end'] < $preceedingItem['end']) { if ($newItem['end'] < $preceedingItem['end']) {
$newItems[] = [ $newItems[] = [
'start' => $newItem['end'] + 1, 'start' => $newItem['end'] + 1,
'end' => $preceedingItem['end'], 'end' => $preceedingItem['end'],
'type' => $preceedingItem['type'], 'type' => $preceedingItem['type']
]; ];
} }
} }
@ -151,8 +155,8 @@ class FreeBusyData
// merge them into one item. // merge them into one item.
if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) { if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) {
$doMerge = true; $doMerge = true;
--$mergeOffset; $mergeOffset--;
++$mergeDelete; $mergeDelete++;
$mergeItem['start'] = $this->data[$insertStartIndex - 1]['start']; $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start'];
} }
} }
@ -164,9 +168,10 @@ class FreeBusyData
// merge them into one item. // merge them into one item.
if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) { if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) {
$doMerge = true; $doMerge = true;
++$mergeDelete; $mergeDelete++;
$mergeItem['end'] = $this->data[$insertStartIndex + 1]['end']; $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end'];
} }
} }
if ($doMerge) { if ($doMerge) {
array_splice( array_splice(
@ -176,10 +181,13 @@ class FreeBusyData
[$mergeItem] [$mergeItem]
); );
} }
} }
public function getData() function getData() {
{
return $this->data; return $this->data;
} }
} }

View File

@ -23,8 +23,8 @@ use Sabre\VObject\Recur\NoInstancesException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class FreeBusyGenerator class FreeBusyGenerator {
{
/** /**
* Input objects. * Input objects.
* *
@ -71,7 +71,7 @@ class FreeBusyGenerator
/** /**
* A VAVAILABILITY document. * A VAVAILABILITY document.
* *
* If this is set, its information will be included when calculating * If this is set, it's information will be included when calculating
* freebusy time. * freebusy time.
* *
* @var Document * @var Document
@ -86,11 +86,11 @@ class FreeBusyGenerator
* *
* @param DateTimeInterface $start * @param DateTimeInterface $start
* @param DateTimeInterface $end * @param DateTimeInterface $end
* @param mixed $objects * @param mixed $objects
* @param DateTimeZone $timeZone * @param DateTimeZone $timeZone
*/ */
public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) {
{
$this->setTimeRange($start, $end); $this->setTimeRange($start, $end);
if ($objects) { if ($objects) {
@ -100,6 +100,7 @@ class FreeBusyGenerator
$timeZone = new DateTimeZone('UTC'); $timeZone = new DateTimeZone('UTC');
} }
$this->setTimeZone($timeZone); $this->setTimeZone($timeZone);
} }
/** /**
@ -111,20 +112,24 @@ class FreeBusyGenerator
* The VFREEBUSY object will be automatically added though. * The VFREEBUSY object will be automatically added though.
* *
* @param Document $vcalendar * @param Document $vcalendar
* @return void
*/ */
public function setBaseObject(Document $vcalendar) function setBaseObject(Document $vcalendar) {
{
$this->baseObject = $vcalendar; $this->baseObject = $vcalendar;
} }
/** /**
* Sets a VAVAILABILITY document. * Sets a VAVAILABILITY document.
* *
* @param Document $vcalendar * @param Document $vcalendar
* @return void
*/ */
public function setVAvailability(Document $vcalendar) function setVAvailability(Document $vcalendar) {
{
$this->vavailability = $vcalendar; $this->vavailability = $vcalendar;
} }
/** /**
@ -135,15 +140,18 @@ class FreeBusyGenerator
* It's also possible to specify multiple objects as an array. * It's also possible to specify multiple objects as an array.
* *
* @param mixed $objects * @param mixed $objects
*
* @return void
*/ */
public function setObjects($objects) function setObjects($objects) {
{
if (!is_array($objects)) { if (!is_array($objects)) {
$objects = [$objects]; $objects = [$objects];
} }
$this->objects = []; $this->objects = [];
foreach ($objects as $object) { foreach ($objects as $object) {
if (is_string($object) || is_resource($object)) { if (is_string($object) || is_resource($object)) {
$this->objects[] = Reader::read($object); $this->objects[] = Reader::read($object);
} elseif ($object instanceof Component) { } elseif ($object instanceof Component) {
@ -151,7 +159,9 @@ class FreeBusyGenerator
} else { } else {
throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
} }
} }
} }
/** /**
@ -161,9 +171,11 @@ class FreeBusyGenerator
* *
* @param DateTimeInterface $start * @param DateTimeInterface $start
* @param DateTimeInterface $end * @param DateTimeInterface $end
*
* @return void
*/ */
public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) {
{
if (!$start) { if (!$start) {
$start = new DateTimeImmutable(Settings::$minDate); $start = new DateTimeImmutable(Settings::$minDate);
} }
@ -172,16 +184,20 @@ class FreeBusyGenerator
} }
$this->start = $start; $this->start = $start;
$this->end = $end; $this->end = $end;
} }
/** /**
* Sets the reference timezone for floating times. * Sets the reference timezone for floating times.
* *
* @param DateTimeZone $timeZone * @param DateTimeZone $timeZone
*
* @return void
*/ */
public function setTimeZone(DateTimeZone $timeZone) function setTimeZone(DateTimeZone $timeZone) {
{
$this->timeZone = $timeZone; $this->timeZone = $timeZone;
} }
/** /**
@ -190,19 +206,23 @@ class FreeBusyGenerator
* *
* @return Component * @return Component
*/ */
public function getResult() function getResult() {
{
$fbData = new FreeBusyData( $fbData = new FreeBusyData(
$this->start->getTimeStamp(), $this->start->getTimeStamp(),
$this->end->getTimeStamp() $this->end->getTimeStamp()
); );
if ($this->vavailability) { if ($this->vavailability) {
$this->calculateAvailability($fbData, $this->vavailability); $this->calculateAvailability($fbData, $this->vavailability);
} }
$this->calculateBusy($fbData, $this->objects); $this->calculateBusy($fbData, $this->objects);
return $this->generateFreeBusyCalendar($fbData); return $this->generateFreeBusyCalendar($fbData);
} }
/** /**
@ -210,30 +230,29 @@ class FreeBusyGenerator
* available times. * available times.
* *
* @param FreeBusyData $fbData * @param FreeBusyData $fbData
* @param VCalendar $vavailability * @param VCalendar $vavailability
* @return void
*/ */
protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) {
{
$vavailComps = iterator_to_array($vavailability->VAVAILABILITY); $vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
usort( usort(
$vavailComps, $vavailComps,
function ($a, $b) { function($a, $b) {
// We need to order the components by priority. Priority 1 // We need to order the components by priority. Priority 1
// comes first, up until priority 9. Priority 0 comes after // comes first, up until priority 9. Priority 0 comes after
// priority 9. No priority implies priority 0. // priority 9. No priority implies priority 0.
// //
// Yes, I'm serious. // Yes, I'm serious.
$priorityA = isset($a->PRIORITY) ? (int) $a->PRIORITY->getValue() : 0; $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0;
$priorityB = isset($b->PRIORITY) ? (int) $b->PRIORITY->getValue() : 0; $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0;
if (0 === $priorityA) { if ($priorityA === 0) $priorityA = 10;
$priorityA = 10; if ($priorityB === 0) $priorityB = 10;
}
if (0 === $priorityB) {
$priorityB = 10;
}
return $priorityA - $priorityB; return $priorityA - $priorityB;
} }
); );
@ -248,6 +267,7 @@ class FreeBusyGenerator
$new = []; $new = [];
foreach ($old as $vavail) { foreach ($old as $vavail) {
list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
// We don't care about datetimes that are earlier or later than the // We don't care about datetimes that are earlier or later than the
@ -268,19 +288,24 @@ class FreeBusyGenerator
// Going through our existing list of components to see if there's // Going through our existing list of components to see if there's
// a higher priority component that already fully covers this one. // a higher priority component that already fully covers this one.
foreach ($new as $higherVavail) { foreach ($new as $higherVavail) {
list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
if ( if (
(is_null($higherStart) || $higherStart < $compStart) && (is_null($higherStart) || $higherStart < $compStart) &&
(is_null($higherEnd) || $higherEnd > $compEnd) (is_null($higherEnd) || $higherEnd > $compEnd)
) { ) {
// Component is fully covered by a higher priority // Component is fully covered by a higher priority
// component. We can skip this component. // component. We can skip this component.
continue 2; continue 2;
} }
} }
// We're keeping it! // We're keeping it!
$new[] = $vavail; $new[] = $vavail;
} }
// Lastly, we need to traverse the remaining components and fill in the // Lastly, we need to traverse the remaining components and fill in the
@ -289,6 +314,7 @@ class FreeBusyGenerator
// We traverse the components in reverse, because we want the higher // We traverse the components in reverse, because we want the higher
// priority components to override the lower ones. // priority components to override the lower ones.
foreach (array_reverse($new) as $vavail) { foreach (array_reverse($new) as $vavail) {
$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
@ -310,53 +336,57 @@ class FreeBusyGenerator
); );
// Looping over the AVAILABLE components. // Looping over the AVAILABLE components.
if (isset($vavail->AVAILABLE)) { if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) {
foreach ($vavail->AVAILABLE as $available) {
list($availStart, $availEnd) = $available->getEffectiveStartEnd(); list($availStart, $availEnd) = $available->getEffectiveStartEnd();
$fbData->add( $fbData->add(
$availStart->getTimeStamp(), $availStart->getTimeStamp(),
$availEnd->getTimeStamp(), $availEnd->getTimeStamp(),
'FREE' 'FREE'
); );
if ($available->RRULE) { if ($available->RRULE) {
// Our favourite thing: recurrence!! // Our favourite thing: recurrence!!
$rruleIterator = new Recur\RRuleIterator( $rruleIterator = new Recur\RRuleIterator(
$available->RRULE->getValue(), $available->RRULE->getValue(),
$availStart $availStart
); );
$rruleIterator->fastForward($vavailStart); $rruleIterator->fastForward($vavailStart);
$startEndDiff = $availStart->diff($availEnd); $startEndDiff = $availStart->diff($availEnd);
while ($rruleIterator->valid()) { while ($rruleIterator->valid()) {
$recurStart = $rruleIterator->current();
$recurEnd = $recurStart->add($startEndDiff);
if ($recurStart > $vavailEnd) { $recurStart = $rruleIterator->current();
// We're beyond the legal timerange. $recurEnd = $recurStart->add($startEndDiff);
break;
}
if ($recurEnd > $vavailEnd) { if ($recurStart > $vavailEnd) {
// Truncating the end if it exceeds the // We're beyond the legal timerange.
// VAVAILABILITY end. break;
$recurEnd = $vavailEnd; }
}
$fbData->add( if ($recurEnd > $vavailEnd) {
// Truncating the end if it exceeds the
// VAVAILABILITY end.
$recurEnd = $vavailEnd;
}
$fbData->add(
$recurStart->getTimeStamp(), $recurStart->getTimeStamp(),
$recurEnd->getTimeStamp(), $recurEnd->getTimeStamp(),
'FREE' 'FREE'
); );
$rruleIterator->next(); $rruleIterator->next();
}
} }
} }
} }
} }
} }
/** /**
@ -364,25 +394,28 @@ class FreeBusyGenerator
* times on fbData. * times on fbData.
* *
* @param FreeBusyData $fbData * @param FreeBusyData $fbData
* @param VCalendar[] $objects * @param VCalendar[] $objects
*/ */
protected function calculateBusy(FreeBusyData $fbData, array $objects) protected function calculateBusy(FreeBusyData $fbData, array $objects) {
{
foreach ($objects as $key => $object) { foreach ($objects as $key => $object) {
foreach ($object->getBaseComponents() as $component) { foreach ($object->getBaseComponents() as $component) {
switch ($component->name) { switch ($component->name) {
case 'VEVENT':
case 'VEVENT' :
$FBTYPE = 'BUSY'; $FBTYPE = 'BUSY';
if (isset($component->TRANSP) && ('TRANSPARENT' === strtoupper($component->TRANSP))) { if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
break; break;
} }
if (isset($component->STATUS)) { if (isset($component->STATUS)) {
$status = strtoupper($component->STATUS); $status = strtoupper($component->STATUS);
if ('CANCELLED' === $status) { if ($status === 'CANCELLED') {
break; break;
} }
if ('TENTATIVE' === $status) { if ($status === 'TENTATIVE') {
$FBTYPE = 'BUSY-TENTATIVE'; $FBTYPE = 'BUSY-TENTATIVE';
} }
} }
@ -391,13 +424,13 @@ class FreeBusyGenerator
if ($component->RRULE) { if ($component->RRULE) {
try { try {
$iterator = new EventIterator($object, (string) $component->UID, $this->timeZone); $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone);
} catch (NoInstancesException $e) { } catch (NoInstancesException $e) {
// This event is recurring, but it doesn't have a single // This event is recurring, but it doesn't have a single
// instance. We are skipping this event from the output // instance. We are skipping this event from the output
// entirely. // entirely.
unset($this->objects[$key]); unset($this->objects[$key]);
break; continue;
} }
if ($this->start) { if ($this->start) {
@ -407,6 +440,7 @@ class FreeBusyGenerator
$maxRecurrences = Settings::$maxRecurrences; $maxRecurrences = Settings::$maxRecurrences;
while ($iterator->valid() && --$maxRecurrences) { while ($iterator->valid() && --$maxRecurrences) {
$startTime = $iterator->getDTStart(); $startTime = $iterator->getDTStart();
if ($this->end && $startTime > $this->end) { if ($this->end && $startTime > $this->end) {
break; break;
@ -417,8 +451,11 @@ class FreeBusyGenerator
]; ];
$iterator->next(); $iterator->next();
} }
} else { } else {
$startTime = $component->DTSTART->getDateTime($this->timeZone); $startTime = $component->DTSTART->getDateTime($this->timeZone);
if ($this->end && $startTime > $this->end) { if ($this->end && $startTime > $this->end) {
break; break;
@ -427,7 +464,7 @@ class FreeBusyGenerator
if (isset($component->DTEND)) { if (isset($component->DTEND)) {
$endTime = $component->DTEND->getDateTime($this->timeZone); $endTime = $component->DTEND->getDateTime($this->timeZone);
} elseif (isset($component->DURATION)) { } elseif (isset($component->DURATION)) {
$duration = DateTimeParser::parseDuration((string) $component->DURATION); $duration = DateTimeParser::parseDuration((string)$component->DURATION);
$endTime = clone $startTime; $endTime = clone $startTime;
$endTime = $endTime->add($duration); $endTime = $endTime->add($duration);
} elseif (!$component->DTSTART->hasTime()) { } elseif (!$component->DTSTART->hasTime()) {
@ -439,15 +476,13 @@ class FreeBusyGenerator
} }
$times[] = [$startTime, $endTime]; $times[] = [$startTime, $endTime];
} }
foreach ($times as $time) { foreach ($times as $time) {
if ($this->end && $time[0] > $this->end) {
break; if ($this->end && $time[0] > $this->end) break;
} if ($this->start && $time[1] < $this->start) break;
if ($this->start && $time[1] < $this->start) {
break;
}
$fbData->add( $fbData->add(
$time[0]->getTimeStamp(), $time[0]->getTimeStamp(),
@ -457,21 +492,21 @@ class FreeBusyGenerator
} }
break; break;
case 'VFREEBUSY': case 'VFREEBUSY' :
foreach ($component->FREEBUSY as $freebusy) { foreach ($component->FREEBUSY as $freebusy) {
$fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
// Skipping intervals marked as 'free' // Skipping intervals marked as 'free'
if ('FREE' === $fbType) { if ($fbType === 'FREE')
continue; continue;
}
$values = explode(',', $freebusy); $values = explode(',', $freebusy);
foreach ($values as $value) { foreach ($values as $value) {
list($startTime, $endTime) = explode('/', $value); list($startTime, $endTime) = explode('/', $value);
$startTime = DateTimeParser::parseDateTime($startTime); $startTime = DateTimeParser::parseDateTime($startTime);
if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) { if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
$duration = DateTimeParser::parseDuration($endTime); $duration = DateTimeParser::parseDuration($endTime);
$endTime = clone $startTime; $endTime = clone $startTime;
$endTime = $endTime->add($duration); $endTime = $endTime->add($duration);
@ -479,23 +514,27 @@ class FreeBusyGenerator
$endTime = DateTimeParser::parseDateTime($endTime); $endTime = DateTimeParser::parseDateTime($endTime);
} }
if ($this->start && $this->start > $endTime) { if ($this->start && $this->start > $endTime) continue;
continue; if ($this->end && $this->end < $startTime) continue;
}
if ($this->end && $this->end < $startTime) {
continue;
}
$fbData->add( $fbData->add(
$startTime->getTimeStamp(), $startTime->getTimeStamp(),
$endTime->getTimeStamp(), $endTime->getTimeStamp(),
$fbType $fbType
); );
} }
} }
break; break;
} }
} }
} }
} }
/** /**
@ -504,8 +543,8 @@ class FreeBusyGenerator
* *
* @return VCalendar * @return VCalendar
*/ */
protected function generateFreeBusyCalendar(FreeBusyData $fbData) protected function generateFreeBusyCalendar(FreeBusyData $fbData) {
{
if ($this->baseObject) { if ($this->baseObject) {
$calendar = $this->baseObject; $calendar = $this->baseObject;
} else { } else {
@ -532,29 +571,34 @@ class FreeBusyGenerator
$vfreebusy->add($dtstamp); $vfreebusy->add($dtstamp);
foreach ($fbData->getData() as $busyTime) { foreach ($fbData->getData() as $busyTime) {
$busyType = strtoupper($busyTime['type']); $busyType = strtoupper($busyTime['type']);
// Ignoring all the FREE parts, because those are already assumed. // Ignoring all the FREE parts, because those are already assumed.
if ('FREE' === $busyType) { if ($busyType === 'FREE') {
continue; continue;
} }
$busyTime[0] = new \DateTimeImmutable('@'.$busyTime['start'], $tz); $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz);
$busyTime[1] = new \DateTimeImmutable('@'.$busyTime['end'], $tz); $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz);
$prop = $calendar->createProperty( $prop = $calendar->createProperty(
'FREEBUSY', 'FREEBUSY',
$busyTime[0]->format('Ymd\\THis\\Z').'/'.$busyTime[1]->format('Ymd\\THis\\Z') $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
); );
// Only setting FBTYPE if it's not BUSY, because BUSY is the // Only setting FBTYPE if it's not BUSY, because BUSY is the
// default anyway. // default anyway.
if ('BUSY' !== $busyType) { if ($busyType !== 'BUSY') {
$prop['FBTYPE'] = $busyType; $prop['FBTYPE'] = $busyType;
} }
$vfreebusy->add($prop); $vfreebusy->add($prop);
} }
return $calendar; return $calendar;
} }
} }

View File

@ -35,8 +35,8 @@ use Sabre\VObject\Recur\EventIterator;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Broker class Broker {
{
/** /**
* This setting determines whether the rules for the SCHEDULE-AGENT * This setting determines whether the rules for the SCHEDULE-AGENT
* parameter should be followed. * parameter should be followed.
@ -104,34 +104,37 @@ class Broker
* *
* If the iTip message was not supported, we will always return false. * If the iTip message was not supported, we will always return false.
* *
* @param Message $itipMessage * @param Message $itipMessage
* @param VCalendar $existingObject * @param VCalendar $existingObject
* *
* @return VCalendar|null * @return VCalendar|null
*/ */
public function processMessage(Message $itipMessage, VCalendar $existingObject = null) function processMessage(Message $itipMessage, VCalendar $existingObject = null) {
{
// We only support events at the moment. // We only support events at the moment.
if ('VEVENT' !== $itipMessage->component) { if ($itipMessage->component !== 'VEVENT') {
return false; return false;
} }
switch ($itipMessage->method) { switch ($itipMessage->method) {
case 'REQUEST':
case 'REQUEST' :
return $this->processMessageRequest($itipMessage, $existingObject); return $this->processMessageRequest($itipMessage, $existingObject);
case 'CANCEL': case 'CANCEL' :
return $this->processMessageCancel($itipMessage, $existingObject); return $this->processMessageCancel($itipMessage, $existingObject);
case 'REPLY': case 'REPLY' :
return $this->processMessageReply($itipMessage, $existingObject); return $this->processMessageReply($itipMessage, $existingObject);
default: default :
// Unsupported iTip message // Unsupported iTip message
return; return;
} }
return $existingObject; return $existingObject;
} }
/** /**
@ -158,13 +161,13 @@ class Broker
* organizer gets the 'declined' message. * organizer gets the 'declined' message.
* *
* @param VCalendar|string $calendar * @param VCalendar|string $calendar
* @param string|array $userHref * @param string|array $userHref
* @param VCalendar|string $oldCalendar * @param VCalendar|string $oldCalendar
* *
* @return array * @return array
*/ */
public function parseEvent($calendar = null, $userHref, $oldCalendar = null) function parseEvent($calendar = null, $userHref, $oldCalendar = null) {
{
if ($oldCalendar) { if ($oldCalendar) {
if (is_string($oldCalendar)) { if (is_string($oldCalendar)) {
$oldCalendar = Reader::read($oldCalendar); $oldCalendar = Reader::read($oldCalendar);
@ -177,15 +180,16 @@ class Broker
$oldEventInfo = $this->parseEventInfo($oldCalendar); $oldEventInfo = $this->parseEventInfo($oldCalendar);
} else { } else {
$oldEventInfo = [ $oldEventInfo = [
'organizer' => null, 'organizer' => null,
'significantChangeHash' => '', 'significantChangeHash' => '',
'attendees' => [], 'attendees' => [],
]; ];
} }
$userHref = (array) $userHref; $userHref = (array)$userHref;
if (!is_null($calendar)) { if (!is_null($calendar)) {
if (is_string($calendar)) { if (is_string($calendar)) {
$calendar = Reader::read($calendar); $calendar = Reader::read($calendar);
} }
@ -213,6 +217,7 @@ class Broker
$eventInfo['organizer'] = $oldEventInfo['organizer']; $eventInfo['organizer'] = $oldEventInfo['organizer'];
$eventInfo['organizerName'] = $oldEventInfo['organizerName']; $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
} }
} else { } else {
// The calendar object got deleted, we need to process this as a // The calendar object got deleted, we need to process this as a
// cancellation / decline. // cancellation / decline.
@ -228,17 +233,19 @@ class Broker
$eventInfo['attendees'] = []; $eventInfo['attendees'] = [];
// Increasing the sequence, but only if the organizer deleted // Increasing the sequence, but only if the organizer deleted
// the event. // the event.
++$eventInfo['sequence']; $eventInfo['sequence']++;
} else { } else {
// This is an attendee deleting the event. // This is an attendee deleting the event.
foreach ($eventInfo['attendees'] as $key => $attendee) { foreach ($eventInfo['attendees'] as $key => $attendee) {
if (in_array($attendee['href'], $userHref)) { if (in_array($attendee['href'], $userHref)) {
$eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'], $eventInfo['attendees'][$key]['instances'] = ['master' =>
['id' => 'master', 'partstat' => 'DECLINED']
]; ];
} }
} }
} }
$baseCalendar = $oldCalendar; $baseCalendar = $oldCalendar;
} }
if (in_array($eventInfo['organizer'], $userHref)) { if (in_array($eventInfo['organizer'], $userHref)) {
@ -253,8 +260,8 @@ class Broker
} }
} }
} }
return []; return [];
} }
/** /**
@ -264,13 +271,13 @@ class Broker
* invite, or an update to an existing one. * invite, or an update to an existing one.
* *
* *
* @param Message $itipMessage * @param Message $itipMessage
* @param VCalendar $existingObject * @param VCalendar $existingObject
* *
* @return VCalendar|null * @return VCalendar|null
*/ */
protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {
{
if (!$existingObject) { if (!$existingObject) {
// This is a new invite, and we're just going to copy over // This is a new invite, and we're just going to copy over
// all the components from the invite. // all the components from the invite.
@ -289,8 +296,8 @@ class Broker
$existingObject->add(clone $component); $existingObject->add(clone $component);
} }
} }
return $existingObject; return $existingObject;
} }
/** /**
@ -300,13 +307,13 @@ class Broker
* attendee got removed from an event, or an event got cancelled * attendee got removed from an event, or an event got cancelled
* altogether. * altogether.
* *
* @param Message $itipMessage * @param Message $itipMessage
* @param VCalendar $existingObject * @param VCalendar $existingObject
* *
* @return VCalendar|null * @return VCalendar|null
*/ */
protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {
{
if (!$existingObject) { if (!$existingObject) {
// The event didn't exist in the first place, so we're just // The event didn't exist in the first place, so we're just
// ignoring this message. // ignoring this message.
@ -316,8 +323,8 @@ class Broker
$vevent->SEQUENCE = $itipMessage->sequence; $vevent->SEQUENCE = $itipMessage->sequence;
} }
} }
return $existingObject; return $existingObject;
} }
/** /**
@ -326,13 +333,13 @@ class Broker
* The message is a reply. This is for example an attendee telling * The message is a reply. This is for example an attendee telling
* an organizer he accepted the invite, or declined it. * an organizer he accepted the invite, or declined it.
* *
* @param Message $itipMessage * @param Message $itipMessage
* @param VCalendar $existingObject * @param VCalendar $existingObject
* *
* @return VCalendar|null * @return VCalendar|null
*/ */
protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {
{
// A reply can only be processed based on an existing object. // A reply can only be processed based on an existing object.
// If the object is not available, the reply is ignored. // If the object is not available, the reply is ignored.
if (!$existingObject) { if (!$existingObject) {
@ -357,7 +364,7 @@ class Broker
$masterObject = null; $masterObject = null;
foreach ($existingObject->VEVENT as $vevent) { foreach ($existingObject->VEVENT as $vevent) {
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
if ('master' === $recurId) { if ($recurId === 'master') {
$masterObject = $vevent; $masterObject = $vevent;
} }
if (isset($instances[$recurId])) { if (isset($instances[$recurId])) {
@ -379,11 +386,9 @@ class Broker
// Adding a new attendee. The iTip documentation calls this // Adding a new attendee. The iTip documentation calls this
// a party crasher. // a party crasher.
$attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [ $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
'PARTSTAT' => $instances[$recurId], 'PARTSTAT' => $instances[$recurId]
]); ]);
if ($itipMessage->senderName) { if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
$attendee['CN'] = $itipMessage->senderName;
}
} }
unset($instances[$recurId]); unset($instances[$recurId]);
} }
@ -396,23 +401,24 @@ class Broker
// If we got replies to instances that did not exist in the // If we got replies to instances that did not exist in the
// original list, it means that new exceptions must be created. // original list, it means that new exceptions must be created.
foreach ($instances as $recurId => $partstat) { foreach ($instances as $recurId => $partstat) {
$recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
$found = false; $found = false;
$iterations = 1000; $iterations = 1000;
do { do {
$newObject = $recurrenceIterator->getEventObject(); $newObject = $recurrenceIterator->getEventObject();
$recurrenceIterator->next(); $recurrenceIterator->next();
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) { if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
$found = true; $found = true;
} }
--$iterations; $iterations--;
} while ($recurrenceIterator->valid() && !$found && $iterations); } while ($recurrenceIterator->valid() && !$found && $iterations);
// Invalid recurrence id. Skipping this object. // Invalid recurrence id. Skipping this object.
if (!$found) { if (!$found) continue;
continue;
}
unset( unset(
$newObject->RRULE, $newObject->RRULE,
@ -432,16 +438,17 @@ class Broker
if (!$attendeeFound) { if (!$attendeeFound) {
// Adding a new attendee // Adding a new attendee
$attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [ $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
'PARTSTAT' => $partstat, 'PARTSTAT' => $partstat
]); ]);
if ($itipMessage->senderName) { if ($itipMessage->senderName) {
$attendee['CN'] = $itipMessage->senderName; $attendee['CN'] = $itipMessage->senderName;
} }
} }
$existingObject->add($newObject); $existingObject->add($newObject);
}
}
return $existingObject; return $existingObject;
} }
/** /**
@ -453,22 +460,22 @@ class Broker
* specific messages for these situations. * specific messages for these situations.
* *
* @param VCalendar $calendar * @param VCalendar $calendar
* @param array $eventInfo * @param array $eventInfo
* @param array $oldEventInfo * @param array $oldEventInfo
* *
* @return array * @return array
*/ */
protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
{
// Merging attendee lists. // Merging attendee lists.
$attendees = []; $attendees = [];
foreach ($oldEventInfo['attendees'] as $attendee) { foreach ($oldEventInfo['attendees'] as $attendee) {
$attendees[$attendee['href']] = [ $attendees[$attendee['href']] = [
'href' => $attendee['href'], 'href' => $attendee['href'],
'oldInstances' => $attendee['instances'], 'oldInstances' => $attendee['instances'],
'newInstances' => [], 'newInstances' => [],
'name' => $attendee['name'], 'name' => $attendee['name'],
'forceSend' => null, 'forceSend' => null,
]; ];
} }
foreach ($eventInfo['attendees'] as $attendee) { foreach ($eventInfo['attendees'] as $attendee) {
@ -478,11 +485,11 @@ class Broker
$attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
} else { } else {
$attendees[$attendee['href']] = [ $attendees[$attendee['href']] = [
'href' => $attendee['href'], 'href' => $attendee['href'],
'oldInstances' => [], 'oldInstances' => [],
'newInstances' => $attendee['instances'], 'newInstances' => $attendee['instances'],
'name' => $attendee['name'], 'name' => $attendee['name'],
'forceSend' => $attendee['forceSend'], 'forceSend' => $attendee['forceSend'],
]; ];
} }
} }
@ -490,6 +497,7 @@ class Broker
$messages = []; $messages = [];
foreach ($attendees as $attendee) { foreach ($attendees as $attendee) {
// An organizer can also be an attendee. We should not generate any // An organizer can also be an attendee. We should not generate any
// messages for those. // messages for those.
if ($attendee['href'] === $eventInfo['organizer']) { if ($attendee['href'] === $eventInfo['organizer']) {
@ -506,6 +514,7 @@ class Broker
$message->recipientName = $attendee['name']; $message->recipientName = $attendee['name'];
if (!$attendee['newInstances']) { if (!$attendee['newInstances']) {
// If there are no instances the attendee is a part of, it // If there are no instances the attendee is a part of, it
// means the attendee was removed and we need to send him a // means the attendee was removed and we need to send him a
// CANCEL. // CANCEL.
@ -514,13 +523,8 @@ class Broker
// Creating the new iCalendar body. // Creating the new iCalendar body.
$icalMsg = new VCalendar(); $icalMsg = new VCalendar();
$icalMsg->METHOD = $message->method; $icalMsg->METHOD = $message->method;
foreach ($calendar->select('VTIMEZONE') as $timezone) {
$icalMsg->add(clone $timezone);
}
$event = $icalMsg->add('VEVENT', [ $event = $icalMsg->add('VEVENT', [
'UID' => $message->uid, 'UID' => $message->uid,
'SEQUENCE' => $message->sequence, 'SEQUENCE' => $message->sequence,
]); ]);
if (isset($calendar->VEVENT->SUMMARY)) { if (isset($calendar->VEVENT->SUMMARY)) {
@ -533,14 +537,14 @@ class Broker
$event->add(clone $calendar->VEVENT->DURATION); $event->add(clone $calendar->VEVENT->DURATION);
} }
$org = $event->add('ORGANIZER', $eventInfo['organizer']); $org = $event->add('ORGANIZER', $eventInfo['organizer']);
if ($eventInfo['organizerName']) { if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
$org['CN'] = $eventInfo['organizerName'];
}
$event->add('ATTENDEE', $attendee['href'], [ $event->add('ATTENDEE', $attendee['href'], [
'CN' => $attendee['name'], 'CN' => $attendee['name'],
]); ]);
$message->significantChange = true; $message->significantChange = true;
} else { } else {
// The attendee gets the updated event body // The attendee gets the updated event body
$message->method = 'REQUEST'; $message->method = 'REQUEST';
@ -561,13 +565,15 @@ class Broker
// difference in instances that the attendee is invited to. // difference in instances that the attendee is invited to.
$message->significantChange = $message->significantChange =
'REQUEST' === $attendee['forceSend'] || $attendee['forceSend'] === 'REQUEST' ||
array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
$oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) { foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
$currentEvent = clone $eventInfo['instances'][$instanceId]; $currentEvent = clone $eventInfo['instances'][$instanceId];
if ('master' === $instanceId) { if ($instanceId === 'master') {
// We need to find a list of events that the attendee // We need to find a list of events that the attendee
// is not a part of to add to the list of exceptions. // is not a part of to add to the list of exceptions.
$exceptions = []; $exceptions = [];
@ -604,18 +610,24 @@ class Broker
if (!isset($attendee['PARTSTAT'])) { if (!isset($attendee['PARTSTAT'])) {
$attendee['PARTSTAT'] = 'NEEDS-ACTION'; $attendee['PARTSTAT'] = 'NEEDS-ACTION';
} }
} }
} }
$icalMsg->add($currentEvent); $icalMsg->add($currentEvent);
} }
} }
$message->message = $icalMsg; $message->message = $icalMsg;
$messages[] = $message; $messages[] = $message;
} }
return $messages; return $messages;
} }
/** /**
@ -624,21 +636,21 @@ class Broker
* This function figures out if we need to send a reply to an organizer. * This function figures out if we need to send a reply to an organizer.
* *
* @param VCalendar $calendar * @param VCalendar $calendar
* @param array $eventInfo * @param array $eventInfo
* @param array $oldEventInfo * @param array $oldEventInfo
* @param string $attendee * @param string $attendee
* *
* @return Message[] * @return Message[]
*/ */
protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {
{
if ($this->scheduleAgentServerRules && 'CLIENT' === $eventInfo['organizerScheduleAgent']) { if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') {
return []; return [];
} }
// Don't bother generating messages for events that have already been // Don't bother generating messages for events that have already been
// cancelled. // cancelled.
if ('CANCELLED' === $eventInfo['status']) { if ($eventInfo['status'] === 'CANCELLED') {
return []; return [];
} }
@ -648,22 +660,26 @@ class Broker
$instances = []; $instances = [];
foreach ($oldInstances as $instance) { foreach ($oldInstances as $instance) {
$instances[$instance['id']] = [ $instances[$instance['id']] = [
'id' => $instance['id'], 'id' => $instance['id'],
'oldstatus' => $instance['partstat'], 'oldstatus' => $instance['partstat'],
'newstatus' => null, 'newstatus' => null,
]; ];
} }
foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) { foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {
if (isset($instances[$instance['id']])) { if (isset($instances[$instance['id']])) {
$instances[$instance['id']]['newstatus'] = $instance['partstat']; $instances[$instance['id']]['newstatus'] = $instance['partstat'];
} else { } else {
$instances[$instance['id']] = [ $instances[$instance['id']] = [
'id' => $instance['id'], 'id' => $instance['id'],
'oldstatus' => null, 'oldstatus' => null,
'newstatus' => $instance['partstat'], 'newstatus' => $instance['partstat'],
]; ];
} }
} }
// We need to also look for differences in EXDATE. If there are new // We need to also look for differences in EXDATE. If there are new
@ -671,29 +687,33 @@ class Broker
// event, which means we need to send DECLINED specifically for those // event, which means we need to send DECLINED specifically for those
// instances. // instances.
// We only need to do that though, if the master event is not declined. // We only need to do that though, if the master event is not declined.
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) { if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
foreach ($eventInfo['exdate'] as $exDate) { foreach ($eventInfo['exdate'] as $exDate) {
if (!in_array($exDate, $oldEventInfo['exdate'])) { if (!in_array($exDate, $oldEventInfo['exdate'])) {
if (isset($instances[$exDate])) { if (isset($instances[$exDate])) {
$instances[$exDate]['newstatus'] = 'DECLINED'; $instances[$exDate]['newstatus'] = 'DECLINED';
} else { } else {
$instances[$exDate] = [ $instances[$exDate] = [
'id' => $exDate, 'id' => $exDate,
'oldstatus' => null, 'oldstatus' => null,
'newstatus' => 'DECLINED', 'newstatus' => 'DECLINED',
]; ];
} }
} }
} }
} }
// Gathering a few extra properties for each instance. // Gathering a few extra properties for each instance.
foreach ($instances as $recurId => $instanceInfo) { foreach ($instances as $recurId => $instanceInfo) {
if (isset($eventInfo['instances'][$recurId])) { if (isset($eventInfo['instances'][$recurId])) {
$instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
} else { } else {
$instances[$recurId]['dtstart'] = $recurId; $instances[$recurId]['dtstart'] = $recurId;
} }
} }
$message = new Message(); $message = new Message();
@ -712,13 +732,14 @@ class Broker
$hasReply = false; $hasReply = false;
foreach ($instances as $instance) { foreach ($instances as $instance) {
if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
// Skip // Skip
continue; continue;
} }
$event = $icalMsg->add('VEVENT', [ $event = $icalMsg->add('VEVENT', [
'UID' => $message->uid, 'UID' => $message->uid,
'SEQUENCE' => $message->sequence, 'SEQUENCE' => $message->sequence,
]); ]);
$summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : ''; $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
@ -752,7 +773,7 @@ class Broker
$event->add('SUMMARY', $summary); $event->add('SUMMARY', $summary);
} }
} }
if ('master' !== $instance['id']) { if ($instance['id'] !== 'master') {
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
// Treat is as a DATE field // Treat is as a DATE field
if (strlen($instance['id']) <= 8) { if (strlen($instance['id']) <= 8) {
@ -766,21 +787,22 @@ class Broker
$organizer['CN'] = $message->recipientName; $organizer['CN'] = $message->recipientName;
} }
$attendee = $event->add('ATTENDEE', $message->sender, [ $attendee = $event->add('ATTENDEE', $message->sender, [
'PARTSTAT' => $instance['newstatus'], 'PARTSTAT' => $instance['newstatus']
]); ]);
if ($message->senderName) { if ($message->senderName) {
$attendee['CN'] = $message->senderName; $attendee['CN'] = $message->senderName;
} }
$hasReply = true; $hasReply = true;
} }
if ($hasReply) { if ($hasReply) {
$message->message = $icalMsg; $message->message = $icalMsg;
return [$message]; return [$message];
} else { } else {
return []; return [];
} }
} }
/** /**
@ -802,13 +824,12 @@ class Broker
* based on. * based on.
* 11. significantChangeHash * 11. significantChangeHash
* 12. status * 12. status
*
* @param VCalendar $calendar * @param VCalendar $calendar
* *
* @return array * @return array
*/ */
protected function parseEventInfo(VCalendar $calendar = null) protected function parseEventInfo(VCalendar $calendar = null) {
{
$uid = null; $uid = null;
$organizer = null; $organizer = null;
$organizerName = null; $organizerName = null;
@ -847,7 +868,7 @@ class Broker
$organizer = $vevent->ORGANIZER->getNormalizedValue(); $organizer = $vevent->ORGANIZER->getNormalizedValue();
$organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null; $organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null;
} else { } else {
if (strtoupper($organizer) !== strtoupper($vevent->ORGANIZER->getNormalizedValue())) { if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
} }
} }
@ -857,7 +878,7 @@ class Broker
null; null;
$organizerScheduleAgent = $organizerScheduleAgent =
isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
strtoupper((string) $vevent->ORGANIZER['SCHEDULE-AGENT']) : strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
'SERVER'; 'SERVER';
} }
if (is_null($sequence) && isset($vevent->SEQUENCE)) { if (is_null($sequence) && isset($vevent->SEQUENCE)) {
@ -873,7 +894,7 @@ class Broker
foreach ($vevent->select('RRULE') as $rr) { foreach ($vevent->select('RRULE') as $rr) {
foreach ($rr->getParts() as $key => $val) { foreach ($rr->getParts() as $key => $val) {
// ignore default values (https://github.com/sabre-io/vobject/issues/126) // ignore default values (https://github.com/sabre-io/vobject/issues/126)
if ('INTERVAL' === $key && 1 == $val) { if ($key === 'INTERVAL' && $val == 1) {
continue; continue;
} }
if (is_array($val)) { if (is_array($val)) {
@ -890,7 +911,7 @@ class Broker
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
if (is_null($timezone)) { if (is_null($timezone)) {
if ('master' === $recurId) { if ($recurId === 'master') {
$timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
} else { } else {
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone(); $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
@ -898,9 +919,10 @@ class Broker
} }
if (isset($vevent->ATTENDEE)) { if (isset($vevent->ATTENDEE)) {
foreach ($vevent->ATTENDEE as $attendee) { foreach ($vevent->ATTENDEE as $attendee) {
if ($this->scheduleAgentServerRules && if ($this->scheduleAgentServerRules &&
isset($attendee['SCHEDULE-AGENT']) && isset($attendee['SCHEDULE-AGENT']) &&
'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue()) strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
) { ) {
continue; continue;
} }
@ -914,42 +936,45 @@ class Broker
strtoupper($attendee['SCHEDULE-FORCE-SEND']) : strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
null; null;
if (isset($attendees[$attendee->getNormalizedValue()])) { if (isset($attendees[$attendee->getNormalizedValue()])) {
$attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [ $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
'id' => $recurId, 'id' => $recurId,
'partstat' => $partStat, 'partstat' => $partStat,
'forceSend' => $forceSend, 'forceSend' => $forceSend,
]; ];
} else { } else {
$attendees[$attendee->getNormalizedValue()] = [ $attendees[$attendee->getNormalizedValue()] = [
'href' => $attendee->getNormalizedValue(), 'href' => $attendee->getNormalizedValue(),
'instances' => [ 'instances' => [
$recurId => [ $recurId => [
'id' => $recurId, 'id' => $recurId,
'partstat' => $partStat, 'partstat' => $partStat,
], ],
], ],
'name' => isset($attendee['CN']) ? (string) $attendee['CN'] : null, 'name' => isset($attendee['CN']) ? (string)$attendee['CN'] : null,
'forceSend' => $forceSend, 'forceSend' => $forceSend,
]; ];
} }
} }
$instances[$recurId] = $vevent; $instances[$recurId] = $vevent;
} }
foreach ($this->significantChangeProperties as $prop) { foreach ($this->significantChangeProperties as $prop) {
if (isset($vevent->$prop)) { if (isset($vevent->$prop)) {
$propertyValues = $vevent->select($prop); $propertyValues = $vevent->select($prop);
$significantChangeHash .= $prop.':'; $significantChangeHash .= $prop . ':';
if ('EXDATE' === $prop) { if ($prop === 'EXDATE') {
$significantChangeHash .= implode(',', $exdate).';'; $significantChangeHash .= implode(',', $exdate) . ';';
} elseif ('RRULE' === $prop) { } elseif ($prop === 'RRULE') {
$significantChangeHash .= implode(',', $rrule).';'; $significantChangeHash .= implode(',', $rrule) . ';';
} else { } else {
foreach ($propertyValues as $val) { foreach ($propertyValues as $val) {
$significantChangeHash .= $val->getValue().';'; $significantChangeHash .= $val->getValue() . ';';
} }
} }
} }
@ -971,5 +996,7 @@ class Broker
'significantChangeHash', 'significantChangeHash',
'status' 'status'
); );
} }
} }

View File

@ -11,6 +11,5 @@ use Exception;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class ITipException extends Exception class ITipException extends Exception {
{
} }

View File

@ -14,8 +14,8 @@ namespace Sabre\VObject\ITip;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Message class Message {
{
/** /**
* The object's UID. * The object's UID.
* *
@ -123,14 +123,19 @@ class Message
* *
* @return mixed bool|string * @return mixed bool|string
*/ */
public function getScheduleStatus() function getScheduleStatus() {
{
if (!$this->scheduleStatus) {
return false;
} else {
list($scheduleStatus) = explode(';', $this->scheduleStatus);
if (!$this->scheduleStatus) {
return false;
} else {
list($scheduleStatus) = explode(';', $this->scheduleStatus);
return $scheduleStatus; return $scheduleStatus;
} }
} }
} }

View File

@ -13,6 +13,6 @@ namespace Sabre\VObject\ITip;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class SameOrganizerForAllComponentsException extends ITipException class SameOrganizerForAllComponentsException extends ITipException {
{
} }

View File

@ -10,6 +10,5 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class InvalidDataException extends \Exception class InvalidDataException extends \Exception {
{
} }

View File

@ -11,8 +11,13 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \JsonSerializable, Xml\XmlSerializable abstract class Node
{ implements \IteratorAggregate,
\ArrayAccess,
\Countable,
\JsonSerializable,
Xml\XmlSerializable {
/** /**
* The following constants are used by the validate() method. * The following constants are used by the validate() method.
* *
@ -65,7 +70,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return string * @return string
*/ */
abstract public function serialize(); abstract function serialize();
/** /**
* This method returns an array, with the representation as it should be * This method returns an array, with the representation as it should be
@ -73,26 +78,31 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return array * @return array
*/ */
abstract public function jsonSerialize(); abstract function jsonSerialize();
/** /**
* This method serializes the data into XML. This is used to create xCard or * This method serializes the data into XML. This is used to create xCard or
* xCal documents. * xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
abstract public function xmlSerialize(Xml\Writer $writer); abstract function xmlSerialize(Xml\Writer $writer);
/** /**
* Call this method on a document if you're done using it. * Call this method on a document if you're done using it.
* *
* It's intended to remove all circular references, so PHP can easily clean * It's intended to remove all circular references, so PHP can easily clean
* it up. * it up.
*
* @return void
*/ */
public function destroy() function destroy() {
{
$this->parent = null; $this->parent = null;
$this->root = null; $this->root = null;
} }
/* {{{ IteratorAggregator interface */ /* {{{ IteratorAggregator interface */
@ -102,13 +112,14 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return ElementList * @return ElementList
*/ */
public function getIterator() function getIterator() {
{
if (!is_null($this->iterator)) { if (!is_null($this->iterator)) {
return $this->iterator; return $this->iterator;
} }
return new ElementList([$this]); return new ElementList([$this]);
} }
/** /**
@ -117,10 +128,13 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* Note that this is not actually part of the iterator interface * Note that this is not actually part of the iterator interface
* *
* @param ElementList $iterator * @param ElementList $iterator
*
* @return void
*/ */
public function setIterator(ElementList $iterator) function setIterator(ElementList $iterator) {
{
$this->iterator = $iterator; $this->iterator = $iterator;
} }
/** /**
@ -145,9 +159,10 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
return []; return [];
} }
/* }}} */ /* }}} */
@ -159,17 +174,18 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return int * @return int
*/ */
public function count() function count() {
{
$it = $this->getIterator();
$it = $this->getIterator();
return $it->count(); return $it->count();
} }
/* }}} */ /* }}} */
/* {{{ ArrayAccess Interface */ /* {{{ ArrayAccess Interface */
/** /**
* Checks if an item exists through ArrayAccess. * Checks if an item exists through ArrayAccess.
* *
@ -179,11 +195,11 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return bool * @return bool
*/ */
public function offsetExists($offset) function offsetExists($offset) {
{
$iterator = $this->getIterator();
$iterator = $this->getIterator();
return $iterator->offsetExists($offset); return $iterator->offsetExists($offset);
} }
/** /**
@ -195,11 +211,11 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* @return mixed * @return mixed
*/ */
public function offsetGet($offset) function offsetGet($offset) {
{
$iterator = $this->getIterator();
$iterator = $this->getIterator();
return $iterator->offsetGet($offset); return $iterator->offsetGet($offset);
} }
/** /**
@ -207,20 +223,21 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* *
* This method just forwards the request to the inner iterator * This method just forwards the request to the inner iterator
* *
* @param int $offset * @param int $offset
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
public function offsetSet($offset, $value) function offsetSet($offset, $value) {
{
$iterator = $this->getIterator(); $iterator = $this->getIterator();
$iterator->offsetSet($offset, $value); $iterator->offsetSet($offset, $value);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
// //
// This method always throws an exception, so we ignore the closing // This method always throws an exception, so we ignore the closing
// brace // brace
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
/** /**
@ -229,18 +246,19 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
* This method just forwards the request to the inner iterator * This method just forwards the request to the inner iterator
* *
* @param int $offset * @param int $offset
*
* @return void
*/ */
public function offsetUnset($offset) function offsetUnset($offset) {
{
$iterator = $this->getIterator(); $iterator = $this->getIterator();
$iterator->offsetUnset($offset); $iterator->offsetUnset($offset);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
// //
// This method always throws an exception, so we ignore the closing // This method always throws an exception, so we ignore the closing
// brace // brace
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
/* }}} */ /* }}} */

View File

@ -3,7 +3,7 @@
namespace Sabre\VObject; namespace Sabre\VObject;
/** /**
* PHPUnit Assertions. * PHPUnit Assertions
* *
* This trait can be added to your unittest to make it easier to test iCalendar * This trait can be added to your unittest to make it easier to test iCalendar
* and/or vCards. * and/or vCards.
@ -12,8 +12,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
trait PHPUnitAssertions trait PHPUnitAssertions {
{
/** /**
* This method tests wether two vcards or icalendar objects are * This method tests wether two vcards or icalendar objects are
* semantically identical. * semantically identical.
@ -30,12 +30,13 @@ trait PHPUnitAssertions
* *
* @param resource|string|Component $expected * @param resource|string|Component $expected
* @param resource|string|Component $actual * @param resource|string|Component $actual
* @param string $message * @param string $message
*/ */
public function assertVObjectEqualsVObject($expected, $actual, $message = '') function assertVObjectEqualsVObject($expected, $actual, $message = '') {
{
$self = $this; $self = $this;
$getObj = function ($input) use ($self) { $getObj = function($input) use ($self) {
if (is_resource($input)) { if (is_resource($input)) {
$input = stream_get_contents($input); $input = stream_get_contents($input);
} }
@ -46,11 +47,11 @@ trait PHPUnitAssertions
$this->fail('Input must be a string, stream or VObject component'); $this->fail('Input must be a string, stream or VObject component');
} }
unset($input->PRODID); unset($input->PRODID);
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) { if ($input instanceof Component\VCalendar && (string)$input->CALSCALE === 'GREGORIAN') {
unset($input->CALSCALE); unset($input->CALSCALE);
} }
return $input; return $input;
}; };
$expected = $getObj($expected)->serialize(); $expected = $getObj($expected)->serialize();
@ -60,11 +61,13 @@ trait PHPUnitAssertions
preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER); preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER);
foreach ($matches as $match) { foreach ($matches as $match) {
$actual = preg_replace( $actual = preg_replace(
'|^'.preg_quote($match[1], '|').':(.*)\r$|m', '|^' . preg_quote($match[1], '|') . ':(.*)\r$|m',
$match[1].':**ANY**'."\r", $match[1] . ':**ANY**' . "\r",
$actual $actual
); );
} }
$this->assertEquals( $this->assertEquals(
@ -72,5 +75,8 @@ trait PHPUnitAssertions
$actual, $actual,
$message $message
); );
} }
} }

View File

@ -17,8 +17,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Parameter extends Node class Parameter extends Node {
{
/** /**
* Parameter name. * Parameter name.
* *
@ -29,7 +29,7 @@ class Parameter extends Node
/** /**
* vCard 2.1 allows parameters to be encoded without a name. * vCard 2.1 allows parameters to be encoded without a name.
* *
* We can deduce the parameter name based on its value. * We can deduce the parameter name based on it's value.
* *
* @var bool * @var bool
*/ */
@ -50,8 +50,8 @@ class Parameter extends Node
* @param string $name * @param string $name
* @param string $value * @param string $value
*/ */
public function __construct(Document $root, $name, $value = null) function __construct(Document $root, $name, $value = null) {
{
$this->name = strtoupper($name); $this->name = strtoupper($name);
$this->root = $root; $this->root = $root;
if (is_null($name)) { if (is_null($name)) {
@ -62,12 +62,13 @@ class Parameter extends Node
// If guessParameterNameByValue() returns an empty string // If guessParameterNameByValue() returns an empty string
// above, we're actually dealing with a parameter that has no value. // above, we're actually dealing with a parameter that has no value.
// In that case we have to move the value to the name. // In that case we have to move the value to the name.
if ('' === $this->name) { if ($this->name === '') {
$this->noName = false; $this->noName = false;
$this->name = strtoupper($value); $this->name = strtoupper($value);
} else { } else {
$this->setValue($value); $this->setValue($value);
} }
} }
/** /**
@ -81,85 +82,85 @@ class Parameter extends Node
* *
* @return string * @return string
*/ */
public static function guessParameterNameByValue($value) static function guessParameterNameByValue($value) {
{
switch (strtoupper($value)) { switch (strtoupper($value)) {
// Encodings // Encodings
case '7-BIT': case '7-BIT' :
case 'QUOTED-PRINTABLE': case 'QUOTED-PRINTABLE' :
case 'BASE64': case 'BASE64' :
$name = 'ENCODING'; $name = 'ENCODING';
break; break;
// Common types // Common types
case 'WORK': case 'WORK' :
case 'HOME': case 'HOME' :
case 'PREF': case 'PREF' :
// Delivery Label Type // Delivery Label Type
case 'DOM': case 'DOM' :
case 'INTL': case 'INTL' :
case 'POSTAL': case 'POSTAL' :
case 'PARCEL': case 'PARCEL' :
// Telephone types // Telephone types
case 'VOICE': case 'VOICE' :
case 'FAX': case 'FAX' :
case 'MSG': case 'MSG' :
case 'CELL': case 'CELL' :
case 'PAGER': case 'PAGER' :
case 'BBS': case 'BBS' :
case 'MODEM': case 'MODEM' :
case 'CAR': case 'CAR' :
case 'ISDN': case 'ISDN' :
case 'VIDEO': case 'VIDEO' :
// EMAIL types (lol) // EMAIL types (lol)
case 'AOL': case 'AOL' :
case 'APPLELINK': case 'APPLELINK' :
case 'ATTMAIL': case 'ATTMAIL' :
case 'CIS': case 'CIS' :
case 'EWORLD': case 'EWORLD' :
case 'INTERNET': case 'INTERNET' :
case 'IBMMAIL': case 'IBMMAIL' :
case 'MCIMAIL': case 'MCIMAIL' :
case 'POWERSHARE': case 'POWERSHARE' :
case 'PRODIGY': case 'PRODIGY' :
case 'TLX': case 'TLX' :
case 'X400': case 'X400' :
// Photo / Logo format types // Photo / Logo format types
case 'GIF': case 'GIF' :
case 'CGM': case 'CGM' :
case 'WMF': case 'WMF' :
case 'BMP': case 'BMP' :
case 'DIB': case 'DIB' :
case 'PICT': case 'PICT' :
case 'TIFF': case 'TIFF' :
case 'PDF': case 'PDF' :
case 'PS': case 'PS' :
case 'JPEG': case 'JPEG' :
case 'MPEG': case 'MPEG' :
case 'MPEG2': case 'MPEG2' :
case 'AVI': case 'AVI' :
case 'QTIME': case 'QTIME' :
// Sound Digital Audio Type // Sound Digital Audio Type
case 'WAVE': case 'WAVE' :
case 'PCM': case 'PCM' :
case 'AIFF': case 'AIFF' :
// Key types // Key types
case 'X509': case 'X509' :
case 'PGP': case 'PGP' :
$name = 'TYPE'; $name = 'TYPE';
break; break;
// Value types // Value types
case 'INLINE': case 'INLINE' :
case 'URL': case 'URL' :
case 'CONTENT-ID': case 'CONTENT-ID' :
case 'CID': case 'CID' :
$name = 'VALUE'; $name = 'VALUE';
break; break;
@ -176,10 +177,13 @@ class Parameter extends Node
* This may be either a single, or multiple strings in an array. * This may be either a single, or multiple strings in an array.
* *
* @param string|array $value * @param string|array $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
$this->value = $value; $this->value = $value;
} }
/** /**
@ -190,23 +194,27 @@ class Parameter extends Node
* *
* @return string|null * @return string|null
*/ */
public function getValue() function getValue() {
{
if (is_array($this->value)) { if (is_array($this->value)) {
return implode(',', $this->value); return implode(',', $this->value);
} else { } else {
return $this->value; return $this->value;
} }
} }
/** /**
* Sets multiple values for this parameter. * Sets multiple values for this parameter.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setParts(array $value) function setParts(array $value) {
{
$this->value = $value; $this->value = $value;
} }
/** /**
@ -216,8 +224,8 @@ class Parameter extends Node
* *
* @return array * @return array
*/ */
public function getParts() function getParts() {
{
if (is_array($this->value)) { if (is_array($this->value)) {
return $this->value; return $this->value;
} elseif (is_null($this->value)) { } elseif (is_null($this->value)) {
@ -225,6 +233,7 @@ class Parameter extends Node
} else { } else {
return [$this->value]; return [$this->value];
} }
} }
/** /**
@ -234,14 +243,17 @@ class Parameter extends Node
* parameter value list. * parameter value list.
* *
* @param string|array $part * @param string|array $part
*
* @return void
*/ */
public function addValue($part) function addValue($part) {
{
if (is_null($this->value)) { if (is_null($this->value)) {
$this->value = $part; $this->value = $part;
} else { } else {
$this->value = array_merge((array) $this->value, (array) $part); $this->value = array_merge((array)$this->value, (array)$part);
} }
} }
/** /**
@ -255,12 +267,13 @@ class Parameter extends Node
* *
* @return bool * @return bool
*/ */
public function has($value) function has($value) {
{
return in_array( return in_array(
strtolower($value), strtolower($value),
array_map('strtolower', (array) $this->value) array_map('strtolower', (array)$this->value)
); );
} }
/** /**
@ -268,24 +281,25 @@ class Parameter extends Node
* *
* @return string * @return string
*/ */
public function serialize() function serialize() {
{
$value = $this->getParts(); $value = $this->getParts();
if (0 === count($value)) { if (count($value) === 0) {
return $this->name.'='; return $this->name . '=';
} }
if (Document::VCARD21 === $this->root->getDocumentType() && $this->noName) { if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
return implode(';', $value); return implode(';', $value);
} }
return $this->name.'='.array_reduce( return $this->name . '=' . array_reduce(
$value, $value,
function ($out, $item) { function($out, $item) {
if (!is_null($out)) {
$out .= ','; if (!is_null($out)) $out .= ',';
}
// If there's no special characters in the string, we'll use the simple // If there's no special characters in the string, we'll use the simple
// format. // format.
@ -304,23 +318,24 @@ class Parameter extends Node
// severaly trips on + characters not being quoted, so we // severaly trips on + characters not being quoted, so we
// added + as well. // added + as well.
if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
return $out.$item; return $out . $item;
} else { } else {
// Enclosing in double-quotes, and using RFC6868 for encoding any // Enclosing in double-quotes, and using RFC6868 for encoding any
// special characters // special characters
$out .= '"'.strtr( $out .= '"' . strtr(
$item, $item,
[ [
'^' => '^^', '^' => '^^',
"\n" => '^n', "\n" => '^n',
'"' => '^\'', '"' => '^\'',
] ]
).'"'; ) . '"';
return $out; return $out;
} }
} }
); );
} }
/** /**
@ -329,22 +344,26 @@ class Parameter extends Node
* *
* @return array * @return array
*/ */
public function jsonSerialize() function jsonSerialize() {
{
return $this->value; return $this->value;
} }
/** /**
* This method serializes the data into XML. This is used to create xCard or * This method serializes the data into XML. This is used to create xCard or
* xCal documents. * xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
public function xmlSerialize(Xml\Writer $writer) function xmlSerialize(Xml\Writer $writer) {
{
foreach (explode(',', $this->value) as $value) { foreach (explode(',', $this->value) as $value) {
$writer->writeElement('text', $value); $writer->writeElement('text', $value);
} }
} }
/** /**
@ -352,9 +371,10 @@ class Parameter extends Node
* *
* @return string * @return string
*/ */
public function __toString() function __toString() {
{
return (string) $this->getValue(); return (string)$this->getValue();
} }
/** /**
@ -362,12 +382,13 @@ class Parameter extends Node
* *
* @return ElementList * @return ElementList
*/ */
public function getIterator() function getIterator() {
{
if (!is_null($this->iterator)) { if (!is_null($this->iterator))
return $this->iterator; return $this->iterator;
}
return $this->iterator = new ArrayIterator((array)$this->value);
return $this->iterator = new ArrayIterator((array) $this->value);
} }
} }

View File

@ -9,6 +9,5 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class ParseException extends \Exception class ParseException extends \Exception {
{
} }

View File

@ -4,7 +4,6 @@ namespace Sabre\VObject\Parser;
use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard; use Sabre\VObject\Component\VCard;
use Sabre\VObject\Document;
use Sabre\VObject\EofException; use Sabre\VObject\EofException;
use Sabre\VObject\ParseException; use Sabre\VObject\ParseException;
@ -17,8 +16,8 @@ use Sabre\VObject\ParseException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Json extends Parser class Json extends Parser {
{
/** /**
* The input data. * The input data.
* *
@ -42,12 +41,12 @@ class Json extends Parser
* If either input or options are not supplied, the defaults will be used. * If either input or options are not supplied, the defaults will be used.
* *
* @param resource|string|array|null $input * @param resource|string|array|null $input
* @param int $options * @param int $options
* *
* @return \Sabre\VObject\Document * @return Sabre\VObject\Document
*/ */
public function parse($input = null, $options = 0) function parse($input = null, $options = 0) {
{
if (!is_null($input)) { if (!is_null($input)) {
$this->setInput($input); $this->setInput($input);
} }
@ -60,28 +59,28 @@ class Json extends Parser
} }
switch ($this->input[0]) { switch ($this->input[0]) {
case 'vcalendar': case 'vcalendar' :
$this->root = new VCalendar([], false); $this->root = new VCalendar([], false);
break; break;
case 'vcard': case 'vcard' :
$this->root = new VCard([], false); $this->root = new VCard([], false);
break; break;
default: default :
throw new ParseException('The root component must either be a vcalendar, or a vcard'); throw new ParseException('The root component must either be a vcalendar, or a vcard');
} }
foreach ($this->input[1] as $prop) { foreach ($this->input[1] as $prop) {
$this->root->add($this->parseProperty($prop)); $this->root->add($this->parseProperty($prop));
} }
if (isset($this->input[2])) { if (isset($this->input[2])) foreach ($this->input[2] as $comp) {
foreach ($this->input[2] as $comp) { $this->root->add($this->parseComponent($comp));
$this->root->add($this->parseComponent($comp));
}
} }
// Resetting the input so we can throw an feof exception the next time. // Resetting the input so we can throw an feof exception the next time.
$this->input = null; $this->input = null;
return $this->root; return $this->root;
} }
/** /**
@ -91,34 +90,35 @@ class Json extends Parser
* *
* @return \Sabre\VObject\Component * @return \Sabre\VObject\Component
*/ */
public function parseComponent(array $jComp) function parseComponent(array $jComp) {
{
// We can remove $self from PHP 5.4 onward. // We can remove $self from PHP 5.4 onward.
$self = $this; $self = $this;
$properties = array_map( $properties = array_map(
function ($jProp) use ($self) { function($jProp) use ($self) {
return $self->parseProperty($jProp); return $self->parseProperty($jProp);
}, },
$jComp[1] $jComp[1]
); );
if (isset($jComp[2])) { if (isset($jComp[2])) {
$components = array_map( $components = array_map(
function ($jComp) use ($self) { function($jComp) use ($self) {
return $self->parseComponent($jComp); return $self->parseComponent($jComp);
}, },
$jComp[2] $jComp[2]
); );
} else {
$components = []; } else $components = [];
}
return $this->root->createComponent( return $this->root->createComponent(
$jComp[0], $jComp[0],
array_merge($properties, $components), array_merge($properties, $components),
$defaults = false $defaults = false
); );
} }
/** /**
@ -128,8 +128,8 @@ class Json extends Parser
* *
* @return \Sabre\VObject\Property * @return \Sabre\VObject\Property
*/ */
public function parseProperty(array $jProp) function parseProperty(array $jProp) {
{
list( list(
$propertyName, $propertyName,
$parameters, $parameters,
@ -142,14 +142,14 @@ class Json extends Parser
// value type. We're using this value later in this function. // value type. We're using this value later in this function.
$defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
$parameters = (array) $parameters; $parameters = (array)$parameters;
$value = array_slice($jProp, 3); $value = array_slice($jProp, 3);
$valueType = strtoupper($valueType); $valueType = strtoupper($valueType);
if (isset($parameters['group'])) { if (isset($parameters['group'])) {
$propertyName = $parameters['group'].'.'.$propertyName; $propertyName = $parameters['group'] . '.' . $propertyName;
unset($parameters['group']); unset($parameters['group']);
} }
@ -160,7 +160,7 @@ class Json extends Parser
// represents TEXT values. We have to normalize these here. In the // 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 // future we can get rid of FlatText once we're allowed to break BC
// again. // again.
if ('Sabre\VObject\Property\FlatText' === $defaultPropertyClass) { if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
$defaultPropertyClass = 'Sabre\VObject\Property\Text'; $defaultPropertyClass = 'Sabre\VObject\Property\Text';
} }
@ -168,19 +168,22 @@ class Json extends Parser
// type for the given property (e.g.: BDAY), we need to add a VALUE= // type for the given property (e.g.: BDAY), we need to add a VALUE=
// parameter. // parameter.
if ($defaultPropertyClass !== get_class($prop)) { if ($defaultPropertyClass !== get_class($prop)) {
$prop['VALUE'] = $valueType; $prop["VALUE"] = $valueType;
} }
return $prop; return $prop;
} }
/** /**
* Sets the input data. * Sets the input data.
* *
* @param resource|string|array $input * @param resource|string|array $input
*
* @return void
*/ */
public function setInput($input) function setInput($input) {
{
if (is_resource($input)) { if (is_resource($input)) {
$input = stream_get_contents($input); $input = stream_get_contents($input);
} }
@ -188,5 +191,7 @@ class Json extends Parser
$input = json_decode($input); $input = json_decode($input);
} }
$this->input = $input; $this->input = $input;
} }
} }

View File

@ -7,7 +7,6 @@ use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard; use Sabre\VObject\Component\VCard;
use Sabre\VObject\Document; use Sabre\VObject\Document;
use Sabre\VObject\EofException; use Sabre\VObject\EofException;
use Sabre\VObject\Node;
use Sabre\VObject\ParseException; use Sabre\VObject\ParseException;
/** /**
@ -23,8 +22,8 @@ use Sabre\VObject\ParseException;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class MimeDir extends Parser class MimeDir extends Parser {
{
/** /**
* The input stream. * The input stream.
* *
@ -71,12 +70,12 @@ class MimeDir extends Parser
* used. * used.
* *
* @param string|resource|null $input * @param string|resource|null $input
* @param int $options * @param int $options
* *
* @return \Sabre\VObject\Document * @return Sabre\VObject\Document
*/ */
public function parse($input = null, $options = 0) function parse($input = null, $options = 0) {
{
$this->root = null; $this->root = null;
if (!is_null($input)) { if (!is_null($input)) {
@ -90,6 +89,7 @@ class MimeDir extends Parser
$this->parseDocument(); $this->parseDocument();
return $this->root; return $this->root;
} }
/** /**
@ -104,21 +104,24 @@ class MimeDir extends Parser
* *
* @param string $charset * @param string $charset
*/ */
public function setCharset($charset) function setCharset($charset) {
{
if (!in_array($charset, self::$SUPPORTED_CHARSETS)) { if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')'); throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')');
} }
$this->charset = $charset; $this->charset = $charset;
} }
/** /**
* Sets the input buffer. Must be a string or stream. * Sets the input buffer. Must be a string or stream.
* *
* @param resource|string $input * @param resource|string $input
*
* @return void
*/ */
public function setInput($input) function setInput($input) {
{
// Resetting the parser // Resetting the parser
$this->lineIndex = 0; $this->lineIndex = 0;
$this->startLine = 0; $this->startLine = 0;
@ -134,53 +137,59 @@ class MimeDir extends Parser
} else { } else {
throw new \InvalidArgumentException('This parser can only read from strings or streams.'); throw new \InvalidArgumentException('This parser can only read from strings or streams.');
} }
} }
/** /**
* Parses an entire document. * Parses an entire document.
*
* @return void
*/ */
protected function parseDocument() protected function parseDocument() {
{
$line = $this->readLine(); $line = $this->readLine();
// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
// It's 0xEF 0xBB 0xBF in UTF-8 hex. // It's 0xEF 0xBB 0xBF in UTF-8 hex.
if (3 <= strlen($line) if (3 <= strlen($line)
&& 0xef === ord($line[0]) && ord($line[0]) === 0xef
&& 0xbb === ord($line[1]) && ord($line[1]) === 0xbb
&& 0xbf === ord($line[2])) { && ord($line[2]) === 0xbf) {
$line = substr($line, 3); $line = substr($line, 3);
} }
switch (strtoupper($line)) { switch (strtoupper($line)) {
case 'BEGIN:VCALENDAR': case 'BEGIN:VCALENDAR' :
$class = VCalendar::$componentMap['VCALENDAR']; $class = VCalendar::$componentMap['VCALENDAR'];
break; break;
case 'BEGIN:VCARD': case 'BEGIN:VCARD' :
$class = VCard::$componentMap['VCARD']; $class = VCard::$componentMap['VCARD'];
break; break;
default: default :
throw new ParseException('This parser only supports VCARD and VCALENDAR files'); throw new ParseException('This parser only supports VCARD and VCALENDAR files');
} }
$this->root = new $class([], false); $this->root = new $class([], false);
while (true) { while (true) {
// Reading until we hit END: // Reading until we hit END:
$line = $this->readLine(); $line = $this->readLine();
if ('END:' === strtoupper(substr($line, 0, 4))) { if (strtoupper(substr($line, 0, 4)) === 'END:') {
break; break;
} }
$result = $this->parseLine($line); $result = $this->parseLine($line);
if ($result) { if ($result) {
$this->root->add($result); $this->root->add($result);
} }
} }
$name = strtoupper(substr($line, 4)); $name = strtoupper(substr($line, 4));
if ($name !== $this->root->name) { if ($name !== $this->root->name) {
throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"'); throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
} }
} }
/** /**
@ -191,40 +200,46 @@ class MimeDir extends Parser
* *
* @return Node * @return Node
*/ */
protected function parseLine($line) protected function parseLine($line) {
{
// Start of a new component // Start of a new component
if ('BEGIN:' === strtoupper(substr($line, 0, 6))) { if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
$component = $this->root->createComponent(substr($line, 6), [], false); $component = $this->root->createComponent(substr($line, 6), [], false);
while (true) { while (true) {
// Reading until we hit END: // Reading until we hit END:
$line = $this->readLine(); $line = $this->readLine();
if ('END:' === strtoupper(substr($line, 0, 4))) { if (strtoupper(substr($line, 0, 4)) === 'END:') {
break; break;
} }
$result = $this->parseLine($line); $result = $this->parseLine($line);
if ($result) { if ($result) {
$component->add($result); $component->add($result);
} }
} }
$name = strtoupper(substr($line, 4)); $name = strtoupper(substr($line, 4));
if ($name !== $component->name) { if ($name !== $component->name) {
throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"'); throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
} }
return $component; return $component;
} else { } else {
// Property reader // Property reader
$property = $this->readProperty($line); $property = $this->readProperty($line);
if (!$property) { if (!$property) {
// Ignored line // Ignored line
return false; return false;
} }
return $property; return $property;
} }
} }
/** /**
@ -233,7 +248,7 @@ class MimeDir extends Parser
* *
* If that was not the case, we store it here. * If that was not the case, we store it here.
* *
* @var string|null * @var null|string
*/ */
protected $lineBuffer; protected $lineBuffer;
@ -266,8 +281,8 @@ class MimeDir extends Parser
* *
* @return string * @return string
*/ */
protected function readLine() protected function readLine() {
{
if (!\is_null($this->lineBuffer)) { if (!\is_null($this->lineBuffer)) {
$rawLine = $this->lineBuffer; $rawLine = $this->lineBuffer;
$this->lineBuffer = null; $this->lineBuffer = null;
@ -277,15 +292,15 @@ class MimeDir extends Parser
$rawLine = \fgets($this->input); $rawLine = \fgets($this->input);
if ($eof || (\feof($this->input) && false === $rawLine)) { if ($eof || (\feof($this->input) && $rawLine === false)) {
throw new EofException('End of document reached prematurely'); throw new EofException('End of document reached prematurely');
} }
if (false === $rawLine) { if ($rawLine === false) {
throw new ParseException('Error reading from input stream'); throw new ParseException('Error reading from input stream');
} }
$rawLine = \rtrim($rawLine, "\r\n"); $rawLine = \rtrim($rawLine, "\r\n");
} while ('' === $rawLine); // Skipping empty lines } while ($rawLine === ''); // Skipping empty lines
++$this->lineIndex; $this->lineIndex++;
} }
$line = $rawLine; $line = $rawLine;
@ -293,30 +308,34 @@ class MimeDir extends Parser
// Looking ahead for folded lines. // Looking ahead for folded lines.
while (true) { while (true) {
$nextLine = \rtrim(\fgets($this->input), "\r\n"); $nextLine = \rtrim(\fgets($this->input), "\r\n");
++$this->lineIndex; $this->lineIndex++;
if (!$nextLine) { if (!$nextLine) {
break; break;
} }
if ("\t" === $nextLine[0] || ' ' === $nextLine[0]) { if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
$curLine = \substr($nextLine, 1); $curLine = \substr($nextLine, 1);
$line .= $curLine; $line .= $curLine;
$rawLine .= "\n ".$curLine; $rawLine .= "\n " . $curLine;
} else { } else {
$this->lineBuffer = $nextLine; $this->lineBuffer = $nextLine;
break; break;
} }
} }
$this->rawLine = $rawLine; $this->rawLine = $rawLine;
return $line; return $line;
} }
/** /**
* Reads a property or component from a line. * Reads a property or component from a line.
*
* @return void
*/ */
protected function readProperty($line) protected function readProperty($line) {
{
if ($this->options & self::OPTION_FORGIVING) { if ($this->options & self::OPTION_FORGIVING) {
$propNameToken = 'A-Z0-9\-\._\\/'; $propNameToken = 'A-Z0-9\-\._\\/';
} else { } else {
@ -341,17 +360,17 @@ class MimeDir extends Parser
/xi"; /xi";
//echo $regex, "\n"; die(); //echo $regex, "\n"; die();
preg_match_all($regex, $line, $matches, PREG_SET_ORDER); preg_match_all($regex, $line, $matches, PREG_SET_ORDER);
$property = [ $property = [
'name' => null, 'name' => null,
'parameters' => [], 'parameters' => [],
'value' => null, 'value' => null
]; ];
$lastParam = null; $lastParam = null;
/* /**
* Looping through all the tokens. * Looping through all the tokens.
* *
* Note that we are looping through them in reverse order, because if a * Note that we are looping through them in reverse order, because if a
@ -359,8 +378,9 @@ class MimeDir extends Parser
* in the result. * in the result.
*/ */
foreach ($matches as $match) { foreach ($matches as $match) {
if (isset($match['paramValue'])) { if (isset($match['paramValue'])) {
if ($match['paramValue'] && '"' === $match['paramValue'][0]) { if ($match['paramValue'] && $match['paramValue'][0] === '"') {
$value = substr($match['paramValue'], 1, -1); $value = substr($match['paramValue'], 1, -1);
} else { } else {
$value = $match['paramValue']; $value = $match['paramValue'];
@ -369,7 +389,7 @@ class MimeDir extends Parser
$value = $this->unescapeParam($value); $value = $this->unescapeParam($value);
if (is_null($lastParam)) { if (is_null($lastParam)) {
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions'); throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
} }
if (is_null($property['parameters'][$lastParam])) { if (is_null($property['parameters'][$lastParam])) {
$property['parameters'][$lastParam] = $value; $property['parameters'][$lastParam] = $value;
@ -378,7 +398,7 @@ class MimeDir extends Parser
} else { } else {
$property['parameters'][$lastParam] = [ $property['parameters'][$lastParam] = [
$property['parameters'][$lastParam], $property['parameters'][$lastParam],
$value, $value
]; ];
} }
continue; continue;
@ -402,6 +422,7 @@ class MimeDir extends Parser
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
throw new \LogicException('This code should not be reachable'); throw new \LogicException('This code should not be reachable');
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
if (is_null($property['value'])) { if (is_null($property['value'])) {
@ -411,11 +432,11 @@ class MimeDir extends Parser
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
return false; return false;
} }
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions'); 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 // vCard 2.1 states that parameters may appear without a name, and only
// a value. We can deduce the value based on its name. // a value. We can deduce the value based on it's name.
// //
// Our parser will get those as parameters without a value instead, so // Our parser will get those as parameters without a value instead, so
// we're filtering these parameters out first. // we're filtering these parameters out first.
@ -436,30 +457,31 @@ class MimeDir extends Parser
$propObj->add(null, $namelessParameter); $propObj->add(null, $namelessParameter);
} }
if ('QUOTED-PRINTABLE' === strtoupper($propObj['ENCODING'])) { if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
$propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
} else { } else {
$charset = $this->charset; $charset = $this->charset;
if (Document::VCARD21 === $this->root->getDocumentType() && isset($propObj['CHARSET'])) { if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) {
// vCard 2.1 allows the character set to be specified per property. // vCard 2.1 allows the character set to be specified per property.
$charset = (string) $propObj['CHARSET']; $charset = (string)$propObj['CHARSET'];
} }
switch (strtolower($charset)) { switch (strtolower($charset)) {
case 'utf-8': case 'utf-8' :
break; break;
case 'iso-8859-1': case 'iso-8859-1' :
$property['value'] = utf8_encode($property['value']); $property['value'] = utf8_encode($property['value']);
break; break;
case 'windows-1252': case 'windows-1252' :
$property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset); $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
break; break;
default: default :
throw new ParseException('Unsupported CHARSET: '.$propObj['CHARSET']); throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']);
} }
$propObj->setRawMimeDirValue($property['value']); $propObj->setRawMimeDirValue($property['value']);
} }
return $propObj; return $propObj;
} }
/** /**
@ -524,11 +546,11 @@ class MimeDir extends Parser
* *
* @return string|string[] * @return string|string[]
*/ */
public static function unescapeValue($input, $delimiter = ';') static function unescapeValue($input, $delimiter = ';') {
{
$regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )'; $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
if ($delimiter) { if ($delimiter) {
$regex .= ' | ('.$delimiter.')'; $regex .= ' | (' . $delimiter . ')';
} }
$regex .= ') #x'; $regex .= ') #x';
@ -538,33 +560,36 @@ class MimeDir extends Parser
$result = ''; $result = '';
foreach ($matches as $match) { foreach ($matches as $match) {
switch ($match) { switch ($match) {
case '\\\\': case '\\\\' :
$result .= '\\'; $result .= '\\';
break; break;
case '\N': case '\N' :
case '\n': case '\n' :
$result .= "\n"; $result .= "\n";
break; break;
case '\;': case '\;' :
$result .= ';'; $result .= ';';
break; break;
case '\,': case '\,' :
$result .= ','; $result .= ',';
break; break;
case $delimiter: case $delimiter :
$resultArray[] = $result; $resultArray[] = $result;
$result = ''; $result = '';
break; break;
default: default :
$result .= $match; $result .= $match;
break; break;
} }
} }
$resultArray[] = $result; $resultArray[] = $result;
return $delimiter ? $resultArray : $result; return $delimiter ? $resultArray : $result;
} }
/** /**
@ -598,19 +623,21 @@ class MimeDir extends Parser
* * " is encoded as ^' * * " is encoded as ^'
* *
* @param string $input * @param string $input
*
* @return void
*/ */
private function unescapeParam($input) private function unescapeParam($input) {
{
return return
preg_replace_callback( preg_replace_callback(
'#(\^(\^|n|\'))#', '#(\^(\^|n|\'))#',
function ($matches) { function($matches) {
switch ($matches[2]) { switch ($matches[2]) {
case 'n': case 'n' :
return "\n"; return "\n";
case '^': case '^' :
return '^'; return '^';
case '\'': case '\'' :
return '"'; return '"';
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@ -631,8 +658,8 @@ class MimeDir extends Parser
* *
* @return string * @return string
*/ */
private function extractQuotedPrintableValue() private function extractQuotedPrintableValue() {
{
// We need to parse the raw line again to get the start of the value. // 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 // We are basically looking for the first colon (:), but we need to
@ -655,14 +682,16 @@ class MimeDir extends Parser
// missing a whitespace. So if 'forgiving' is turned on, we will take // missing a whitespace. So if 'forgiving' is turned on, we will take
// those as well. // those as well.
if ($this->options & self::OPTION_FORGIVING) { if ($this->options & self::OPTION_FORGIVING) {
while ('=' === substr($value, -1)) { while (substr($value, -1) === '=') {
// Reading the line // Reading the line
$this->readLine(); $this->readLine();
// Grabbing the raw form // Grabbing the raw form
$value .= "\n".$this->rawLine; $value .= "\n" . $this->rawLine;
} }
} }
return $value; return $value;
} }
} }

View File

@ -11,8 +11,8 @@ namespace Sabre\VObject\Parser;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
abstract class Parser abstract class Parser {
{
/** /**
* Turning on this option makes the parser more forgiving. * Turning on this option makes the parser more forgiving.
* *
@ -41,10 +41,12 @@ abstract class Parser
* Optionally, it's possible to parse the input stream here. * Optionally, it's possible to parse the input stream here.
* *
* @param mixed $input * @param mixed $input
* @param int $options any parser options (OPTION constants) * @param int $options Any parser options (OPTION constants).
*
* @return void
*/ */
public function __construct($input = null, $options = 0) function __construct($input = null, $options = 0) {
{
if (!is_null($input)) { if (!is_null($input)) {
$this->setInput($input); $this->setInput($input);
} }
@ -60,16 +62,19 @@ abstract class Parser
* If either input or options are not supplied, the defaults will be used. * If either input or options are not supplied, the defaults will be used.
* *
* @param mixed $input * @param mixed $input
* @param int $options * @param int $options
* *
* @return array * @return array
*/ */
abstract public function parse($input = null, $options = 0); abstract function parse($input = null, $options = 0);
/** /**
* Sets the input data. * Sets the input data.
* *
* @param mixed $input * @param mixed $input
*
* @return void
*/ */
abstract public function setInput($input); abstract function setInput($input);
} }

View File

@ -18,8 +18,8 @@ use Sabre\Xml as SabreXml;
* @author Ivan Enderlin * @author Ivan Enderlin
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class XML extends Parser class XML extends Parser {
{
const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0'; const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0';
const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0'; const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';
@ -40,7 +40,7 @@ class XML extends Parser
/** /**
* Document, root component. * Document, root component.
* *
* @var \Sabre\VObject\Document * @var Sabre\VObject\Document
*/ */
protected $root; protected $root;
@ -50,29 +50,32 @@ class XML extends Parser
* Optionally, it's possible to parse the input stream here. * Optionally, it's possible to parse the input stream here.
* *
* @param mixed $input * @param mixed $input
* @param int $options any parser options (OPTION constants) * @param int $options Any parser options (OPTION constants).
*
* @return void
*/ */
public function __construct($input = null, $options = 0) function __construct($input = null, $options = 0) {
{
if (0 === $options) { if (0 === $options) {
$options = parent::OPTION_FORGIVING; $options = parent::OPTION_FORGIVING;
} }
parent::__construct($input, $options); parent::__construct($input, $options);
} }
/** /**
* Parse xCal or xCard. * Parse xCal or xCard.
* *
* @param resource|string $input * @param resource|string $input
* @param int $options * @param int $options
* *
* @throws \Exception * @throws \Exception
* *
* @return \Sabre\VObject\Document * @return Sabre\VObject\Document
*/ */
public function parse($input = null, $options = 0) function parse($input = null, $options = 0) {
{
if (!is_null($input)) { if (!is_null($input)) {
$this->setInput($input); $this->setInput($input);
} }
@ -86,25 +89,29 @@ class XML extends Parser
} }
switch ($this->input['name']) { switch ($this->input['name']) {
case '{'.self::XCAL_NAMESPACE.'}icalendar':
case '{' . self::XCAL_NAMESPACE . '}icalendar':
$this->root = new VCalendar([], false); $this->root = new VCalendar([], false);
$this->pointer = &$this->input['value'][0]; $this->pointer = &$this->input['value'][0];
$this->parseVCalendarComponents($this->root); $this->parseVCalendarComponents($this->root);
break; break;
case '{'.self::XCARD_NAMESPACE.'}vcards': case '{' . self::XCARD_NAMESPACE . '}vcards':
foreach ($this->input['value'] as &$vCard) { foreach ($this->input['value'] as &$vCard) {
$this->root = new VCard(['version' => '4.0'], false); $this->root = new VCard(['version' => '4.0'], false);
$this->pointer = &$vCard; $this->pointer = &$vCard;
$this->parseVCardComponents($this->root); $this->parseVCardComponents($this->root);
// We just parse the first <vcard /> element. // We just parse the first <vcard /> element.
break; break;
} }
break; break;
default: default:
throw new ParseException('Unsupported XML standard'); throw new ParseException('Unsupported XML standard');
} }
return $this->root; return $this->root;
@ -114,11 +121,15 @@ class XML extends Parser
* Parse a xCalendar component. * Parse a xCalendar component.
* *
* @param Component $parentComponent * @param Component $parentComponent
*
* @return void
*/ */
protected function parseVCalendarComponents(Component $parentComponent) protected function parseVCalendarComponents(Component $parentComponent) {
{
foreach ($this->pointer['value'] ?: [] as $children) { foreach ($this->pointer['value'] ?: [] as $children) {
switch (static::getTagName($children['name'])) { switch (static::getTagName($children['name'])) {
case 'properties': case 'properties':
$this->pointer = &$children['value']; $this->pointer = &$children['value'];
$this->parseProperties($parentComponent); $this->parseProperties($parentComponent);
@ -130,28 +141,35 @@ class XML extends Parser
break; break;
} }
} }
} }
/** /**
* Parse a xCard component. * Parse a xCard component.
* *
* @param Component $parentComponent * @param Component $parentComponent
*
* @return void
*/ */
protected function parseVCardComponents(Component $parentComponent) protected function parseVCardComponents(Component $parentComponent) {
{
$this->pointer = &$this->pointer['value']; $this->pointer = &$this->pointer['value'];
$this->parseProperties($parentComponent); $this->parseProperties($parentComponent);
} }
/** /**
* Parse xCalendar and xCard properties. * Parse xCalendar and xCard properties.
* *
* @param Component $parentComponent * @param Component $parentComponent
* @param string $propertyNamePrefix * @param string $propertyNamePrefix
*
* @return void
*/ */
protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') {
{
foreach ($this->pointer ?: [] as $xmlProperty) { foreach ($this->pointer ?: [] as $xmlProperty) {
list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']); list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
$propertyName = $tagName; $propertyName = $tagName;
@ -160,16 +178,17 @@ class XML extends Parser
$propertyType = 'text'; $propertyType = 'text';
// A property which is not part of the standard. // A property which is not part of the standard.
if (self::XCAL_NAMESPACE !== $namespace if ($namespace !== self::XCAL_NAMESPACE
&& self::XCARD_NAMESPACE !== $namespace) { && $namespace !== self::XCARD_NAMESPACE) {
$propertyName = 'xml'; $propertyName = 'xml';
$value = '<'.$tagName.' xmlns="'.$namespace.'"'; $value = '<' . $tagName . ' xmlns="' . $namespace . '"';
foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) { foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
$value .= ' '.$attributeName.'="'.str_replace('"', '\"', $attributeValue).'"'; $value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"';
} }
$value .= '>'.$xmlProperty['value'].'</'.$tagName.'>'; $value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>';
$propertyValue = [$value]; $propertyValue = [$value];
@ -185,7 +204,8 @@ class XML extends Parser
} }
// xCard group. // xCard group.
if ('group' === $propertyName) { if ($propertyName === 'group') {
if (!isset($xmlProperty['attributes']['name'])) { if (!isset($xmlProperty['attributes']['name'])) {
continue; continue;
} }
@ -193,22 +213,24 @@ class XML extends Parser
$this->pointer = &$xmlProperty['value']; $this->pointer = &$xmlProperty['value'];
$this->parseProperties( $this->parseProperties(
$parentComponent, $parentComponent,
strtoupper($xmlProperty['attributes']['name']).'.' strtoupper($xmlProperty['attributes']['name']) . '.'
); );
continue; continue;
} }
// Collect parameters. // Collect parameters.
foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) { foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {
if (!is_array($xmlPropertyChild) if (!is_array($xmlPropertyChild)
|| 'parameters' !== static::getTagName($xmlPropertyChild['name'])) { || 'parameters' !== static::getTagName($xmlPropertyChild['name']))
continue; continue;
}
$xmlParameters = $xmlPropertyChild['value']; $xmlParameters = $xmlPropertyChild['value'];
foreach ($xmlParameters as $xmlParameter) { foreach ($xmlParameters as $xmlParameter) {
$propertyParameterValues = []; $propertyParameterValues = [];
foreach ($xmlParameter['value'] as $xmlParameterValues) { foreach ($xmlParameter['value'] as $xmlParameterValues) {
@ -217,16 +239,19 @@ class XML extends Parser
$propertyParameters[static::getTagName($xmlParameter['name'])] $propertyParameters[static::getTagName($xmlParameter['name'])]
= implode(',', $propertyParameterValues); = implode(',', $propertyParameterValues);
} }
array_splice($xmlProperty['value'], $i, 1); array_splice($xmlProperty['value'], $i, 1);
} }
$propertyNameExtended = ($this->root instanceof VCalendar $propertyNameExtended = ($this->root instanceof VCalendar
? 'xcal' ? 'xcal'
: 'xcard').':'.$propertyName; : 'xcard') . ':' . $propertyName;
switch ($propertyNameExtended) { switch ($propertyNameExtended) {
case 'xcal:geo': case 'xcal:geo':
$propertyType = 'float'; $propertyType = 'float';
$propertyValue['latitude'] = 0; $propertyValue['latitude'] = 0;
@ -252,7 +277,6 @@ class XML extends Parser
// We don't break because we only want to set // We don't break because we only want to set
// another property type. // another property type.
// no break
case 'xcal:categories': case 'xcal:categories':
case 'xcal:resources': case 'xcal:resources':
case 'xcal:exdate': case 'xcal:exdate':
@ -266,12 +290,16 @@ class XML extends Parser
$propertyType = 'date-time'; $propertyType = 'date-time';
foreach ($xmlProperty['value'] as $specialChild) { foreach ($xmlProperty['value'] as $specialChild) {
$tagName = static::getTagName($specialChild['name']); $tagName = static::getTagName($specialChild['name']);
if ('period' === $tagName) { if ('period' === $tagName) {
$propertyParameters['value'] = 'PERIOD'; $propertyParameters['value'] = 'PERIOD';
$propertyValue[] = implode('/', $specialChild['value']); $propertyValue[] = implode('/', $specialChild['value']);
} else {
}
else {
$propertyValue[] = $specialChild['value']; $propertyValue[] = $specialChild['value'];
} }
} }
@ -292,24 +320,29 @@ class XML extends Parser
$this->createProperty( $this->createProperty(
$parentComponent, $parentComponent,
$propertyNamePrefix.$propertyName, $propertyNamePrefix . $propertyName,
$propertyParameters, $propertyParameters,
$propertyType, $propertyType,
$propertyValue $propertyValue
); );
} }
} }
/** /**
* Parse a component. * Parse a component.
* *
* @param Component $parentComponent * @param Component $parentComponent
*
* @return void
*/ */
protected function parseComponent(Component $parentComponent) protected function parseComponent(Component $parentComponent) {
{
$components = $this->pointer['value'] ?: []; $components = $this->pointer['value'] ?: [];
foreach ($components as $component) { foreach ($components as $component) {
$componentName = static::getTagName($component['name']); $componentName = static::getTagName($component['name']);
$currentComponent = $this->root->createComponent( $currentComponent = $this->root->createComponent(
$componentName, $componentName,
@ -321,20 +354,24 @@ class XML extends Parser
$this->parseVCalendarComponents($currentComponent); $this->parseVCalendarComponents($currentComponent);
$parentComponent->add($currentComponent); $parentComponent->add($currentComponent);
} }
} }
/** /**
* Create a property. * Create a property.
* *
* @param Component $parentComponent * @param Component $parentComponent
* @param string $name * @param string $name
* @param array $parameters * @param array $parameters
* @param string $type * @param string $type
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) {
{
$property = $this->root->createProperty( $property = $this->root->createProperty(
$name, $name,
null, null,
@ -343,30 +380,36 @@ class XML extends Parser
); );
$parentComponent->add($property); $parentComponent->add($property);
$property->setXmlValue($value); $property->setXmlValue($value);
} }
/** /**
* Sets the input data. * Sets the input data.
* *
* @param resource|string $input * @param resource|string $input
*
* @return void
*/ */
public function setInput($input) function setInput($input) {
{
if (is_resource($input)) { if (is_resource($input)) {
$input = stream_get_contents($input); $input = stream_get_contents($input);
} }
if (is_string($input)) { if (is_string($input)) {
$reader = new SabreXml\Reader(); $reader = new SabreXml\Reader();
$reader->elementMap['{'.self::XCAL_NAMESPACE.'}period'] $reader->elementMap['{' . self::XCAL_NAMESPACE . '}period']
= 'Sabre\VObject\Parser\XML\Element\KeyValue'; = 'Sabre\VObject\Parser\XML\Element\KeyValue';
$reader->elementMap['{'.self::XCAL_NAMESPACE.'}recur'] $reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur']
= 'Sabre\VObject\Parser\XML\Element\KeyValue'; = 'Sabre\VObject\Parser\XML\Element\KeyValue';
$reader->xml($input); $reader->xml($input);
$input = $reader->parse(); $input = $reader->parse();
} }
$this->input = $input; $this->input = $input;
} }
/** /**
@ -376,10 +419,10 @@ class XML extends Parser
* *
* @return string * @return string
*/ */
protected static function getTagName($clarkedTagName) protected static function getTagName($clarkedTagName) {
{
list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
return $tagName; return $tagName;
} }
} }

View File

@ -13,8 +13,8 @@ use Sabre\Xml as SabreXml;
* @author Ivan Enderlin * @author Ivan Enderlin
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class KeyValue extends SabreXml\Element\KeyValue class KeyValue extends SabreXml\Element\KeyValue {
{
/** /**
* The deserialize method is called during xml parsing. * The deserialize method is called during xml parsing.
* *
@ -37,12 +37,11 @@ class KeyValue extends SabreXml\Element\KeyValue
* *
* @return mixed * @return mixed
*/ */
public static function xmlDeserialize(SabreXml\Reader $reader) static function xmlDeserialize(SabreXml\Reader $reader) {
{
// If there's no children, we don't do anything. // If there's no children, we don't do anything.
if ($reader->isEmptyElement) { if ($reader->isEmptyElement) {
$reader->next(); $reader->next();
return []; return [];
} }
@ -50,16 +49,22 @@ class KeyValue extends SabreXml\Element\KeyValue
$reader->read(); $reader->read();
do { do {
if (SabreXml\Reader::ELEMENT === $reader->nodeType) {
if ($reader->nodeType === SabreXml\Reader::ELEMENT) {
$name = $reader->localName; $name = $reader->localName;
$values[$name] = $reader->parseCurrentElement()['value']; $values[$name] = $reader->parseCurrentElement()['value'];
} else { } else {
$reader->read(); $reader->read();
} }
} while (SabreXml\Reader::END_ELEMENT !== $reader->nodeType);
} while ($reader->nodeType !== SabreXml\Reader::END_ELEMENT);
$reader->read(); $reader->read();
return $values; return $values;
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
abstract class Property extends Node abstract class Property extends Node {
{
/** /**
* Property name. * Property name.
* *
@ -61,14 +61,16 @@ abstract class Property extends Node
* *
* Parameters must be specified in key=>value syntax. * Parameters must be specified in key=>value syntax.
* *
* @param Component $root The root document * @param Component $root The root document
* @param string $name * @param string $name
* @param string|array|null $value * @param string|array|null $value
* @param array $parameters List of parameters * @param array $parameters List of parameters
* @param string $group The vcard property group * @param string $group The vcard property group
*
* @return void
*/ */
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) {
{
$this->name = $name; $this->name = $name;
$this->group = $group; $this->group = $group;
@ -81,6 +83,7 @@ abstract class Property extends Node
if (!is_null($value)) { if (!is_null($value)) {
$this->setValue($value); $this->setValue($value);
} }
} }
/** /**
@ -89,10 +92,13 @@ abstract class Property extends Node
* This may be either a single, or multiple strings in an array. * This may be either a single, or multiple strings in an array.
* *
* @param string|array $value * @param string|array $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
$this->value = $value; $this->value = $value;
} }
/** /**
@ -106,12 +112,12 @@ abstract class Property extends Node
* *
* @return string * @return string
*/ */
public function getValue() function getValue() {
{
if (is_array($this->value)) { if (is_array($this->value)) {
if (0 == count($this->value)) { if (count($this->value) == 0) {
return; return;
} elseif (1 === count($this->value)) { } elseif (count($this->value) === 1) {
return $this->value[0]; return $this->value[0];
} else { } else {
return $this->getRawMimeDirValue(); return $this->getRawMimeDirValue();
@ -119,16 +125,20 @@ abstract class Property extends Node
} else { } else {
return $this->value; return $this->value;
} }
} }
/** /**
* Sets a multi-valued property. * Sets a multi-valued property.
* *
* @param array $parts * @param array $parts
*
* @return void
*/ */
public function setParts(array $parts) function setParts(array $parts) {
{
$this->value = $parts; $this->value = $parts;
} }
/** /**
@ -139,8 +149,8 @@ abstract class Property extends Node
* *
* @return array * @return array
*/ */
public function getParts() function getParts() {
{
if (is_null($this->value)) { if (is_null($this->value)) {
return []; return [];
} elseif (is_array($this->value)) { } elseif (is_array($this->value)) {
@ -148,6 +158,7 @@ abstract class Property extends Node
} else { } else {
return [$this->value]; return [$this->value];
} }
} }
/** /**
@ -155,22 +166,22 @@ abstract class Property extends Node
* *
* If a parameter with same name already existed, the values will be * If a parameter with same name already existed, the values will be
* combined. * combined.
* If nameless parameter is added, we try to guess its name. * If nameless parameter is added, we try to guess it's name.
* *
* @param string $name * @param string $name
* @param string|array|null $value * @param string|null|array $value
*/ */
public function add($name, $value = null) function add($name, $value = null) {
{
$noName = false; $noName = false;
if (null === $name) { if ($name === null) {
$name = Parameter::guessParameterNameByValue($value); $name = Parameter::guessParameterNameByValue($value);
$noName = true; $noName = true;
} }
if (isset($this->parameters[strtoupper($name)])) { if (isset($this->parameters[strtoupper($name)])) {
$this->parameters[strtoupper($name)]->addValue($value); $this->parameters[strtoupper($name)]->addValue($value);
} else { }
else {
$param = new Parameter($this->root, $name, $value); $param = new Parameter($this->root, $name, $value);
$param->noName = $noName; $param->noName = $noName;
$this->parameters[$param->name] = $param; $this->parameters[$param->name] = $param;
@ -182,9 +193,10 @@ abstract class Property extends Node
* *
* @return array * @return array
*/ */
public function parameters() function parameters() {
{
return $this->parameters; return $this->parameters;
} }
/** /**
@ -195,7 +207,7 @@ abstract class Property extends Node
* *
* @return string * @return string
*/ */
abstract public function getValueType(); abstract function getValueType();
/** /**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file. * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
@ -204,33 +216,35 @@ abstract class Property extends Node
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
abstract public function setRawMimeDirValue($val); abstract function setRawMimeDirValue($val);
/** /**
* Returns a raw mime-dir representation of the value. * Returns a raw mime-dir representation of the value.
* *
* @return string * @return string
*/ */
abstract public function getRawMimeDirValue(); abstract function getRawMimeDirValue();
/** /**
* Turns the object back into a serialized blob. * Turns the object back into a serialized blob.
* *
* @return string * @return string
*/ */
public function serialize() function serialize() {
{
$str = $this->name; $str = $this->name;
if ($this->group) { if ($this->group) $str = $this->group . '.' . $this->name;
$str = $this->group.'.'.$this->name;
}
foreach ($this->parameters() as $param) { foreach ($this->parameters() as $param) {
$str .= ';'.$param->serialize();
$str .= ';' . $param->serialize();
} }
$str .= ':'.$this->getRawMimeDirValue(); $str .= ':' . $this->getRawMimeDirValue();
$str = \preg_replace( $str = \preg_replace(
'/( '/(
@ -244,6 +258,7 @@ abstract class Property extends Node
// remove single space after last CRLF // remove single space after last CRLF
return \substr($str, 0, -1); return \substr($str, 0, -1);
} }
/** /**
@ -253,9 +268,10 @@ abstract class Property extends Node
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
return $this->getParts(); return $this->getParts();
} }
/** /**
@ -264,14 +280,17 @@ abstract class Property extends Node
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
if (1 === count($value)) { if (count($value) === 1) {
$this->setValue(reset($value)); $this->setValue(reset($value));
} else { } else {
$this->setValue($value); $this->setValue($value);
} }
} }
/** /**
@ -280,12 +299,12 @@ abstract class Property extends Node
* *
* @return array * @return array
*/ */
public function jsonSerialize() function jsonSerialize() {
{
$parameters = []; $parameters = [];
foreach ($this->parameters as $parameter) { foreach ($this->parameters as $parameter) {
if ('VALUE' === $parameter->name) { if ($parameter->name === 'VALUE') {
continue; continue;
} }
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize(); $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
@ -299,7 +318,7 @@ abstract class Property extends Node
return array_merge( return array_merge(
[ [
strtolower($this->name), strtolower($this->name),
(object) $parameters, (object)$parameters,
strtolower($this->getValueType()), strtolower($this->getValueType()),
], ],
$this->getJsonValue() $this->getJsonValue()
@ -311,63 +330,78 @@ abstract class Property extends Node
* object. * object.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setXmlValue(array $value) function setXmlValue(array $value) {
{
$this->setJsonValue($value); $this->setJsonValue($value);
} }
/** /**
* This method serializes the data into XML. This is used to create xCard or * This method serializes the data into XML. This is used to create xCard or
* xCal documents. * xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
public function xmlSerialize(Xml\Writer $writer) function xmlSerialize(Xml\Writer $writer) {
{
$parameters = []; $parameters = [];
foreach ($this->parameters as $parameter) { foreach ($this->parameters as $parameter) {
if ('VALUE' === $parameter->name) {
if ($parameter->name === 'VALUE') {
continue; continue;
} }
$parameters[] = $parameter; $parameters[] = $parameter;
} }
$writer->startElement(strtolower($this->name)); $writer->startElement(strtolower($this->name));
if (!empty($parameters)) { if (!empty($parameters)) {
$writer->startElement('parameters'); $writer->startElement('parameters');
foreach ($parameters as $parameter) { foreach ($parameters as $parameter) {
$writer->startElement(strtolower($parameter->name)); $writer->startElement(strtolower($parameter->name));
$writer->write($parameter); $writer->write($parameter);
$writer->endElement(); $writer->endElement();
} }
$writer->endElement(); $writer->endElement();
} }
$this->xmlSerializeValue($writer); $this->xmlSerializeValue($writer);
$writer->endElement(); $writer->endElement();
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
$valueType = strtolower($this->getValueType()); $valueType = strtolower($this->getValueType());
foreach ($this->getJsonValue() as $values) { foreach ($this->getJsonValue() as $values) {
foreach ((array) $values as $value) { foreach ((array)$values as $value) {
$writer->writeElement($valueType, $value); $writer->writeElement($valueType, $value);
} }
} }
} }
/** /**
@ -379,9 +413,10 @@ abstract class Property extends Node
* *
* @return string * @return string
*/ */
public function __toString() function __toString() {
{
return (string) $this->getValue(); return (string)$this->getValue();
} }
/* ArrayAccess interface {{{ */ /* ArrayAccess interface {{{ */
@ -393,21 +428,17 @@ abstract class Property extends Node
* *
* @return bool * @return bool
*/ */
public function offsetExists($name) function offsetExists($name) {
{
if (is_int($name)) { if (is_int($name)) return parent::offsetExists($name);
return parent::offsetExists($name);
}
$name = strtoupper($name); $name = strtoupper($name);
foreach ($this->parameters as $parameter) { foreach ($this->parameters as $parameter) {
if ($parameter->name == $name) { if ($parameter->name == $name) return true;
return true;
}
} }
return false; return false;
} }
/** /**
@ -419,11 +450,9 @@ abstract class Property extends Node
* *
* @return Node * @return Node
*/ */
public function offsetGet($name) function offsetGet($name) {
{
if (is_int($name)) { if (is_int($name)) return parent::offsetGet($name);
return parent::offsetGet($name);
}
$name = strtoupper($name); $name = strtoupper($name);
if (!isset($this->parameters[$name])) { if (!isset($this->parameters[$name])) {
@ -431,16 +460,19 @@ abstract class Property extends Node
} }
return $this->parameters[$name]; return $this->parameters[$name];
} }
/** /**
* Creates a new parameter. * Creates a new parameter.
* *
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
public function offsetSet($name, $value) function offsetSet($name, $value) {
{
if (is_int($name)) { if (is_int($name)) {
parent::offsetSet($name, $value); parent::offsetSet($name, $value);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@ -452,15 +484,18 @@ abstract class Property extends Node
$param = new Parameter($this->root, $name, $value); $param = new Parameter($this->root, $name, $value);
$this->parameters[$param->name] = $param; $this->parameters[$param->name] = $param;
} }
/** /**
* Removes one or more parameters with the specified name. * Removes one or more parameters with the specified name.
* *
* @param string $name * @param string $name
*
* @return void
*/ */
public function offsetUnset($name) function offsetUnset($name) {
{
if (is_int($name)) { if (is_int($name)) {
parent::offsetUnset($name); parent::offsetUnset($name);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@ -471,20 +506,23 @@ abstract class Property extends Node
} }
unset($this->parameters[strtoupper($name)]); unset($this->parameters[strtoupper($name)]);
}
}
/* }}} */ /* }}} */
/** /**
* This method is automatically called when the object is cloned. * This method is automatically called when the object is cloned.
* Specifically, this will ensure all child elements are also cloned. * Specifically, this will ensure all child elements are also cloned.
*
* @return void
*/ */
public function __clone() function __clone() {
{
foreach ($this->parameters as $key => $child) { foreach ($this->parameters as $key => $child) {
$this->parameters[$key] = clone $child; $this->parameters[$key] = clone $child;
$this->parameters[$key]->parent = $this; $this->parameters[$key]->parent = $this;
} }
} }
/** /**
@ -505,12 +543,13 @@ abstract class Property extends Node
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$warnings = []; $warnings = [];
// Checking if our value is UTF-8 // Checking if our value is UTF-8
if (!StringUtil::isUTF8($this->getRawMimeDirValue())) { if (!StringUtil::isUTF8($this->getRawMimeDirValue())) {
$oldValue = $this->getRawMimeDirValue(); $oldValue = $this->getRawMimeDirValue();
$level = 3; $level = 3;
if ($options & self::REPAIR) { if ($options & self::REPAIR) {
@ -519,27 +558,29 @@ abstract class Property extends Node
$this->setRawMimeDirValue($newValue); $this->setRawMimeDirValue($newValue);
$level = 1; $level = 1;
} }
} }
if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) { if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) {
$message = 'Property contained a control character (0x'.bin2hex($matches[1]).')'; $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')';
} else { } else {
$message = 'Property is not valid UTF-8! '.$oldValue; $message = 'Property is not valid UTF-8! ' . $oldValue;
} }
$warnings[] = [ $warnings[] = [
'level' => $level, 'level' => $level,
'message' => $message, 'message' => $message,
'node' => $this, 'node' => $this,
]; ];
} }
// Checking if the propertyname does not contain any invalid bytes. // Checking if the propertyname does not contain any invalid bytes.
if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) { if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
$warnings[] = [ $warnings[] = [
'level' => $options & self::REPAIR ? 1 : 3, 'level' => $options & self::REPAIR ? 1 : 3,
'message' => 'The propertyname: '.$this->name.' contains invalid characters. Only A-Z, 0-9 and - are allowed', 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
'node' => $this, 'node' => $this,
]; ];
if ($options & self::REPAIR) { if ($options & self::REPAIR) {
// Uppercasing and converting underscores to dashes. // Uppercasing and converting underscores to dashes.
@ -548,52 +589,46 @@ abstract class Property extends Node
); );
// Removing every other invalid character // Removing every other invalid character
$this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name); $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
} }
} }
if ($encoding = $this->offsetGet('ENCODING')) { if ($encoding = $this->offsetGet('ENCODING')) {
if (Document::VCARD40 === $this->root->getDocumentType()) {
if ($this->root->getDocumentType() === Document::VCARD40) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'ENCODING parameter is not valid in vCard 4.', 'message' => 'ENCODING parameter is not valid in vCard 4.',
'node' => $this, 'node' => $this
]; ];
} else { } else {
$encoding = (string) $encoding;
$encoding = (string)$encoding;
$allowedEncoding = []; $allowedEncoding = [];
switch ($this->root->getDocumentType()) { switch ($this->root->getDocumentType()) {
case Document::ICALENDAR20: case Document::ICALENDAR20 :
$allowedEncoding = ['8BIT', 'BASE64']; $allowedEncoding = ['8BIT', 'BASE64'];
break; break;
case Document::VCARD21: case Document::VCARD21 :
$allowedEncoding = ['QUOTED-PRINTABLE', 'BASE64', '8BIT']; $allowedEncoding = ['QUOTED-PRINTABLE', 'BASE64', '8BIT'];
break; break;
case Document::VCARD30: case Document::VCARD30 :
$allowedEncoding = ['B']; $allowedEncoding = ['B'];
//Repair vCard30 that use BASE64 encoding
if ($options & self::REPAIR) {
if ('BASE64' === strtoupper($encoding)) {
$encoding = 'B';
$this['ENCODING'] = $encoding;
$warnings[] = [
'level' => 1,
'message' => 'ENCODING=BASE64 has been transformed to ENCODING=B.',
'node' => $this,
];
}
}
break; break;
} }
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) { if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
$warnings[] = [ $warnings[] = [
'level' => 3, 'level' => 3,
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.', 'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.',
'node' => $this, 'node' => $this
]; ];
} }
} }
} }
// Validating inner parameters // Validating inner parameters
@ -602,6 +637,7 @@ abstract class Property extends Node
} }
return $warnings; return $warnings;
} }
/** /**
@ -609,13 +645,17 @@ abstract class Property extends Node
* *
* It's intended to remove all circular references, so PHP can easily clean * It's intended to remove all circular references, so PHP can easily clean
* it up. * it up.
*
* @return void
*/ */
public function destroy() function destroy() {
{
parent::destroy(); parent::destroy();
foreach ($this->parameters as $param) { foreach ($this->parameters as $param) {
$param->destroy(); $param->destroy();
} }
$this->parameters = []; $this->parameters = [];
} }
} }

View File

@ -18,8 +18,8 @@ use Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Binary extends Property class Binary extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -34,18 +34,25 @@ class Binary extends Property
* This may be either a single, or multiple strings in an array. * This may be either a single, or multiple strings in an array.
* *
* @param string|array $value * @param string|array $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
if (is_array($value)) { if (is_array($value)) {
if (1 === count($value)) {
if (count($value) === 1) {
$this->value = $value[0]; $this->value = $value[0];
} else { } else {
throw new \InvalidArgumentException('The argument must either be a string or an array with only one child'); throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
} }
} else { } else {
$this->value = $value; $this->value = $value;
} }
} }
/** /**
@ -55,10 +62,13 @@ class Binary extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->value = base64_decode($val); $this->value = base64_decode($val);
} }
/** /**
@ -66,9 +76,10 @@ class Binary extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return base64_encode($this->value); return base64_encode($this->value);
} }
/** /**
@ -79,9 +90,10 @@ class Binary extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'BINARY'; return 'BINARY';
} }
/** /**
@ -91,9 +103,10 @@ class Binary extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
return [base64_encode($this->getValue())]; return [base64_encode($this->getValue())];
} }
/** /**
@ -102,10 +115,14 @@ class Binary extends Property
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
$value = array_map('base64_decode', $value); $value = array_map('base64_decode', $value);
parent::setJsonValue($value); parent::setJsonValue($value);
} }
} }

View File

@ -17,8 +17,8 @@ use
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Boolean extends Property class Boolean extends Property {
{
/** /**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file. * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
* *
@ -26,11 +26,14 @@ class Boolean extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$val = 'TRUE' === strtoupper($val) ? true : false; $val = strtoupper($val) === 'TRUE' ? true : false;
$this->setValue($val); $this->setValue($val);
} }
/** /**
@ -38,9 +41,10 @@ class Boolean extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return $this->value ? 'TRUE' : 'FALSE'; return $this->value ? 'TRUE' : 'FALSE';
} }
/** /**
@ -51,9 +55,10 @@ class Boolean extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'BOOLEAN'; return 'BOOLEAN';
} }
/** /**
@ -61,15 +66,19 @@ class Boolean extends Property
* object. * object.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setXmlValue(array $value) function setXmlValue(array $value) {
{
$value = array_map( $value = array_map(
function ($value) { function($value) {
return 'true' === $value; return 'true' === $value;
}, },
$value $value
); );
parent::setXmlValue($value); parent::setXmlValue($value);
} }
} }

View File

@ -22,8 +22,8 @@ namespace Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class FlatText extends Text class FlatText extends Text {
{
/** /**
* Field separator. * Field separator.
* *
@ -37,10 +37,14 @@ class FlatText extends Text
* Overriding this so we're not splitting on a ; delimiter. * Overriding this so we're not splitting on a ; delimiter.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setQuotedPrintableValue($val) function setQuotedPrintableValue($val) {
{
$val = quoted_printable_decode($val); $val = quoted_printable_decode($val);
$this->setValue($val); $this->setValue($val);
} }
} }

View File

@ -15,8 +15,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class FloatValue extends Property class FloatValue extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -32,14 +32,17 @@ class FloatValue extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$val = explode($this->delimiter, $val); $val = explode($this->delimiter, $val);
foreach ($val as &$item) { foreach ($val as &$item) {
$item = (float) $item; $item = (float)$item;
} }
$this->setParts($val); $this->setParts($val);
} }
/** /**
@ -47,12 +50,13 @@ class FloatValue extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return implode( return implode(
$this->delimiter, $this->delimiter,
$this->getParts() $this->getParts()
); );
} }
/** /**
@ -63,9 +67,10 @@ class FloatValue extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'FLOAT'; return 'FLOAT';
} }
/** /**
@ -75,19 +80,20 @@ class FloatValue extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$val = array_map('floatval', $this->getParts()); $val = array_map('floatval', $this->getParts());
// Special-casing the GEO property. // Special-casing the GEO property.
// //
// See: // See:
// http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2 // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
if ('GEO' === $this->name) { if ($this->name === 'GEO') {
return [$val]; return [$val];
} }
return $val; return $val;
} }
/** /**
@ -95,32 +101,42 @@ class FloatValue extends Property
* object. * object.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setXmlValue(array $value) function setXmlValue(array $value) {
{
$value = array_map('floatval', $value); $value = array_map('floatval', $value);
parent::setXmlValue($value); parent::setXmlValue($value);
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
// Special-casing the GEO property. // Special-casing the GEO property.
// //
// See: // See:
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2 // http://tools.ietf.org/html/rfc6321#section-3.4.1.2
if ('GEO' === $this->name) { if ($this->name === 'GEO') {
$value = array_map('floatval', $this->getParts()); $value = array_map('floatval', $this->getParts());
$writer->writeElement('latitude', $value[0]); $writer->writeElement('latitude', $value[0]);
$writer->writeElement('longitude', $value[1]); $writer->writeElement('longitude', $value[1]);
} else {
}
else {
parent::xmlSerializeValue($writer); parent::xmlSerializeValue($writer);
} }
} }
} }

View File

@ -14,8 +14,8 @@ use
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class CalAddress extends Text class CalAddress extends Text {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -32,9 +32,10 @@ class CalAddress extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'CAL-ADDRESS'; return 'CAL-ADDRESS';
} }
/** /**
@ -47,14 +48,14 @@ class CalAddress extends Text
* *
* @return string * @return string
*/ */
public function getNormalizedValue() function getNormalizedValue() {
{
$input = $this->getValue(); $input = $this->getValue();
if (!strpos($input, ':')) { if (!strpos($input, ':')) {
return $input; return $input;
} }
list($schema, $everythingElse) = explode(':', $input, 2); list($schema, $everythingElse) = explode(':', $input, 2);
return strtolower($schema) . ':' . $everythingElse;
return strtolower($schema).':'.$everythingElse;
} }
} }

View File

@ -13,6 +13,6 @@ namespace Sabre\VObject\Property\ICalendar;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Date extends DateTime class Date extends DateTime {
{
} }

View File

@ -24,8 +24,8 @@ use Sabre\VObject\TimeZoneUtil;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class DateTime extends Property class DateTime extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -40,14 +40,17 @@ class DateTime extends Property
* You may also specify DateTime objects here. * You may also specify DateTime objects here.
* *
* @param array $parts * @param array $parts
*
* @return void
*/ */
public function setParts(array $parts) function setParts(array $parts) {
{
if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) { if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
$this->setDateTimes($parts); $this->setDateTimes($parts);
} else { } else {
parent::setParts($parts); parent::setParts($parts);
} }
} }
/** /**
@ -58,9 +61,11 @@ class DateTime extends Property
* Instead of strings, you may also use DateTime here. * Instead of strings, you may also use DateTime here.
* *
* @param string|array|DateTimeInterface $value * @param string|array|DateTimeInterface $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) { if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) {
$this->setDateTimes($value); $this->setDateTimes($value);
} elseif ($value instanceof DateTimeInterface) { } elseif ($value instanceof DateTimeInterface) {
@ -68,6 +73,7 @@ class DateTime extends Property
} else { } else {
parent::setValue($value); parent::setValue($value);
} }
} }
/** /**
@ -77,10 +83,13 @@ class DateTime extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue(explode($this->delimiter, $val)); $this->setValue(explode($this->delimiter, $val));
} }
/** /**
@ -88,9 +97,10 @@ class DateTime extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return implode($this->delimiter, $this->getParts()); return implode($this->delimiter, $this->getParts());
} }
/** /**
@ -98,9 +108,10 @@ class DateTime extends Property
* *
* @return bool * @return bool
*/ */
public function hasTime() function hasTime() {
{
return 'DATE' !== strtoupper((string) $this['VALUE']); return strtoupper((string)$this['VALUE']) !== 'DATE';
} }
/** /**
@ -108,14 +119,15 @@ class DateTime extends Property
* *
* Note that DATE is always floating. * Note that DATE is always floating.
*/ */
public function isFloating() function isFloating() {
{
return return
!$this->hasTime() || !$this->hasTime() ||
( (
!isset($this['TZID']) && !isset($this['TZID']) &&
false === strpos($this->getValue(), 'Z') strpos($this->getValue(), 'Z') === false
); );
} }
/** /**
@ -131,16 +143,15 @@ class DateTime extends Property
* *
* @param DateTimeZone $timeZone * @param DateTimeZone $timeZone
* *
* @return \DateTimeImmutable * @return DateTimeImmutable
*/ */
public function getDateTime(DateTimeZone $timeZone = null) function getDateTime(DateTimeZone $timeZone = null) {
{
$dt = $this->getDateTimes($timeZone); $dt = $this->getDateTimes($timeZone);
if (!$dt) { if (!$dt) return;
return;
}
return $dt[0]; return $dt[0];
} }
/** /**
@ -152,35 +163,38 @@ class DateTime extends Property
* *
* @param DateTimeZone $timeZone * @param DateTimeZone $timeZone
* *
* @return \DateTimeImmutable[] * @return DateTimeImmutable[]
* @return \DateTime[] * @return \DateTime[]
*/ */
public function getDateTimes(DateTimeZone $timeZone = null) function getDateTimes(DateTimeZone $timeZone = null) {
{
// Does the property have a TZID? // Does the property have a TZID?
$tzid = $this['TZID']; $tzid = $this['TZID'];
if ($tzid) { if ($tzid) {
$timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root); $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root);
} }
$dts = []; $dts = [];
foreach ($this->getParts() as $part) { foreach ($this->getParts() as $part) {
$dts[] = DateTimeParser::parse($part, $timeZone); $dts[] = DateTimeParser::parse($part, $timeZone);
} }
return $dts; return $dts;
} }
/** /**
* Sets the property as a DateTime object. * Sets the property as a DateTime object.
* *
* @param DateTimeInterface $dt * @param DateTimeInterface $dt
* @param bool isFloating If set to true, timezones will be ignored * @param bool isFloating If set to true, timezones will be ignored.
*
* @return void
*/ */
public function setDateTime(DateTimeInterface $dt, $isFloating = false) function setDateTime(DateTimeInterface $dt, $isFloating = false) {
{
$this->setDateTimes([$dt], $isFloating); $this->setDateTimes([$dt], $isFloating);
} }
/** /**
@ -190,17 +204,21 @@ class DateTime extends Property
* the otehr values will be adjusted for that timezone * the otehr values will be adjusted for that timezone
* *
* @param DateTimeInterface[] $dt * @param DateTimeInterface[] $dt
* @param bool isFloating If set to true, timezones will be ignored * @param bool isFloating If set to true, timezones will be ignored.
*
* @return void
*/ */
public function setDateTimes(array $dt, $isFloating = false) function setDateTimes(array $dt, $isFloating = false) {
{
$values = []; $values = [];
if ($this->hasTime()) { if ($this->hasTime()) {
$tz = null; $tz = null;
$isUtc = false; $isUtc = false;
foreach ($dt as $d) { foreach ($dt as $d) {
if ($isFloating) { if ($isFloating) {
$values[] = $d->format('Ymd\\THis'); $values[] = $d->format('Ymd\\THis');
continue; continue;
@ -220,18 +238,25 @@ class DateTime extends Property
} else { } else {
$values[] = $d->format('Ymd\\THis'); $values[] = $d->format('Ymd\\THis');
} }
} }
if ($isUtc || $isFloating) { if ($isUtc || $isFloating) {
$this->offsetUnset('TZID'); $this->offsetUnset('TZID');
} }
} else { } else {
foreach ($dt as $d) { foreach ($dt as $d) {
$values[] = $d->format('Ymd'); $values[] = $d->format('Ymd');
} }
$this->offsetUnset('TZID'); $this->offsetUnset('TZID');
} }
$this->value = $values; $this->value = $values;
} }
/** /**
@ -242,9 +267,10 @@ class DateTime extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return $this->hasTime() ? 'DATE-TIME' : 'DATE'; return $this->hasTime() ? 'DATE-TIME' : 'DATE';
} }
/** /**
@ -254,8 +280,8 @@ class DateTime extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$dts = $this->getDateTimes(); $dts = $this->getDateTimes();
$hasTime = $this->hasTime(); $hasTime = $this->hasTime();
$isFloating = $this->isFloating(); $isFloating = $this->isFloating();
@ -264,15 +290,18 @@ class DateTime extends Property
$isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']); $isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
return array_map( return array_map(
function (DateTimeInterface $dt) use ($hasTime, $isUtc) { function(DateTimeInterface $dt) use ($hasTime, $isUtc) {
if ($hasTime) { if ($hasTime) {
return $dt->format('Y-m-d\\TH:i:s').($isUtc ? 'Z' : ''); return $dt->format('Y-m-d\\TH:i:s') . ($isUtc ? 'Z' : '');
} else { } else {
return $dt->format('Y-m-d'); return $dt->format('Y-m-d');
} }
}, },
$dts $dts
); );
} }
/** /**
@ -281,21 +310,26 @@ class DateTime extends Property
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
// dates and times in jCal have one difference to dates and times in // dates and times in jCal have one difference to dates and times in
// iCalendar. In jCal date-parts are separated by dashes, and // iCalendar. In jCal date-parts are separated by dashes, and
// time-parts are separated by colons. It makes sense to just remove // time-parts are separated by colons. It makes sense to just remove
// those. // those.
$this->setValue( $this->setValue(
array_map( array_map(
function ($item) { function($item) {
return strtr($item, [':' => '', '-' => '']); return strtr($item, [':' => '', '-' => '']);
}, },
$value $value
) )
); );
} }
/** /**
@ -303,17 +337,20 @@ class DateTime extends Property
* VALUE from DATE-TIME to DATE or vice-versa. * VALUE from DATE-TIME to DATE or vice-versa.
* *
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
*
* @return void
*/ */
public function offsetSet($name, $value) function offsetSet($name, $value) {
{
parent::offsetSet($name, $value); parent::offsetSet($name, $value);
if ('VALUE' !== strtoupper($name)) { if (strtoupper($name) !== 'VALUE') {
return; return;
} }
// This will ensure that dates are correctly encoded. // This will ensure that dates are correctly encoded.
$this->setDateTimes($this->getDateTimes()); $this->setDateTimes($this->getDateTimes());
} }
/** /**
@ -338,30 +375,30 @@ class DateTime extends Property
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$messages = parent::validate($options); $messages = parent::validate($options);
$valueType = $this->getValueType(); $valueType = $this->getValueType();
$values = $this->getParts(); $values = $this->getParts();
try { try {
foreach ($values as $value) { foreach ($values as $value) {
switch ($valueType) { switch ($valueType) {
case 'DATE': case 'DATE' :
DateTimeParser::parseDate($value); DateTimeParser::parseDate($value);
break; break;
case 'DATE-TIME': case 'DATE-TIME' :
DateTimeParser::parseDateTime($value); DateTimeParser::parseDateTime($value);
break; break;
} }
} }
} catch (InvalidDataException $e) { } catch (InvalidDataException $e) {
$messages[] = [ $messages[] = [
'level' => 3, 'level' => 3,
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType, 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType,
'node' => $this, 'node' => $this,
]; ];
} }
return $messages; return $messages;
} }
} }

View File

@ -16,8 +16,8 @@ use Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Duration extends Property class Duration extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -33,10 +33,13 @@ class Duration extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue(explode($this->delimiter, $val)); $this->setValue(explode($this->delimiter, $val));
} }
/** /**
@ -44,9 +47,10 @@ class Duration extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return implode($this->delimiter, $this->getParts()); return implode($this->delimiter, $this->getParts());
} }
/** /**
@ -57,9 +61,10 @@ class Duration extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'DURATION'; return 'DURATION';
} }
/** /**
@ -69,11 +74,12 @@ class Duration extends Property
* *
* @return \DateInterval * @return \DateInterval
*/ */
public function getDateInterval() function getDateInterval() {
{
$parts = $this->getParts(); $parts = $this->getParts();
$value = $parts[0]; $value = $parts[0];
return DateTimeParser::parseDuration($value); return DateTimeParser::parseDuration($value);
} }
} }

View File

@ -17,8 +17,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Period extends Property class Period extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -34,10 +34,13 @@ class Period extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue(explode($this->delimiter, $val)); $this->setValue(explode($this->delimiter, $val));
} }
/** /**
@ -45,9 +48,10 @@ class Period extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return implode($this->delimiter, $this->getParts()); return implode($this->delimiter, $this->getParts());
} }
/** /**
@ -58,9 +62,10 @@ class Period extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'PERIOD'; return 'PERIOD';
} }
/** /**
@ -69,16 +74,21 @@ class Period extends Property
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
$value = array_map( $value = array_map(
function ($item) { function($item) {
return strtr(implode('/', $item), [':' => '', '-' => '']); return strtr(implode('/', $item), [':' => '', '-' => '']);
}, },
$value $value
); );
parent::setJsonValue($value); parent::setJsonValue($value);
} }
/** /**
@ -88,19 +98,20 @@ class Period extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$return = []; $return = [];
foreach ($this->getParts() as $item) { foreach ($this->getParts() as $item) {
list($start, $end) = explode('/', $item, 2); list($start, $end) = explode('/', $item, 2);
$start = DateTimeParser::parseDateTime($start); $start = DateTimeParser::parseDateTime($start);
// This is a duration value. // This is a duration value.
if ('P' === $end[0]) { if ($end[0] === 'P') {
$return[] = [ $return[] = [
$start->format('Y-m-d\\TH:i:s'), $start->format('Y-m-d\\TH:i:s'),
$end, $end
]; ];
} else { } else {
$end = DateTimeParser::parseDateTime($end); $end = DateTimeParser::parseDateTime($end);
@ -109,29 +120,36 @@ class Period extends Property
$end->format('Y-m-d\\TH:i:s'), $end->format('Y-m-d\\TH:i:s'),
]; ];
} }
} }
return $return; return $return;
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
$writer->startElement(strtolower($this->getValueType())); $writer->startElement(strtolower($this->getValueType()));
$value = $this->getJsonValue(); $value = $this->getJsonValue();
$writer->writeElement('start', $value[0][0]); $writer->writeElement('start', $value[0][0]);
if ('P' === $value[0][1][0]) { if ($value[0][1][0] === 'P') {
$writer->writeElement('duration', $value[0][1]); $writer->writeElement('duration', $value[0][1]);
} else { }
else {
$writer->writeElement('end', $value[0][1]); $writer->writeElement('end', $value[0][1]);
} }
$writer->endElement(); $writer->endElement();
} }
} }

View File

@ -22,33 +22,36 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Recur extends Property class Recur extends Property {
{
/** /**
* Updates the current value. * Updates the current value.
* *
* This may be either a single, or multiple strings in an array. * This may be either a single, or multiple strings in an array.
* *
* @param string|array $value * @param string|array $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
// If we're getting the data from json, we'll be receiving an object // If we're getting the data from json, we'll be receiving an object
if ($value instanceof \StdClass) { if ($value instanceof \StdClass) {
$value = (array) $value; $value = (array)$value;
} }
if (is_array($value)) { if (is_array($value)) {
$newVal = []; $newVal = [];
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
if (is_string($v)) { if (is_string($v)) {
$v = strtoupper($v); $v = strtoupper($v);
// The value had multiple sub-values // The value had multiple sub-values
if (false !== strpos($v, ',')) { if (strpos($v, ',') !== false) {
$v = explode(',', $v); $v = explode(',', $v);
} }
if (0 === strcmp($k, 'until')) { if (strcmp($k, 'until') === 0) {
$v = strtr($v, [':' => '', '-' => '']); $v = strtr($v, [':' => '', '-' => '']);
} }
} elseif (is_array($v)) { } elseif (is_array($v)) {
@ -63,6 +66,7 @@ class Recur extends Property
} else { } else {
throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
} }
} }
/** /**
@ -76,24 +80,26 @@ class Recur extends Property
* *
* @return string * @return string
*/ */
public function getValue() function getValue() {
{
$out = []; $out = [];
foreach ($this->value as $key => $value) { foreach ($this->value as $key => $value) {
$out[] = $key.'='.(is_array($value) ? implode(',', $value) : $value); $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value);
} }
return strtoupper(implode(';', $out)); return strtoupper(implode(';', $out));
} }
/** /**
* Sets a multi-valued property. * Sets a multi-valued property.
* *
* @param array $parts * @param array $parts
* @return void
*/ */
public function setParts(array $parts) function setParts(array $parts) {
{
$this->setValue($parts); $this->setValue($parts);
} }
/** /**
@ -104,9 +110,10 @@ class Recur extends Property
* *
* @return array * @return array
*/ */
public function getParts() function getParts() {
{
return $this->value; return $this->value;
} }
/** /**
@ -116,10 +123,13 @@ class Recur extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue($val); $this->setValue($val);
} }
/** /**
@ -127,9 +137,10 @@ class Recur extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return $this->getValue(); return $this->getValue();
} }
/** /**
@ -140,9 +151,10 @@ class Recur extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'RECUR'; return 'RECUR';
} }
/** /**
@ -152,36 +164,39 @@ class Recur extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$values = []; $values = [];
foreach ($this->getParts() as $k => $v) { foreach ($this->getParts() as $k => $v) {
if (0 === strcmp($k, 'UNTIL')) { if (strcmp($k, 'UNTIL') === 0) {
$date = new DateTime($this->root, null, $v); $date = new DateTime($this->root, null, $v);
$values[strtolower($k)] = $date->getJsonValue()[0]; $values[strtolower($k)] = $date->getJsonValue()[0];
} elseif (0 === strcmp($k, 'COUNT')) { } elseif (strcmp($k, 'COUNT') === 0) {
$values[strtolower($k)] = intval($v); $values[strtolower($k)] = intval($v);
} else { } else {
$values[strtolower($k)] = $v; $values[strtolower($k)] = $v;
} }
} }
return [$values]; return [$values];
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
$valueType = strtolower($this->getValueType()); $valueType = strtolower($this->getValueType());
foreach ($this->getJsonValue() as $value) { foreach ($this->getJsonValue() as $value) {
$writer->writeElement($valueType, $value); $writer->writeElement($valueType, $value);
} }
} }
/** /**
@ -191,11 +206,12 @@ class Recur extends Property
* *
* @return array * @return array
*/ */
public static function stringToArray($value) static function stringToArray($value) {
{
$value = strtoupper($value); $value = strtoupper($value);
$newValue = []; $newValue = [];
foreach (explode(';', $value) as $part) { foreach (explode(';', $value) as $part) {
// Skipping empty parts. // Skipping empty parts.
if (empty($part)) { if (empty($part)) {
continue; continue;
@ -203,10 +219,11 @@ class Recur extends Property
list($partName, $partValue) = explode('=', $part); list($partName, $partValue) = explode('=', $part);
// The value itself had multiple values.. // The value itself had multiple values..
if (false !== strpos($partValue, ',')) { if (strpos($partValue, ',') !== false) {
$partValue = explode(',', $partValue); $partValue = explode(',', $partValue);
} }
$newValue[$partName] = $partValue; $newValue[$partName] = $partValue;
} }
return $newValue; return $newValue;
@ -234,31 +251,32 @@ class Recur extends Property
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$repair = ($options & self::REPAIR); $repair = ($options & self::REPAIR);
$warnings = parent::validate($options); $warnings = parent::validate($options);
$values = $this->getParts(); $values = $this->getParts();
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
if ('' === $value) {
if ($value === '') {
$warnings[] = [ $warnings[] = [
'level' => $repair ? 1 : 3, 'level' => $repair ? 1 : 3,
'message' => 'Invalid value for '.$key.' in '.$this->name, 'message' => 'Invalid value for ' . $key . ' in ' . $this->name,
'node' => $this, 'node' => $this
]; ];
if ($repair) { if ($repair) {
unset($values[$key]); unset($values[$key]);
} }
} elseif ('BYMONTH' == $key) { } elseif ($key == 'BYMONTH') {
$byMonth = (array) $value; $byMonth = (array)$value;
foreach ($byMonth as $i => $v) { foreach ($byMonth as $i => $v) {
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) { if (!is_numeric($v) || (int)$v < 1 || (int)$v > 12) {
$warnings[] = [ $warnings[] = [
'level' => $repair ? 1 : 3, 'level' => $repair ? 1 : 3,
'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!', 'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!',
'node' => $this, 'node' => $this
]; ];
if ($repair) { if ($repair) {
if (is_array($value)) { if (is_array($value)) {
@ -273,14 +291,14 @@ class Recur extends Property
if (is_array($value) && empty($values[$key])) { if (is_array($value) && empty($values[$key])) {
unset($values[$key]); unset($values[$key]);
} }
} elseif ('BYWEEKNO' == $key) { } elseif ($key == 'BYWEEKNO') {
$byWeekNo = (array) $value; $byWeekNo = (array)$value;
foreach ($byWeekNo as $i => $v) { foreach ($byWeekNo as $i => $v) {
if (!is_numeric($v) || (int) $v < -53 || 0 == (int) $v || (int) $v > 53) { if (!is_numeric($v) || (int)$v < -53 || (int)$v == 0 || (int)$v > 53) {
$warnings[] = [ $warnings[] = [
'level' => $repair ? 1 : 3, 'level' => $repair ? 1 : 3,
'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', 'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
'node' => $this, 'node' => $this
]; ];
if ($repair) { if ($repair) {
if (is_array($value)) { if (is_array($value)) {
@ -295,14 +313,14 @@ class Recur extends Property
if (is_array($value) && empty($values[$key])) { if (is_array($value) && empty($values[$key])) {
unset($values[$key]); unset($values[$key]);
} }
} elseif ('BYYEARDAY' == $key) { } elseif ($key == 'BYYEARDAY') {
$byYearDay = (array) $value; $byYearDay = (array)$value;
foreach ($byYearDay as $i => $v) { foreach ($byYearDay as $i => $v) {
if (!is_numeric($v) || (int) $v < -366 || 0 == (int) $v || (int) $v > 366) { if (!is_numeric($v) || (int)$v < -366 || (int)$v == 0 || (int)$v > 366) {
$warnings[] = [ $warnings[] = [
'level' => $repair ? 1 : 3, 'level' => $repair ? 1 : 3,
'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', 'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
'node' => $this, 'node' => $this
]; ];
if ($repair) { if ($repair) {
if (is_array($value)) { if (is_array($value)) {
@ -318,12 +336,13 @@ class Recur extends Property
unset($values[$key]); unset($values[$key]);
} }
} }
} }
if (!isset($values['FREQ'])) { if (!isset($values['FREQ'])) {
$warnings[] = [ $warnings[] = [
'level' => $repair ? 1 : 3, 'level' => $repair ? 1 : 3,
'message' => 'FREQ is required in '.$this->name, 'message' => 'FREQ is required in ' . $this->name,
'node' => $this, 'node' => $this
]; ];
if ($repair) { if ($repair) {
$this->parent->remove($this); $this->parent->remove($this);
@ -334,5 +353,7 @@ class Recur extends Property
} }
return $warnings; return $warnings;
} }
} }

View File

@ -15,8 +15,8 @@ use
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class IntegerValue extends Property class IntegerValue extends Property {
{
/** /**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file. * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
* *
@ -24,10 +24,13 @@ class IntegerValue extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue((int) $val); $this->setValue((int)$val);
} }
/** /**
@ -35,9 +38,10 @@ class IntegerValue extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return $this->value; return $this->value;
} }
/** /**
@ -48,9 +52,10 @@ class IntegerValue extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'INTEGER'; return 'INTEGER';
} }
/** /**
@ -60,9 +65,10 @@ class IntegerValue extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
return [(int) $this->getValue()]; return [(int)$this->getValue()];
} }
/** /**
@ -70,10 +76,13 @@ class IntegerValue extends Property
* object. * object.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setXmlValue(array $value) function setXmlValue(array $value) {
{
$value = array_map('intval', $value); $value = array_map('intval', $value);
parent::setXmlValue($value); parent::setXmlValue($value);
} }
} }

View File

@ -17,8 +17,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Text extends Property class Text extends Property {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -53,7 +53,7 @@ class Text extends Property
* @var array * @var array
*/ */
protected $minimumPropertyValues = [ protected $minimumPropertyValues = [
'N' => 5, 'N' => 5,
'ADR' => 7, 'ADR' => 7,
]; ];
@ -64,14 +64,16 @@ class Text extends Property
* parameters will automatically be created, or you can just pass a list of * parameters will automatically be created, or you can just pass a list of
* Parameter objects. * Parameter objects.
* *
* @param Component $root The root document * @param Component $root The root document
* @param string $name * @param string $name
* @param string|array|null $value * @param string|array|null $value
* @param array $parameters List of parameters * @param array $parameters List of parameters
* @param string $group The vcard property group * @param string $group The vcard property group
*
* @return void
*/ */
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) {
{
// There's two types of multi-valued text properties: // There's two types of multi-valued text properties:
// 1. multivalue properties. // 1. multivalue properties.
// 2. structured value properties // 2. structured value properties
@ -82,6 +84,7 @@ class Text extends Property
} }
parent::__construct($root, $name, $value, $parameters, $group); parent::__construct($root, $name, $value, $parameters, $group);
} }
/** /**
@ -91,19 +94,24 @@ class Text extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); $this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
} }
/** /**
* Sets the value as a quoted-printable encoded string. * Sets the value as a quoted-printable encoded string.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setQuotedPrintableValue($val) function setQuotedPrintableValue($val) {
{
$val = quoted_printable_decode($val); $val = quoted_printable_decode($val);
// Quoted printable only appears in vCard 2.1, and the only character // Quoted printable only appears in vCard 2.1, and the only character
@ -115,6 +123,7 @@ class Text extends Property
$regex = '# (?<!\\\\) ; #x'; $regex = '# (?<!\\\\) ; #x';
$matches = preg_split($regex, $val); $matches = preg_split($regex, $val);
$this->setValue($matches); $this->setValue($matches);
} }
/** /**
@ -122,8 +131,8 @@ class Text extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
$val = $this->getParts(); $val = $this->getParts();
if (isset($this->minimumPropertyValues[$this->name])) { if (isset($this->minimumPropertyValues[$this->name])) {
@ -131,6 +140,7 @@ class Text extends Property
} }
foreach ($val as &$item) { foreach ($val as &$item) {
if (!is_array($item)) { if (!is_array($item)) {
$item = [$item]; $item = [$item];
} }
@ -140,17 +150,19 @@ class Text extends Property
$subItem, $subItem,
[ [
'\\' => '\\\\', '\\' => '\\\\',
';' => '\;', ';' => '\;',
',' => '\,', ',' => '\,',
"\n" => '\n', "\n" => '\n',
"\r" => '', "\r" => "",
] ]
); );
} }
$item = implode(',', $item); $item = implode(',', $item);
} }
return implode($this->delimiter, $val); return implode($this->delimiter, $val);
} }
/** /**
@ -160,16 +172,16 @@ class Text extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
// Structured text values should always be returned as a single // Structured text values should always be returned as a single
// array-item. Multi-value text should be returned as multiple items in // array-item. Multi-value text should be returned as multiple items in
// the top-array. // the top-array.
if (in_array($this->name, $this->structuredValues)) { if (in_array($this->name, $this->structuredValues)) {
return [$this->getParts()]; return [$this->getParts()];
} }
return $this->getParts(); return $this->getParts();
} }
/** /**
@ -180,9 +192,10 @@ class Text extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'TEXT'; return 'TEXT';
} }
/** /**
@ -190,10 +203,10 @@ class Text extends Property
* *
* @return string * @return string
*/ */
public function serialize() function serialize() {
{
// We need to kick in a special type of encoding, if it's a 2.1 vcard. // We need to kick in a special type of encoding, if it's a 2.1 vcard.
if (Document::VCARD21 !== $this->root->getDocumentType()) { if ($this->root->getDocumentType() !== Document::VCARD21) {
return parent::serialize(); return parent::serialize();
} }
@ -215,19 +228,22 @@ class Text extends Property
} }
$str = $this->name; $str = $this->name;
if ($this->group) { if ($this->group) $str = $this->group . '.' . $this->name;
$str = $this->group.'.'.$this->name;
}
foreach ($this->parameters as $param) { foreach ($this->parameters as $param) {
if ('QUOTED-PRINTABLE' === $param->getValue()) {
if ($param->getValue() === 'QUOTED-PRINTABLE') {
continue; continue;
} }
$str .= ';'.$param->serialize(); $str .= ';' . $param->serialize();
} }
// If the resulting value contains a \n, we must encode it as // If the resulting value contains a \n, we must encode it as
// quoted-printable. // quoted-printable.
if (false !== \strpos($val, "\n")) { if (\strpos($val, "\n") !== false) {
$str .= ';ENCODING=QUOTED-PRINTABLE:'; $str .= ';ENCODING=QUOTED-PRINTABLE:';
$lastLine = $str; $lastLine = $str;
$out = null; $out = null;
@ -236,27 +252,26 @@ class Text extends Property
// encode newlines for us. Specifically, the \r\n sequence must in // encode newlines for us. Specifically, the \r\n sequence must in
// vcards be encoded as =0D=OA and we must insert soft-newlines // vcards be encoded as =0D=OA and we must insert soft-newlines
// every 75 bytes. // every 75 bytes.
for ($ii = 0; $ii < \strlen($val); ++$ii) { for ($ii = 0;$ii < \strlen($val);$ii++) {
$ord = \ord($val[$ii]); $ord = \ord($val[$ii]);
// These characters are encoded as themselves. // These characters are encoded as themselves.
if ($ord >= 32 && $ord <= 126) { if ($ord >= 32 && $ord <= 126) {
$lastLine .= $val[$ii]; $lastLine .= $val[$ii];
} else { } else {
$lastLine .= '='.\strtoupper(\bin2hex($val[$ii])); $lastLine .= '=' . \strtoupper(\bin2hex($val[$ii]));
} }
if (\strlen($lastLine) >= 75) { if (\strlen($lastLine) >= 75) {
// Soft line break // Soft line break
$out .= $lastLine."=\r\n "; $out .= $lastLine . "=\r\n ";
$lastLine = null; $lastLine = null;
} }
}
if (!\is_null($lastLine)) {
$out .= $lastLine."\r\n";
}
}
if (!\is_null($lastLine)) $out .= $lastLine . "\r\n";
return $out; return $out;
} else { } else {
$str .= ':'.$val; $str .= ':' . $val;
$str = \preg_replace( $str = \preg_replace(
'/( '/(
@ -270,20 +285,24 @@ class Text extends Property
// remove single space after last CRLF // remove single space after last CRLF
return \substr($str, 0, -1); return \substr($str, 0, -1);
} }
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
$values = $this->getParts(); $values = $this->getParts();
$map = function ($items) use ($values, $writer) { $map = function($items) use ($values, $writer) {
foreach ($items as $i => $item) { foreach ($items as $i => $item) {
$writer->writeElement( $writer->writeElement(
$item, $item,
@ -293,6 +312,7 @@ class Text extends Property
}; };
switch ($this->name) { switch ($this->name) {
// Special-casing the REQUEST-STATUS property. // Special-casing the REQUEST-STATUS property.
// //
// See: // See:
@ -312,14 +332,14 @@ class Text extends Property
'given', 'given',
'additional', 'additional',
'prefix', 'prefix',
'suffix', 'suffix'
]); ]);
break; break;
case 'GENDER': case 'GENDER':
$map([ $map([
'sex', 'sex',
'text', 'text'
]); ]);
break; break;
@ -331,20 +351,21 @@ class Text extends Property
'locality', 'locality',
'region', 'region',
'code', 'code',
'country', 'country'
]); ]);
break; break;
case 'CLIENTPIDMAP': case 'CLIENTPIDMAP':
$map([ $map([
'sourceid', 'sourceid',
'uri', 'uri'
]); ]);
break; break;
default: default:
parent::xmlSerializeValue($writer); parent::xmlSerializeValue($writer);
} }
} }
/** /**
@ -365,26 +386,28 @@ class Text extends Property
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$warnings = parent::validate($options); $warnings = parent::validate($options);
if (isset($this->minimumPropertyValues[$this->name])) { if (isset($this->minimumPropertyValues[$this->name])) {
$minimum = $this->minimumPropertyValues[$this->name]; $minimum = $this->minimumPropertyValues[$this->name];
$parts = $this->getParts(); $parts = $this->getParts();
if (count($parts) < $minimum) { if (count($parts) < $minimum) {
$warnings[] = [ $warnings[] = [
'level' => $options & self::REPAIR ? 1 : 3, 'level' => $options & self::REPAIR ? 1 : 3,
'message' => 'The '.$this->name.' property must have at least '.$minimum.' values. It only has '.count($parts), 'message' => 'The ' . $this->name . ' property must have at least ' . $minimum . ' values. It only has ' . count($parts),
'node' => $this, 'node' => $this,
]; ];
if ($options & self::REPAIR) { if ($options & self::REPAIR) {
$parts = array_pad($parts, $minimum, ''); $parts = array_pad($parts, $minimum, '');
$this->setParts($parts); $this->setParts($parts);
} }
} }
}
}
return $warnings; return $warnings;
} }
} }

View File

@ -13,8 +13,8 @@ use Sabre\VObject\DateTimeParser;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Time extends Text class Time extends Text {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -31,9 +31,10 @@ class Time extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'TIME'; return 'TIME';
} }
/** /**
@ -42,9 +43,11 @@ class Time extends Text
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
// Removing colons from value. // Removing colons from value.
$value = str_replace( $value = str_replace(
':', ':',
@ -52,11 +55,12 @@ class Time extends Text
$value $value
); );
if (1 === count($value)) { if (count($value) === 1) {
$this->setValue(reset($value)); $this->setValue(reset($value));
} else { } else {
$this->setValue($value); $this->setValue($value);
} }
} }
/** /**
@ -66,8 +70,8 @@ class Time extends Text
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$parts = DateTimeParser::parseVCardTime($this->getValue()); $parts = DateTimeParser::parseVCardTime($this->getValue());
$timeStr = ''; $timeStr = '';
@ -105,7 +109,7 @@ class Time extends Text
// Timezone // Timezone
if (!is_null($parts['timezone'])) { if (!is_null($parts['timezone'])) {
if ('Z' === $parts['timezone']) { if ($parts['timezone'] === 'Z') {
$timeStr .= 'Z'; $timeStr .= 'Z';
} else { } else {
$timeStr .= $timeStr .=
@ -114,6 +118,7 @@ class Time extends Text
} }
return [$timeStr]; return [$timeStr];
} }
/** /**
@ -121,15 +126,19 @@ class Time extends Text
* object. * object.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setXmlValue(array $value) function setXmlValue(array $value) {
{
$value = array_map( $value = array_map(
function ($value) { function($value) {
return str_replace(':', '', $value); return str_replace(':', '', $value);
}, },
$value $value
); );
parent::setXmlValue($value); parent::setXmlValue($value);
} }
} }

View File

@ -12,8 +12,8 @@ namespace Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Unknown extends Text class Unknown extends Text {
{
/** /**
* Returns the value, in the format it should be encoded for json. * Returns the value, in the format it should be encoded for json.
* *
@ -21,9 +21,10 @@ class Unknown extends Text
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
return [$this->getRawMimeDirValue()]; return [$this->getRawMimeDirValue()];
} }
/** /**
@ -34,8 +35,10 @@ class Unknown extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'UNKNOWN'; return 'UNKNOWN';
} }
} }

View File

@ -14,8 +14,8 @@ use Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Uri extends Text class Uri extends Text {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -32,9 +32,10 @@ class Uri extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'URI'; return 'URI';
} }
/** /**
@ -42,8 +43,8 @@ class Uri extends Text
* *
* @return array * @return array
*/ */
public function parameters() function parameters() {
{
$parameters = parent::parameters(); $parameters = parent::parameters();
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) { if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
// If we are encoding a URI value, and this URI value has no // If we are encoding a URI value, and this URI value has no
@ -56,8 +57,8 @@ class Uri extends Text
// See Issue #227 and #235 // See Issue #227 and #235
$parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI'); $parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI');
} }
return $parameters; return $parameters;
} }
/** /**
@ -67,9 +68,11 @@ class Uri extends Text
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
// Normally we don't need to do any type of unescaping for these // Normally we don't need to do any type of unescaping for these
// properties, however.. we've noticed that Google Contacts // properties, however.. we've noticed that Google Contacts
// specifically escapes the colon (:) with a blackslash. While I have // specifically escapes the colon (:) with a blackslash. While I have
@ -78,16 +81,16 @@ class Uri extends Text
// //
// Good thing backslashes are not allowed in urls. Makes it easy to // Good thing backslashes are not allowed in urls. Makes it easy to
// assume that a backslash is always intended as an escape character. // assume that a backslash is always intended as an escape character.
if ('URL' === $this->name) { if ($this->name === 'URL') {
$regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x'; $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x';
$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$newVal = ''; $newVal = '';
foreach ($matches as $match) { foreach ($matches as $match) {
switch ($match) { switch ($match) {
case '\:': case '\:' :
$newVal .= ':'; $newVal .= ':';
break; break;
default: default :
$newVal .= $match; $newVal .= $match;
break; break;
} }
@ -96,6 +99,7 @@ class Uri extends Text
} else { } else {
$this->value = strtr($val, ['\,' => ',']); $this->value = strtr($val, ['\,' => ',']);
} }
} }
/** /**
@ -103,8 +107,8 @@ class Uri extends Text
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
if (is_array($this->value)) { if (is_array($this->value)) {
$value = $this->value[0]; $value = $this->value[0];
} else { } else {
@ -112,5 +116,7 @@ class Uri extends Text
} }
return strtr($value, [',' => '\,']); return strtr($value, [',' => '\,']);
} }
} }

View File

@ -11,8 +11,8 @@ namespace Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class UtcOffset extends Text class UtcOffset extends Text {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -29,9 +29,10 @@ class UtcOffset extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'UTC-OFFSET'; return 'UTC-OFFSET';
} }
/** /**
@ -40,16 +41,19 @@ class UtcOffset extends Text
* The value must always be an array. * The value must always be an array.
* *
* @param array $value * @param array $value
*
* @return void
*/ */
public function setJsonValue(array $value) function setJsonValue(array $value) {
{
$value = array_map( $value = array_map(
function ($value) { function($value) {
return str_replace(':', '', $value); return str_replace(':', '', $value);
}, },
$value $value
); );
parent::setJsonValue($value); parent::setJsonValue($value);
} }
/** /**
@ -59,14 +63,15 @@ class UtcOffset extends Text
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
return array_map( return array_map(
function ($value) { function($value) {
return substr($value, 0, -2).':'. return substr($value, 0, -2) . ':' .
substr($value, -2); substr($value, -2);
}, },
parent::getJsonValue() parent::getJsonValue()
); );
} }
} }

View File

@ -11,8 +11,8 @@ namespace Sabre\VObject\Property\VCard;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Date extends DateAndOrTime class Date extends DateAndOrTime {
{
/** /**
* Returns the type of value. * Returns the type of value.
* *
@ -21,18 +21,23 @@ class Date extends DateAndOrTime
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'DATE'; return 'DATE';
} }
/** /**
* Sets the property as a DateTime object. * Sets the property as a DateTime object.
* *
* @param \DateTimeInterface $dt * @param \DateTimeInterface $dt
*
* @return void
*/ */
public function setDateTime(\DateTimeInterface $dt) function setDateTime(\DateTimeInterface $dt) {
{
$this->value = $dt->format('Ymd'); $this->value = $dt->format('Ymd');
} }
} }

View File

@ -19,12 +19,12 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class DateAndOrTime extends Property class DateAndOrTime extends Property {
{
/** /**
* Field separator. * Field separator.
* *
* @var string|null * @var null|string
*/ */
public $delimiter = null; public $delimiter = null;
@ -36,9 +36,10 @@ class DateAndOrTime extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'DATE-AND-OR-TIME'; return 'DATE-AND-OR-TIME';
} }
/** /**
@ -47,9 +48,11 @@ class DateAndOrTime extends Property
* You may also specify DateTimeInterface objects here. * You may also specify DateTimeInterface objects here.
* *
* @param array $parts * @param array $parts
*
* @return void
*/ */
public function setParts(array $parts) function setParts(array $parts) {
{
if (count($parts) > 1) { if (count($parts) > 1) {
throw new \InvalidArgumentException('Only one value allowed'); throw new \InvalidArgumentException('Only one value allowed');
} }
@ -58,6 +61,7 @@ class DateAndOrTime extends Property
} else { } else {
parent::setParts($parts); parent::setParts($parts);
} }
} }
/** /**
@ -68,23 +72,28 @@ class DateAndOrTime extends Property
* Instead of strings, you may also use DateTimeInterface here. * Instead of strings, you may also use DateTimeInterface here.
* *
* @param string|array|DateTimeInterface $value * @param string|array|DateTimeInterface $value
*
* @return void
*/ */
public function setValue($value) function setValue($value) {
{
if ($value instanceof DateTimeInterface) { if ($value instanceof DateTimeInterface) {
$this->setDateTime($value); $this->setDateTime($value);
} else { } else {
parent::setValue($value); parent::setValue($value);
} }
} }
/** /**
* Sets the property as a DateTime object. * Sets the property as a DateTime object.
* *
* @param DateTimeInterface $dt * @param DateTimeInterface $dt
*
* @return void
*/ */
public function setDateTime(DateTimeInterface $dt) function setDateTime(DateTimeInterface $dt) {
{
$tz = $dt->getTimeZone(); $tz = $dt->getTimeZone();
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']); $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
@ -96,6 +105,7 @@ class DateAndOrTime extends Property
} }
$this->value = $value; $this->value = $value;
} }
/** /**
@ -114,12 +124,12 @@ class DateAndOrTime extends Property
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public function getDateTime() function getDateTime() {
{
$now = new DateTime(); $now = new DateTime();
$tzFormat = 0 === $now->getTimezone()->getOffset($now) ? '\\Z' : 'O'; $tzFormat = $now->getTimezone()->getOffset($now) === 0 ? '\\Z' : 'O';
$nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This'.$tzFormat)); $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat));
$dateParts = DateTimeParser::parseVCardDateTime($this->getValue()); $dateParts = DateTimeParser::parseVCardDateTime($this->getValue());
@ -131,8 +141,8 @@ class DateAndOrTime extends Property
$dateParts[$k] = $nowParts[$k]; $dateParts[$k] = $nowParts[$k];
} }
} }
return new DateTimeImmutable("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]"); return new DateTimeImmutable("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");
} }
/** /**
@ -142,14 +152,15 @@ class DateAndOrTime extends Property
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$parts = DateTimeParser::parseVCardDateTime($this->getValue()); $parts = DateTimeParser::parseVCardDateTime($this->getValue());
$dateStr = ''; $dateStr = '';
// Year // Year
if (!is_null($parts['year'])) { if (!is_null($parts['year'])) {
$dateStr .= $parts['year']; $dateStr .= $parts['year'];
if (!is_null($parts['month'])) { if (!is_null($parts['month'])) {
@ -157,21 +168,26 @@ class DateAndOrTime extends Property
// dash. // dash.
$dateStr .= '-'; $dateStr .= '-';
} }
} else { } else {
if (!is_null($parts['month']) || !is_null($parts['date'])) { if (!is_null($parts['month']) || !is_null($parts['date'])) {
// Inserting two dashes // Inserting two dashes
$dateStr .= '--'; $dateStr .= '--';
} }
} }
// Month // Month
if (!is_null($parts['month'])) { if (!is_null($parts['month'])) {
$dateStr .= $parts['month']; $dateStr .= $parts['month'];
if (isset($parts['date'])) { if (isset($parts['date'])) {
// If month and date are set, we need the separator dash. // If month and date are set, we need the separator dash.
$dateStr .= '-'; $dateStr .= '-';
} }
} elseif (isset($parts['date'])) { } elseif (isset($parts['date'])) {
// If the month is empty, and a date is set, we need a 'empty // If the month is empty, and a date is set, we need a 'empty
// dash' // dash'
@ -183,6 +199,7 @@ class DateAndOrTime extends Property
$dateStr .= $parts['date']; $dateStr .= $parts['date'];
} }
// Early exit if we don't have a time string. // Early exit if we don't have a time string.
if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) { if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
return [$dateStr]; return [$dateStr];
@ -192,11 +209,13 @@ class DateAndOrTime extends Property
// Hour // Hour
if (!is_null($parts['hour'])) { if (!is_null($parts['hour'])) {
$dateStr .= $parts['hour']; $dateStr .= $parts['hour'];
if (!is_null($parts['minute'])) { if (!is_null($parts['minute'])) {
$dateStr .= ':'; $dateStr .= ':';
} }
} else { } else {
// We know either minute or second _must_ be set, so we insert a // We know either minute or second _must_ be set, so we insert a
// dash for an empty value. // dash for an empty value.
@ -205,11 +224,13 @@ class DateAndOrTime extends Property
// Minute // Minute
if (!is_null($parts['minute'])) { if (!is_null($parts['minute'])) {
$dateStr .= $parts['minute']; $dateStr .= $parts['minute'];
if (!is_null($parts['second'])) { if (!is_null($parts['second'])) {
$dateStr .= ':'; $dateStr .= ':';
} }
} elseif (isset($parts['second'])) { } elseif (isset($parts['second'])) {
// Dash for empty minute // Dash for empty minute
$dateStr .= '-'; $dateStr .= '-';
@ -226,27 +247,30 @@ class DateAndOrTime extends Property
} }
return [$dateStr]; return [$dateStr];
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
$valueType = strtolower($this->getValueType()); $valueType = strtolower($this->getValueType());
$parts = DateTimeParser::parseVCardDateAndOrTime($this->getValue()); $parts = DateTimeParser::parseVCardDateAndOrTime($this->getValue());
$value = ''; $value = '';
// $d = defined // $d = defined
$d = function ($part) use ($parts) { $d = function($part) use ($parts) {
return !is_null($parts[$part]); return !is_null($parts[$part]);
}; };
// $r = read // $r = read
$r = function ($part) use ($parts) { $r = function($part) use ($parts) {
return $parts[$part]; return $parts[$part];
}; };
@ -258,29 +282,31 @@ class DateAndOrTime extends Property
// } // }
if (($d('year') || $d('month') || $d('date')) if (($d('year') || $d('month') || $d('date'))
&& (!$d('hour') && !$d('minute') && !$d('second') && !$d('timezone'))) { && (!$d('hour') && !$d('minute') && !$d('second') && !$d('timezone'))) {
if ($d('year') && $d('month') && $d('date')) { if ($d('year') && $d('month') && $d('date')) {
$value .= $r('year').$r('month').$r('date'); $value .= $r('year') . $r('month') . $r('date');
} elseif ($d('year') && $d('month') && !$d('date')) { } elseif ($d('year') && $d('month') && !$d('date')) {
$value .= $r('year').'-'.$r('month'); $value .= $r('year') . '-' . $r('month');
} elseif (!$d('year') && $d('month')) { } elseif (!$d('year') && $d('month')) {
$value .= '--'.$r('month').$r('date'); $value .= '--' . $r('month') . $r('date');
} elseif (!$d('year') && !$d('month') && $d('date')) { } elseif (!$d('year') && !$d('month') && $d('date')) {
$value .= '---'.$r('date'); $value .= '---' . $r('date');
} }
// # 4.3.2 // # 4.3.2
// value-time = element time { // value-time = element time {
// xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d?)|--\d\d)" // xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d?)|--\d\d)"
// ~ "(Z|[+\-]\d\d(\d\d)?)?" } // ~ "(Z|[+\-]\d\d(\d\d)?)?" }
// } // }
} elseif ((!$d('year') && !$d('month') && !$d('date')) } elseif ((!$d('year') && !$d('month') && !$d('date'))
&& ($d('hour') || $d('minute') || $d('second'))) { && ($d('hour') || $d('minute') || $d('second'))) {
if ($d('hour')) { if ($d('hour')) {
$value .= $r('hour').$r('minute').$r('second'); $value .= $r('hour') . $r('minute') . $r('second');
} elseif ($d('minute')) { } elseif ($d('minute')) {
$value .= '-'.$r('minute').$r('second'); $value .= '-' . $r('minute') . $r('second');
} elseif ($d('second')) { } elseif ($d('second')) {
$value .= '--'.$r('second'); $value .= '--' . $r('second');
} }
$value .= $r('timezone'); $value .= $r('timezone');
@ -291,19 +317,22 @@ class DateAndOrTime extends Property
// ~ "(Z|[+\-]\d\d(\d\d)?)?" } // ~ "(Z|[+\-]\d\d(\d\d)?)?" }
// } // }
} elseif ($d('date') && $d('hour')) { } elseif ($d('date') && $d('hour')) {
if ($d('year') && $d('month') && $d('date')) { if ($d('year') && $d('month') && $d('date')) {
$value .= $r('year').$r('month').$r('date'); $value .= $r('year') . $r('month') . $r('date');
} elseif (!$d('year') && $d('month') && $d('date')) { } elseif (!$d('year') && $d('month') && $d('date')) {
$value .= '--'.$r('month').$r('date'); $value .= '--' . $r('month') . $r('date');
} elseif (!$d('year') && !$d('month') && $d('date')) { } elseif (!$d('year') && !$d('month') && $d('date')) {
$value .= '---'.$r('date'); $value .= '---' . $r('date');
} }
$value .= 'T'.$r('hour').$r('minute').$r('second'). $value .= 'T' . $r('hour') . $r('minute') . $r('second') .
$r('timezone'); $r('timezone');
} }
$writer->writeElement($valueType, $value); $writer->writeElement($valueType, $value);
} }
/** /**
@ -313,10 +342,13 @@ class DateAndOrTime extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue($val); $this->setValue($val);
} }
/** /**
@ -324,9 +356,10 @@ class DateAndOrTime extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return implode($this->delimiter, $this->getParts()); return implode($this->delimiter, $this->getParts());
} }
/** /**
@ -351,8 +384,8 @@ class DateAndOrTime extends Property
* *
* @return array * @return array
*/ */
public function validate($options = 0) function validate($options = 0) {
{
$messages = parent::validate($options); $messages = parent::validate($options);
$value = $this->getValue(); $value = $this->getValue();
@ -360,12 +393,13 @@ class DateAndOrTime extends Property
DateTimeParser::parseVCardDateTime($value); DateTimeParser::parseVCardDateTime($value);
} catch (InvalidDataException $e) { } catch (InvalidDataException $e) {
$messages[] = [ $messages[] = [
'level' => 3, 'level' => 3,
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property', 'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property',
'node' => $this, 'node' => $this,
]; ];
} }
return $messages; return $messages;
} }
} }

View File

@ -11,8 +11,8 @@ namespace Sabre\VObject\Property\VCard;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class DateTime extends DateAndOrTime class DateTime extends DateAndOrTime {
{
/** /**
* Returns the type of value. * Returns the type of value.
* *
@ -21,8 +21,10 @@ class DateTime extends DateAndOrTime
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'DATE-TIME'; return 'DATE-TIME';
} }
} }

View File

@ -14,8 +14,8 @@ use
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class LanguageTag extends Property class LanguageTag extends Property {
{
/** /**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file. * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
* *
@ -23,10 +23,13 @@ class LanguageTag extends Property
* not yet done, but parameters are not included. * not yet done, but parameters are not included.
* *
* @param string $val * @param string $val
*
* @return void
*/ */
public function setRawMimeDirValue($val) function setRawMimeDirValue($val) {
{
$this->setValue($val); $this->setValue($val);
} }
/** /**
@ -34,9 +37,10 @@ class LanguageTag extends Property
* *
* @return string * @return string
*/ */
public function getRawMimeDirValue() function getRawMimeDirValue() {
{
return $this->getValue(); return $this->getValue();
} }
/** /**
@ -47,8 +51,10 @@ class LanguageTag extends Property
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'LANGUAGE-TAG'; return 'LANGUAGE-TAG';
} }
} }

View File

@ -15,8 +15,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class TimeStamp extends Text class TimeStamp extends Text {
{
/** /**
* In case this is a multi-value property. This string will be used as a * In case this is a multi-value property. This string will be used as a
* delimiter. * delimiter.
@ -33,9 +33,10 @@ class TimeStamp extends Text
* *
* @return string * @return string
*/ */
public function getValueType() function getValueType() {
{
return 'TIMESTAMP'; return 'TIMESTAMP';
} }
/** /**
@ -45,16 +46,16 @@ class TimeStamp extends Text
* *
* @return array * @return array
*/ */
public function getJsonValue() function getJsonValue() {
{
$parts = DateTimeParser::parseVCardDateTime($this->getValue()); $parts = DateTimeParser::parseVCardDateTime($this->getValue());
$dateStr = $dateStr =
$parts['year'].'-'. $parts['year'] . '-' .
$parts['month'].'-'. $parts['month'] . '-' .
$parts['date'].'T'. $parts['date'] . 'T' .
$parts['hour'].':'. $parts['hour'] . ':' .
$parts['minute'].':'. $parts['minute'] . ':' .
$parts['second']; $parts['second'];
// Timezone // Timezone
@ -63,19 +64,23 @@ class TimeStamp extends Text
} }
return [$dateStr]; return [$dateStr];
} }
/** /**
* This method serializes only the value of a property. This is used to * This method serializes only the value of a property. This is used to
* create xCard or xCal documents. * create xCard or xCal documents.
* *
* @param Xml\Writer $writer XML writer * @param Xml\Writer $writer XML writer.
*
* @return void
*/ */
protected function xmlSerializeValue(Xml\Writer $writer) protected function xmlSerializeValue(Xml\Writer $writer) {
{
// xCard is the only XML and JSON format that has the same date and time // xCard is the only XML and JSON format that has the same date and time
// format than vCard. // format than vCard.
$valueType = strtolower($this->getValueType()); $valueType = strtolower($this->getValueType());
$writer->writeElement($valueType, $this->getValue()); $writer->writeElement($valueType, $this->getValue());
} }
} }

View File

@ -12,8 +12,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Reader class Reader {
{
/** /**
* If this option is passed to the reader, it will be less strict about the * If this option is passed to the reader, it will be less strict about the
* validity of the lines. * validity of the lines.
@ -35,18 +35,18 @@ class Reader
* You can either supply a string, or a readable stream for input. * You can either supply a string, or a readable stream for input.
* *
* @param string|resource $data * @param string|resource $data
* @param int $options * @param int $options
* @param string $charset * @param string $charset
*
* @return Document * @return Document
*/ */
public static function read($data, $options = 0, $charset = 'UTF-8') static function read($data, $options = 0, $charset = 'UTF-8') {
{
$parser = new Parser\MimeDir(); $parser = new Parser\MimeDir();
$parser->setCharset($charset); $parser->setCharset($charset);
$result = $parser->parse($data, $options); $result = $parser->parse($data, $options);
return $result; return $result;
} }
/** /**
@ -55,21 +55,22 @@ class Reader
* The options argument is a bitfield. Pass any of the OPTIONS constant to * The options argument is a bitfield. Pass any of the OPTIONS constant to
* alter the parsers' behaviour. * alter the parsers' behaviour.
* *
* You can either a string, a readable stream, or an array for its input. * 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 * Specifying the array is useful if json_decode was already called on the
* input. * input.
* *
* @param string|resource|array $data * @param string|resource|array $data
* @param int $options * @param int $options
* *
* @return Document * @return Document
*/ */
public static function readJson($data, $options = 0) static function readJson($data, $options = 0) {
{
$parser = new Parser\Json(); $parser = new Parser\Json();
$result = $parser->parse($data, $options); $result = $parser->parse($data, $options);
return $result; return $result;
} }
/** /**
@ -81,15 +82,17 @@ class Reader
* You can either supply a string, or a readable stream for input. * You can either supply a string, or a readable stream for input.
* *
* @param string|resource $data * @param string|resource $data
* @param int $options * @param int $options
* *
* @return Document * @return Document
*/ */
public static function readXML($data, $options = 0) static function readXML($data, $options = 0) {
{
$parser = new Parser\XML(); $parser = new Parser\XML();
$result = $parser->parse($data, $options); $result = $parser->parse($data, $options);
return $result; return $result;
} }
} }

View File

@ -58,8 +58,8 @@ use Sabre\VObject\Settings;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class EventIterator implements \Iterator class EventIterator implements \Iterator {
{
/** /**
* Reference timeZone for floating dates and times. * Reference timeZone for floating dates and times.
* *
@ -89,12 +89,12 @@ class EventIterator implements \Iterator
* The $uid parameter is only required for the first method. * The $uid parameter is only required for the first method.
* *
* @param Component|array $input * @param Component|array $input
* @param string|null $uid * @param string|null $uid
* @param DateTimeZone $timeZone reference timezone for floating dates and * @param DateTimeZone $timeZone Reference timezone for floating dates and
* times * times.
*/ */
public function __construct($input, $uid = null, DateTimeZone $timeZone = null) function __construct($input, $uid = null, DateTimeZone $timeZone = null) {
{
if (is_null($timeZone)) { if (is_null($timeZone)) {
$timeZone = new DateTimeZone('UTC'); $timeZone = new DateTimeZone('UTC');
} }
@ -107,7 +107,7 @@ class EventIterator implements \Iterator
$events = [$input]; $events = [$input];
} else { } else {
// Calendar + UID mode. // Calendar + UID mode.
$uid = (string) $uid; $uid = (string)$uid;
if (!$uid) { if (!$uid) {
throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
} }
@ -115,17 +115,24 @@ class EventIterator implements \Iterator
throw new InvalidArgumentException('No events found in this calendar'); throw new InvalidArgumentException('No events found in this calendar');
} }
$events = $input->getByUID($uid); $events = $input->getByUID($uid);
} }
foreach ($events as $vevent) { foreach ($events as $vevent) {
if (!isset($vevent->{'RECURRENCE-ID'})) { if (!isset($vevent->{'RECURRENCE-ID'})) {
$this->masterEvent = $vevent; $this->masterEvent = $vevent;
} else { } else {
$this->exceptions[ $this->exceptions[
$vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
] = true; ] = true;
$this->overriddenEvents[] = $vevent; $this->overriddenEvents[] = $vevent;
} }
} }
if (!$this->masterEvent) { if (!$this->masterEvent) {
@ -136,7 +143,7 @@ class EventIterator implements \Iterator
// event and use that instead. This may not always give the // event and use that instead. This may not always give the
// desired result. // desired result.
if (!count($this->overriddenEvents)) { if (!count($this->overriddenEvents)) {
throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid); throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
} }
$this->masterEvent = array_shift($this->overriddenEvents); $this->masterEvent = array_shift($this->overriddenEvents);
} }
@ -145,11 +152,15 @@ class EventIterator implements \Iterator
$this->allDay = !$this->masterEvent->DTSTART->hasTime(); $this->allDay = !$this->masterEvent->DTSTART->hasTime();
if (isset($this->masterEvent->EXDATE)) { if (isset($this->masterEvent->EXDATE)) {
foreach ($this->masterEvent->EXDATE as $exDate) { foreach ($this->masterEvent->EXDATE as $exDate) {
foreach ($exDate->getDateTimes($this->timeZone) as $dt) { foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
$this->exceptions[$dt->getTimeStamp()] = true; $this->exceptions[$dt->getTimeStamp()] = true;
} }
} }
} }
if (isset($this->masterEvent->DTEND)) { if (isset($this->masterEvent->DTEND)) {
@ -180,7 +191,7 @@ class EventIterator implements \Iterator
} else { } else {
$this->recurIterator = new RRuleIterator( $this->recurIterator = new RRuleIterator(
[ [
'FREQ' => 'DAILY', 'FREQ' => 'DAILY',
'COUNT' => 1, 'COUNT' => 1,
], ],
$this->startDate $this->startDate
@ -191,6 +202,7 @@ class EventIterator implements \Iterator
if (!$this->valid()) { if (!$this->valid()) {
throw new NoInstancesException('This recurrence rule does not generate any valid instances'); throw new NoInstancesException('This recurrence rule does not generate any valid instances');
} }
} }
/** /**
@ -198,11 +210,12 @@ class EventIterator implements \Iterator
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public function current() function current() {
{
if ($this->currentDate) { if ($this->currentDate) {
return clone $this->currentDate; return clone $this->currentDate;
} }
} }
/** /**
@ -211,11 +224,12 @@ class EventIterator implements \Iterator
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public function getDtStart() function getDtStart() {
{
if ($this->currentDate) { if ($this->currentDate) {
return clone $this->currentDate; return clone $this->currentDate;
} }
} }
/** /**
@ -224,26 +238,26 @@ class EventIterator implements \Iterator
* *
* @return DateTimeImmutable * @return DateTimeImmutable
*/ */
public function getDtEnd() function getDtEnd() {
{
if (!$this->valid()) { if (!$this->valid()) {
return; return;
} }
$end = clone $this->currentDate; $end = clone $this->currentDate;
return $end->modify('+' . $this->eventDuration . ' seconds');
return $end->modify('+'.$this->eventDuration.' seconds');
} }
/** /**
* Returns a VEVENT for the current iterations of the event. * Returns a VEVENT for the current iterations of the event.
* *
* This VEVENT will have a recurrence id, and its DTSTART and DTEND * This VEVENT will have a recurrence id, and it's DTSTART and DTEND
* altered. * altered.
* *
* @return VEvent * @return VEvent
*/ */
public function getEventObject() function getEventObject() {
{
if ($this->currentOverriddenEvent) { if ($this->currentOverriddenEvent) {
return $this->currentOverriddenEvent; return $this->currentOverriddenEvent;
} }
@ -270,8 +284,8 @@ class EventIterator implements \Iterator
$recurid = clone $event->DTSTART; $recurid = clone $event->DTSTART;
$recurid->name = 'RECURRENCE-ID'; $recurid->name = 'RECURRENCE-ID';
$event->add($recurid); $event->add($recurid);
return $event; return $event;
} }
/** /**
@ -281,10 +295,11 @@ class EventIterator implements \Iterator
* *
* @return int * @return int
*/ */
public function key() function key() {
{
// The counter is always 1 ahead. // The counter is always 1 ahead.
return $this->counter - 1; return $this->counter - 1;
} }
/** /**
@ -293,20 +308,20 @@ class EventIterator implements \Iterator
* *
* @return bool * @return bool
*/ */
public function valid() function valid() {
{
if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) { if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) {
throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences); throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences);
} }
return !!$this->currentDate;
return (bool) $this->currentDate;
} }
/** /**
* Sets the iterator back to the starting point. * Sets the iterator back to the starting point.
*/ */
public function rewind() function rewind() {
{
$this->recurIterator->rewind(); $this->recurIterator->rewind();
// re-creating overridden event index. // re-creating overridden event index.
$index = []; $index = [];
@ -323,15 +338,18 @@ class EventIterator implements \Iterator
$this->currentDate = clone $this->startDate; $this->currentDate = clone $this->startDate;
$this->next(); $this->next();
} }
/** /**
* Advances the iterator with one step. * Advances the iterator with one step.
*
* @return void
*/ */
public function next() function next() {
{
$this->currentOverriddenEvent = null; $this->currentOverriddenEvent = null;
++$this->counter; $this->counter++;
if ($this->nextDate) { if ($this->nextDate) {
// We had a stored value. // We had a stored value.
$nextDate = $this->nextDate; $nextDate = $this->nextDate;
@ -348,11 +366,14 @@ class EventIterator implements \Iterator
$nextDate = $this->recurIterator->current(); $nextDate = $this->recurIterator->current();
$this->recurIterator->next(); $this->recurIterator->next();
} while (isset($this->exceptions[$nextDate->getTimeStamp()])); } while (isset($this->exceptions[$nextDate->getTimeStamp()]));
} }
// $nextDate now contains what rrule thinks is the next one, but an // $nextDate now contains what rrule thinks is the next one, but an
// overridden event may cut ahead. // overridden event may cut ahead.
if ($this->overriddenEventsIndex) { if ($this->overriddenEventsIndex) {
$offsets = end($this->overriddenEventsIndex); $offsets = end($this->overriddenEventsIndex);
$timestamp = key($this->overriddenEventsIndex); $timestamp = key($this->overriddenEventsIndex);
$offset = end($offsets); $offset = end($offsets);
@ -372,10 +393,13 @@ class EventIterator implements \Iterator
// Exit point! // Exit point!
return; return;
} }
} }
$this->currentDate = $nextDate; $this->currentDate = $nextDate;
} }
/** /**
@ -383,11 +407,12 @@ class EventIterator implements \Iterator
* *
* @param DateTimeInterface $dateTime * @param DateTimeInterface $dateTime
*/ */
public function fastForward(DateTimeInterface $dateTime) function fastForward(DateTimeInterface $dateTime) {
{
while ($this->valid() && $this->getDtEnd() <= $dateTime) { while ($this->valid() && $this->getDtEnd() <= $dateTime) {
$this->next(); $this->next();
} }
} }
/** /**
@ -395,9 +420,10 @@ class EventIterator implements \Iterator
* *
* @return bool * @return bool
*/ */
public function isInfinite() function isInfinite() {
{
return $this->recurIterator->isInfinite(); return $this->recurIterator->isInfinite();
} }
/** /**
@ -478,9 +504,10 @@ class EventIterator implements \Iterator
protected $nextDate; protected $nextDate;
/** /**
* The event that overwrites the current iteration. * The event that overwrites the current iteration
* *
* @var VEVENT * @var VEVENT
*/ */
protected $currentOverriddenEvent; protected $currentOverriddenEvent;
} }

View File

@ -12,6 +12,5 @@ use Exception;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/ */
class MaxInstancesExceededException extends Exception class MaxInstancesExceededException extends Exception {
{
} }

View File

@ -13,6 +13,6 @@ use Exception;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/ */
class NoInstancesException extends Exception class NoInstancesException extends Exception {
{
} }

View File

@ -19,30 +19,29 @@ use Sabre\VObject\DateTimeParser;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class RDateIterator implements Iterator class RDateIterator implements Iterator {
{
/** /**
* Creates the Iterator. * Creates the Iterator.
* *
* @param string|array $rrule * @param string|array $rrule
* @param DateTimeInterface $start * @param DateTimeInterface $start
*/ */
public function __construct($rrule, DateTimeInterface $start) function __construct($rrule, DateTimeInterface $start) {
{
$this->startDate = $start; $this->startDate = $start;
$this->parseRDate($rrule); $this->parseRDate($rrule);
$this->currentDate = clone $this->startDate; $this->currentDate = clone $this->startDate;
} }
/* Implementation of the Iterator interface {{{ */ /* Implementation of the Iterator interface {{{ */
public function current() function current() {
{
if (!$this->valid()) {
return;
}
if (!$this->valid()) return;
return clone $this->currentDate; return clone $this->currentDate;
} }
/** /**
@ -50,9 +49,10 @@ class RDateIterator implements Iterator
* *
* @return int * @return int
*/ */
public function key() function key() {
{
return $this->counter; return $this->counter;
} }
/** /**
@ -61,35 +61,40 @@ class RDateIterator implements Iterator
* *
* @return bool * @return bool
*/ */
public function valid() function valid() {
{
return $this->counter <= count($this->dates); return ($this->counter <= count($this->dates));
} }
/** /**
* Resets the iterator. * Resets the iterator.
*
* @return void
*/ */
public function rewind() function rewind() {
{
$this->currentDate = clone $this->startDate; $this->currentDate = clone $this->startDate;
$this->counter = 0; $this->counter = 0;
} }
/** /**
* Goes on to the next iteration. * Goes on to the next iteration.
*
* @return void
*/ */
public function next() function next() {
{
++$this->counter; $this->counter++;
if (!$this->valid()) { if (!$this->valid()) return;
return;
}
$this->currentDate = $this->currentDate =
DateTimeParser::parse( DateTimeParser::parse(
$this->dates[$this->counter - 1], $this->dates[$this->counter - 1],
$this->startDate->getTimezone() $this->startDate->getTimezone()
); );
} }
/* End of Iterator implementation }}} */ /* End of Iterator implementation }}} */
@ -99,9 +104,10 @@ class RDateIterator implements Iterator
* *
* @return bool * @return bool
*/ */
public function isInfinite() function isInfinite() {
{
return false; return false;
} }
/** /**
@ -109,12 +115,15 @@ class RDateIterator implements Iterator
* specified date. * specified date.
* *
* @param DateTimeInterface $dt * @param DateTimeInterface $dt
*
* @return void
*/ */
public function fastForward(DateTimeInterface $dt) function fastForward(DateTimeInterface $dt) {
{
while ($this->valid() && $this->currentDate < $dt) { while ($this->valid() && $this->currentDate < $dt) {
$this->next(); $this->next();
} }
} }
/** /**
@ -150,20 +159,24 @@ class RDateIterator implements Iterator
* class with all the values. * class with all the values.
* *
* @param string|array $rrule * @param string|array $rrule
*
* @return void
*/ */
protected function parseRDate($rdate) protected function parseRDate($rdate) {
{
if (is_string($rdate)) { if (is_string($rdate)) {
$rdate = explode(',', $rdate); $rdate = explode(',', $rdate);
} }
$this->dates = $rdate; $this->dates = $rdate;
} }
/** /**
* Array with the RRULE dates. * Array with the RRULE dates
* *
* @var array * @var array
*/ */
protected $dates = []; protected $dates = [];
} }

View File

@ -22,30 +22,29 @@ use Sabre\VObject\Property;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class RRuleIterator implements Iterator class RRuleIterator implements Iterator {
{
/** /**
* Creates the Iterator. * Creates the Iterator.
* *
* @param string|array $rrule * @param string|array $rrule
* @param DateTimeInterface $start * @param DateTimeInterface $start
*/ */
public function __construct($rrule, DateTimeInterface $start) function __construct($rrule, DateTimeInterface $start) {
{
$this->startDate = $start; $this->startDate = $start;
$this->parseRRule($rrule); $this->parseRRule($rrule);
$this->currentDate = clone $this->startDate; $this->currentDate = clone $this->startDate;
} }
/* Implementation of the Iterator interface {{{ */ /* Implementation of the Iterator interface {{{ */
public function current() function current() {
{
if (!$this->valid()) {
return;
}
if (!$this->valid()) return;
return clone $this->currentDate; return clone $this->currentDate;
} }
/** /**
@ -53,9 +52,10 @@ class RRuleIterator implements Iterator
* *
* @return int * @return int
*/ */
public function key() function key() {
{
return $this->counter; return $this->counter;
} }
/** /**
@ -65,53 +65,61 @@ class RRuleIterator implements Iterator
* *
* @return bool * @return bool
*/ */
public function valid() function valid() {
{
if (!is_null($this->count)) { if (!is_null($this->count)) {
return $this->counter < $this->count; return $this->counter < $this->count;
} }
return is_null($this->until) || $this->currentDate <= $this->until; return is_null($this->until) || $this->currentDate <= $this->until;
} }
/** /**
* Resets the iterator. * Resets the iterator.
*
* @return void
*/ */
public function rewind() function rewind() {
{
$this->currentDate = clone $this->startDate; $this->currentDate = clone $this->startDate;
$this->counter = 0; $this->counter = 0;
} }
/** /**
* Goes on to the next iteration. * Goes on to the next iteration.
*
* @return void
*/ */
public function next() function next() {
{
// Otherwise, we find the next event in the normal RRULE // Otherwise, we find the next event in the normal RRULE
// sequence. // sequence.
switch ($this->frequency) { switch ($this->frequency) {
case 'hourly':
case 'hourly' :
$this->nextHourly(); $this->nextHourly();
break; break;
case 'daily': case 'daily' :
$this->nextDaily(); $this->nextDaily();
break; break;
case 'weekly': case 'weekly' :
$this->nextWeekly(); $this->nextWeekly();
break; break;
case 'monthly': case 'monthly' :
$this->nextMonthly(); $this->nextMonthly();
break; break;
case 'yearly': case 'yearly' :
$this->nextYearly(); $this->nextYearly();
break; break;
} }
++$this->counter; $this->counter++;
} }
/* End of Iterator implementation }}} */ /* End of Iterator implementation }}} */
@ -121,9 +129,10 @@ class RRuleIterator implements Iterator
* *
* @return bool * @return bool
*/ */
public function isInfinite() function isInfinite() {
{
return !$this->count && !$this->until; return !$this->count && !$this->until;
} }
/** /**
@ -131,12 +140,15 @@ class RRuleIterator implements Iterator
* specified date. * specified date.
* *
* @param DateTimeInterface $dt * @param DateTimeInterface $dt
*
* @return void
*/ */
public function fastForward(DateTimeInterface $dt) function fastForward(DateTimeInterface $dt) {
{
while ($this->valid() && $this->currentDate < $dt) { while ($this->valid() && $this->currentDate < $dt) {
$this->next(); $this->next();
} }
} }
/** /**
@ -305,20 +317,24 @@ class RRuleIterator implements Iterator
/** /**
* Does the processing for advancing the iterator for hourly frequency. * Does the processing for advancing the iterator for hourly frequency.
*
* @return void
*/ */
protected function nextHourly() protected function nextHourly() {
{
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours'); $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' hours');
} }
/** /**
* Does the processing for advancing the iterator for daily frequency. * Does the processing for advancing the iterator for daily frequency.
*
* @return void
*/ */
protected function nextDaily() protected function nextDaily() {
{
if (!$this->byHour && !$this->byDay) {
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
if (!$this->byHour && !$this->byDay) {
$this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days');
return; return;
} }
@ -336,14 +352,16 @@ class RRuleIterator implements Iterator
do { do {
if ($this->byHour) { if ($this->byHour) {
if ('23' == $this->currentDate->format('G')) { if ($this->currentDate->format('G') == '23') {
// to obey the interval rule // to obey the interval rule
$this->currentDate = $this->currentDate->modify('+'.$this->interval - 1 .' days'); $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' days');
} }
$this->currentDate = $this->currentDate->modify('+1 hours'); $this->currentDate = $this->currentDate->modify('+1 hours');
} else { } else {
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days'); $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days');
} }
// Current month of the year // Current month of the year
@ -354,21 +372,24 @@ class RRuleIterator implements Iterator
// Current hour of the day // Current hour of the day
$currentHour = $this->currentDate->format('G'); $currentHour = $this->currentDate->format('G');
} while ( } while (
($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
($this->byHour && !in_array($currentHour, $recurrenceHours)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
); );
} }
/** /**
* Does the processing for advancing the iterator for weekly frequency. * Does the processing for advancing the iterator for weekly frequency.
*
* @return void
*/ */
protected function nextWeekly() protected function nextWeekly() {
{
if (!$this->byHour && !$this->byDay) {
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
if (!$this->byHour && !$this->byDay) {
$this->currentDate = $this->currentDate->modify('+' . $this->interval . ' weeks');
return; return;
} }
@ -384,6 +405,7 @@ class RRuleIterator implements Iterator
$firstDay = $this->dayMap[$this->weekStart]; $firstDay = $this->dayMap[$this->weekStart];
do { do {
if ($this->byHour) { if ($this->byHour) {
$this->currentDate = $this->currentDate->modify('+1 hours'); $this->currentDate = $this->currentDate->modify('+1 hours');
} else { } else {
@ -391,19 +413,19 @@ class RRuleIterator implements Iterator
} }
// Current day of the week // Current day of the week
$currentDay = (int) $this->currentDate->format('w'); $currentDay = (int)$this->currentDate->format('w');
// Current hour of the day // Current hour of the day
$currentHour = (int) $this->currentDate->format('G'); $currentHour = (int)$this->currentDate->format('G');
// We need to roll over to the next week // We need to roll over to the next week
if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
$this->currentDate = $this->currentDate->modify('+'.$this->interval - 1 .' weeks'); $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' weeks');
// We need to go to the first day of this week, but only if we // 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. // are not already on this first day of this week.
if ($this->currentDate->format('w') != $firstDay) { if ($this->currentDate->format('w') != $firstDay) {
$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]); $this->currentDate = $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
} }
} }
@ -413,38 +435,43 @@ class RRuleIterator implements Iterator
/** /**
* Does the processing for advancing the iterator for monthly frequency. * Does the processing for advancing the iterator for monthly frequency.
*
* @return void
*/ */
protected function nextMonthly() protected function nextMonthly() {
{
$currentDayOfMonth = $this->currentDate->format('j'); $currentDayOfMonth = $this->currentDate->format('j');
if (!$this->byMonthDay && !$this->byDay) { if (!$this->byMonthDay && !$this->byDay) {
// If the current day is higher than the 28th, rollover can // If the current day is higher than the 28th, rollover can
// occur to the next month. We Must skip these invalid // occur to the next month. We Must skip these invalid
// entries. // entries.
if ($currentDayOfMonth < 29) { if ($currentDayOfMonth < 29) {
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' months'); $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' months');
} else { } else {
$increase = 0; $increase = 0;
do { do {
++$increase; $increase++;
$tempDate = clone $this->currentDate; $tempDate = clone $this->currentDate;
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months'); $tempDate = $tempDate->modify('+ ' . ($this->interval * $increase) . ' months');
} while ($tempDate->format('j') != $currentDayOfMonth); } while ($tempDate->format('j') != $currentDayOfMonth);
$this->currentDate = $tempDate; $this->currentDate = $tempDate;
} }
return; return;
} }
while (true) { while (true) {
$occurrences = $this->getMonthlyOccurrences(); $occurrences = $this->getMonthlyOccurrences();
foreach ($occurrences as $occurrence) { foreach ($occurrences as $occurrence) {
// The first occurrence thats higher than the current // The first occurrence thats higher than the current
// day of the month wins. // day of the month wins.
if ($occurrence > $currentDayOfMonth) { if ($occurrence > $currentDayOfMonth) {
break 2; break 2;
} }
} }
// If we made it all the way here, it means there were no // If we made it all the way here, it means there were no
@ -456,36 +483,42 @@ class RRuleIterator implements Iterator
// $this->currentDate->modify('first day of this month'); // $this->currentDate->modify('first day of this month');
$this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
// end of workaround // end of workaround
$this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months'); $this->currentDate = $this->currentDate->modify('+ ' . $this->interval . ' months');
// This goes to 0 because we need to start counting at the // This goes to 0 because we need to start counting at the
// beginning. // beginning.
$currentDayOfMonth = 0; $currentDayOfMonth = 0;
} }
$this->currentDate = $this->currentDate->setDate( $this->currentDate = $this->currentDate->setDate(
(int) $this->currentDate->format('Y'), (int)$this->currentDate->format('Y'),
(int) $this->currentDate->format('n'), (int)$this->currentDate->format('n'),
(int) $occurrence (int)$occurrence
); );
} }
/** /**
* Does the processing for advancing the iterator for yearly frequency. * Does the processing for advancing the iterator for yearly frequency.
*
* @return void
*/ */
protected function nextYearly() protected function nextYearly() {
{
$currentMonth = $this->currentDate->format('n'); $currentMonth = $this->currentDate->format('n');
$currentYear = $this->currentDate->format('Y'); $currentYear = $this->currentDate->format('Y');
$currentDayOfMonth = $this->currentDate->format('j'); $currentDayOfMonth = $this->currentDate->format('j');
// No sub-rules, so we just advance by year // No sub-rules, so we just advance by year
if (empty($this->byMonth)) { if (empty($this->byMonth)) {
// Unless it was a leap day! // Unless it was a leap day!
if (2 == $currentMonth && 29 == $currentDayOfMonth) { if ($currentMonth == 2 && $currentDayOfMonth == 29) {
$counter = 0; $counter = 0;
do { do {
++$counter; $counter++;
// Here we increase the year count by the interval, until // Here we increase the year count by the interval, until
// we hit a date that's also in a leap year. // we hit a date that's also in a leap year.
// //
@ -495,15 +528,16 @@ class RRuleIterator implements Iterator
// 400. (1800, 1900, 2100). So we just rely on the datetime // 400. (1800, 1900, 2100). So we just rely on the datetime
// functions instead. // functions instead.
$nextDate = clone $this->currentDate; $nextDate = clone $this->currentDate;
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years'); $nextDate = $nextDate->modify('+ ' . ($this->interval * $counter) . ' years');
} while (2 != $nextDate->format('n')); } while ($nextDate->format('n') != 2);
$this->currentDate = $nextDate; $this->currentDate = $nextDate;
return; return;
} }
if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53 if ($this->byWeekNo !== null) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
$dayOffsets = []; $dayOffsets = [];
if ($this->byDay) { if ($this->byDay) {
foreach ($this->byDay as $byDay) { foreach ($this->byDay as $byDay) {
@ -532,7 +566,6 @@ class RRuleIterator implements Iterator
if (count($checkDates) > 0) { if (count($checkDates) > 0) {
$this->currentDate = min($checkDates); $this->currentDate = min($checkDates);
return; return;
} }
@ -541,14 +574,14 @@ class RRuleIterator implements Iterator
} }
} }
if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366 if ($this->byYearDay !== null) { // byYearDay is an array with values from -366 to -1, or 1 to 366
$dayOffsets = []; $dayOffsets = [];
if ($this->byDay) { if ($this->byDay) {
foreach ($this->byDay as $byDay) { foreach ($this->byDay as $byDay) {
$dayOffsets[] = $this->dayMap[$byDay]; $dayOffsets[] = $this->dayMap[$byDay];
} }
} else { // default is Monday-Sunday } else { // default is Monday-Sunday
$dayOffsets = [1, 2, 3, 4, 5, 6, 7]; $dayOffsets = [1,2,3,4,5,6,7];
} }
$currentYear = $this->currentDate->format('Y'); $currentYear = $this->currentDate->format('Y');
@ -561,9 +594,9 @@ class RRuleIterator implements Iterator
$date = clone $this->currentDate; $date = clone $this->currentDate;
$date = $date->setDate($currentYear, 1, 1); $date = $date->setDate($currentYear, 1, 1);
if ($byYearDay > 0) { if ($byYearDay > 0) {
$date = $date->add(new \DateInterval('P'.$byYearDay.'D')); $date = $date->add(new \DateInterval('P' . $byYearDay . 'D'));
} else { } else {
$date = $date->sub(new \DateInterval('P'.abs($byYearDay).'D')); $date = $date->sub(new \DateInterval('P' . abs($byYearDay) . 'D'));
} }
if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) { if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
@ -573,7 +606,6 @@ class RRuleIterator implements Iterator
if (count($checkDates) > 0) { if (count($checkDates) > 0) {
$this->currentDate = min($checkDates); $this->currentDate = min($checkDates);
return; return;
} }
@ -583,9 +615,9 @@ class RRuleIterator implements Iterator
} }
// The easiest form // The easiest form
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' years'); $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' years');
return; return;
} }
$currentMonth = $this->currentDate->format('n'); $currentMonth = $this->currentDate->format('n');
@ -597,10 +629,13 @@ class RRuleIterator implements Iterator
// If we got a byDay or getMonthDay filter, we must first expand // If we got a byDay or getMonthDay filter, we must first expand
// further. // further.
if ($this->byDay || $this->byMonthDay) { if ($this->byDay || $this->byMonthDay) {
while (true) { while (true) {
$occurrences = $this->getMonthlyOccurrences(); $occurrences = $this->getMonthlyOccurrences();
foreach ($occurrences as $occurrence) { foreach ($occurrences as $occurrence) {
// The first occurrence that's higher than the current // The first occurrence that's higher than the current
// day of the month wins. // day of the month wins.
// If we advanced to the next month or year, the first // If we advanced to the next month or year, the first
@ -608,6 +643,7 @@ class RRuleIterator implements Iterator
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
break 2; break 2;
} }
} }
// If we made it here, it means we need to advance to // If we made it here, it means we need to advance to
@ -615,7 +651,8 @@ class RRuleIterator implements Iterator
$currentDayOfMonth = 1; $currentDayOfMonth = 1;
$advancedToNewMonth = true; $advancedToNewMonth = true;
do { do {
++$currentMonth;
$currentMonth++;
if ($currentMonth > 12) { if ($currentMonth > 12) {
$currentYear += $this->interval; $currentYear += $this->interval;
$currentMonth = 1; $currentMonth = 1;
@ -623,38 +660,43 @@ class RRuleIterator implements Iterator
} while (!in_array($currentMonth, $this->byMonth)); } while (!in_array($currentMonth, $this->byMonth));
$this->currentDate = $this->currentDate->setDate( $this->currentDate = $this->currentDate->setDate(
(int) $currentYear, (int)$currentYear,
(int) $currentMonth, (int)$currentMonth,
(int) $currentDayOfMonth (int)$currentDayOfMonth
); );
} }
// If we made it here, it means we got a valid occurrence // If we made it here, it means we got a valid occurrence
$this->currentDate = $this->currentDate->setDate( $this->currentDate = $this->currentDate->setDate(
(int) $currentYear, (int)$currentYear,
(int) $currentMonth, (int)$currentMonth,
(int) $occurrence (int)$occurrence
); );
return; return;
} else { } else {
// These are the 'byMonth' rules, if there are no byDay or // These are the 'byMonth' rules, if there are no byDay or
// byMonthDay sub-rules. // byMonthDay sub-rules.
do { do {
++$currentMonth;
$currentMonth++;
if ($currentMonth > 12) { if ($currentMonth > 12) {
$currentYear += $this->interval; $currentYear += $this->interval;
$currentMonth = 1; $currentMonth = 1;
} }
} while (!in_array($currentMonth, $this->byMonth)); } while (!in_array($currentMonth, $this->byMonth));
$this->currentDate = $this->currentDate->setDate( $this->currentDate = $this->currentDate->setDate(
(int) $currentYear, (int)$currentYear,
(int) $currentMonth, (int)$currentMonth,
(int) $currentDayOfMonth (int)$currentDayOfMonth
); );
return; return;
} }
} }
/* }}} */ /* }}} */
@ -664,28 +706,32 @@ class RRuleIterator implements Iterator
* class with all the values. * class with all the values.
* *
* @param string|array $rrule * @param string|array $rrule
*
* @return void
*/ */
protected function parseRRule($rrule) protected function parseRRule($rrule) {
{
if (is_string($rrule)) { if (is_string($rrule)) {
$rrule = Property\ICalendar\Recur::stringToArray($rrule); $rrule = Property\ICalendar\Recur::stringToArray($rrule);
} }
foreach ($rrule as $key => $value) { foreach ($rrule as $key => $value) {
$key = strtoupper($key); $key = strtoupper($key);
switch ($key) { switch ($key) {
case 'FREQ':
case 'FREQ' :
$value = strtolower($value); $value = strtolower($value);
if (!in_array( if (!in_array(
$value, $value,
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'] ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
)) { )) {
throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value)); throw new InvalidDataException('Unknown value for FREQ=' . strtoupper($value));
} }
$this->frequency = $value; $this->frequency = $value;
break; break;
case 'UNTIL': case 'UNTIL' :
$this->until = DateTimeParser::parse($value, $this->startDate->getTimezone()); $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
// In some cases events are generated with an UNTIL= // In some cases events are generated with an UNTIL=
@ -701,82 +747,86 @@ class RRuleIterator implements Iterator
} }
break; break;
case 'INTERVAL': case 'INTERVAL' :
// No break
case 'COUNT': case 'COUNT' :
$val = (int) $value; $val = (int)$value;
if ($val < 1) { if ($val < 1) {
throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!'); throw new InvalidDataException(strtoupper($key) . ' in RRULE must be a positive integer!');
} }
$key = strtolower($key); $key = strtolower($key);
$this->$key = $val; $this->$key = $val;
break; break;
case 'BYSECOND': case 'BYSECOND' :
$this->bySecond = (array) $value; $this->bySecond = (array)$value;
break; break;
case 'BYMINUTE': case 'BYMINUTE' :
$this->byMinute = (array) $value; $this->byMinute = (array)$value;
break; break;
case 'BYHOUR': case 'BYHOUR' :
$this->byHour = (array) $value; $this->byHour = (array)$value;
break; break;
case 'BYDAY': case 'BYDAY' :
$value = (array) $value; $value = (array)$value;
foreach ($value as $part) { foreach ($value as $part) {
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
throw new InvalidDataException('Invalid part in BYDAY clause: '.$part); throw new InvalidDataException('Invalid part in BYDAY clause: ' . $part);
} }
} }
$this->byDay = $value; $this->byDay = $value;
break; break;
case 'BYMONTHDAY': case 'BYMONTHDAY' :
$this->byMonthDay = (array) $value; $this->byMonthDay = (array)$value;
break; break;
case 'BYYEARDAY': case 'BYYEARDAY' :
$this->byYearDay = (array) $value; $this->byYearDay = (array)$value;
foreach ($this->byYearDay as $byYearDay) { foreach ($this->byYearDay as $byYearDay) {
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) { if (!is_numeric($byYearDay) || (int)$byYearDay < -366 || (int)$byYearDay == 0 || (int)$byYearDay > 366) {
throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!'); throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
} }
} }
break; break;
case 'BYWEEKNO': case 'BYWEEKNO' :
$this->byWeekNo = (array) $value; $this->byWeekNo = (array)$value;
foreach ($this->byWeekNo as $byWeekNo) { foreach ($this->byWeekNo as $byWeekNo) {
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) { if (!is_numeric($byWeekNo) || (int)$byWeekNo < -53 || (int)$byWeekNo == 0 || (int)$byWeekNo > 53) {
throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!'); throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
} }
} }
break; break;
case 'BYMONTH': case 'BYMONTH' :
$this->byMonth = (array) $value; $this->byMonth = (array)$value;
foreach ($this->byMonth as $byMonth) { foreach ($this->byMonth as $byMonth) {
if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) { if (!is_numeric($byMonth) || (int)$byMonth < 1 || (int)$byMonth > 12) {
throw new InvalidDataException('BYMONTH in RRULE must have value(s) betweeen 1 and 12!'); throw new InvalidDataException('BYMONTH in RRULE must have value(s) betweeen 1 and 12!');
} }
} }
break; break;
case 'BYSETPOS': case 'BYSETPOS' :
$this->bySetPos = (array) $value; $this->bySetPos = (array)$value;
break; break;
case 'WKST': case 'WKST' :
$this->weekStart = strtoupper($value); $this->weekStart = strtoupper($value);
break; break;
default: default:
throw new InvalidDataException('Not supported: '.strtoupper($key)); throw new InvalidDataException('Not supported: ' . strtoupper($key));
} }
} }
} }
/** /**
@ -802,75 +852,77 @@ class RRuleIterator implements Iterator
* *
* @return array * @return array
*/ */
protected function getMonthlyOccurrences() protected function getMonthlyOccurrences() {
{
$startDate = clone $this->currentDate; $startDate = clone $this->currentDate;
$byDayResults = []; $byDayResults = [];
// Our strategy is to simply go through the byDays, advance the date to // Our strategy is to simply go through the byDays, advance the date to
// that point and add it to the results. // that point and add it to the results.
if ($this->byDay) { if ($this->byDay) foreach ($this->byDay as $day) {
foreach ($this->byDay as $day) {
$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
// Dayname will be something like 'wednesday'. Now we need to find $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
// all wednesdays in this month.
$dayHits = [];
// 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 = $checkDate->modify($dayName);
}
do { // Dayname will be something like 'wednesday'. Now we need to find
$dayHits[] = $checkDate->format('j'); // all wednesdays in this month.
$checkDate = $checkDate->modify('next '.$dayName); $dayHits = [];
} while ($checkDate->format('n') === $startDate->format('n'));
// So now we have 'all wednesdays' for month. It is however // workaround for missing 'first day of the month' support in hhvm
// possible that the user only really wanted the 1st, 2nd or last $checkDate = new \DateTime($startDate->format('Y-m-1'));
// wednesday. // workaround modify always advancing the date even if the current day is a $dayName in hhvm
if (strlen($day) > 2) { if ($checkDate->format('l') !== $dayName) {
$offset = (int) substr($day, 0, -2); $checkDate = $checkDate->modify($dayName);
}
if ($offset > 0) { do {
// It is possible that the day does not exist, such as a $dayHits[] = $checkDate->format('j');
// 5th or 6th wednesday of the month. $checkDate = $checkDate->modify('next ' . $dayName);
if (isset($dayHits[$offset - 1])) { } while ($checkDate->format('n') === $startDate->format('n'));
$byDayResults[] = $dayHits[$offset - 1];
} // So now we have 'all wednesdays' for month. It is however
} else { // possible that the user only really wanted the 1st, 2nd or last
// if it was negative we count from the end of the array // wednesday.
// might not exist, fx. -5th tuesday if (strlen($day) > 2) {
if (isset($dayHits[count($dayHits) + $offset])) { $offset = (int)substr($day, 0, -2);
$byDayResults[] = $dayHits[count($dayHits) + $offset];
} 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 { } else {
// There was no counter (first, second, last wednesdays), so we
// just need to add the all to the list). // if it was negative we count from the end of the array
$byDayResults = array_merge($byDayResults, $dayHits); // 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 = []; $byMonthDayResults = [];
if ($this->byMonthDay) { if ($this->byMonthDay) foreach ($this->byMonthDay as $monthDay) {
foreach ($this->byMonthDay as $monthDay) {
// Removing values that are out of range for this month // Removing values that are out of range for this month
if ($monthDay > $startDate->format('t') || if ($monthDay > $startDate->format('t') ||
$monthDay < 0 - $startDate->format('t')) { $monthDay < 0 - $startDate->format('t')) {
continue; continue;
} }
if ($monthDay > 0) { if ($monthDay > 0) {
$byMonthDayResults[] = $monthDay; $byMonthDayResults[] = $monthDay;
} else { } else {
// Negative values // Negative values
$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
}
} }
} }
@ -895,6 +947,7 @@ class RRuleIterator implements Iterator
$filteredResult = []; $filteredResult = [];
foreach ($this->bySetPos as $setPos) { foreach ($this->bySetPos as $setPos) {
if ($setPos < 0) { if ($setPos < 0) {
$setPos = count($result) + ($setPos + 1); $setPos = count($result) + ($setPos + 1);
} }
@ -904,8 +957,8 @@ class RRuleIterator implements Iterator
} }
sort($filteredResult, SORT_NUMERIC); sort($filteredResult, SORT_NUMERIC);
return $filteredResult; return $filteredResult;
} }
/** /**
@ -923,8 +976,8 @@ class RRuleIterator implements Iterator
'SA' => 6, 'SA' => 6,
]; ];
protected function getHours() protected function getHours() {
{
$recurrenceHours = []; $recurrenceHours = [];
foreach ($this->byHour as $byHour) { foreach ($this->byHour as $byHour) {
$recurrenceHours[] = $byHour; $recurrenceHours[] = $byHour;
@ -933,21 +986,23 @@ class RRuleIterator implements Iterator
return $recurrenceHours; return $recurrenceHours;
} }
protected function getDays() protected function getDays() {
{
$recurrenceDays = []; $recurrenceDays = [];
foreach ($this->byDay as $byDay) { foreach ($this->byDay as $byDay) {
// The day may be preceeded with a positive (+n) or // The day may be preceeded with a positive (+n) or
// negative (-n) integer. However, this does not make // negative (-n) integer. However, this does not make
// sense in 'weekly' so we ignore it here. // sense in 'weekly' so we ignore it here.
$recurrenceDays[] = $this->dayMap[substr($byDay, -2)]; $recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
} }
return $recurrenceDays; return $recurrenceDays;
} }
protected function getMonths() protected function getMonths() {
{
$recurrenceMonths = []; $recurrenceMonths = [];
foreach ($this->byMonth as $byMonth) { foreach ($this->byMonth as $byMonth) {
$recurrenceMonths[] = $byMonth; $recurrenceMonths[] = $byMonth;

View File

@ -15,8 +15,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Settings class Settings {
{
/** /**
* The minimum date we accept for various calculations with dates, such as * The minimum date we accept for various calculations with dates, such as
* recurrences. * recurrences.
@ -25,7 +25,7 @@ class Settings
* use-cases. In particular, it covers birthdates for virtually everyone * use-cases. In particular, it covers birthdates for virtually everyone
* alive on earth, which is less than 5 people at the time of writing. * alive on earth, which is less than 5 people at the time of writing.
*/ */
public static $minDate = '1900-01-01'; static $minDate = '1900-01-01';
/** /**
* The maximum date we accept for various calculations with dates, such as * The maximum date we accept for various calculations with dates, such as
@ -34,7 +34,7 @@ class Settings
* The choice of 2100 is pretty arbitrary, but should cover most * The choice of 2100 is pretty arbitrary, but should cover most
* appointments made for many years to come. * appointments made for many years to come.
*/ */
public static $maxDate = '2100-01-01'; static $maxDate = '2100-01-01';
/** /**
* The maximum number of recurrences that will be generated. * The maximum number of recurrences that will be generated.
@ -51,5 +51,6 @@ class Settings
* *
* Set this value to -1 to disable this control altogether. * Set this value to -1 to disable this control altogether.
*/ */
public static $maxRecurrences = 3500; static $maxRecurrences = 3500;
} }

View File

@ -19,8 +19,8 @@ use Sabre\VObject\Component\VCalendar;
* @author Armin Hackmann * @author Armin Hackmann
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class ICalendar implements SplitterInterface class ICalendar implements SplitterInterface {
{
/** /**
* Timezones. * Timezones.
* *
@ -38,13 +38,13 @@ class ICalendar implements SplitterInterface
/** /**
* Constructor. * Constructor.
* *
* The splitter should receive an readable file stream as its input. * The splitter should receive an readable file stream as it's input.
* *
* @param resource $input * @param resource $input
* @param int $options parser options, see the OPTIONS constants * @param int $options Parser options, see the OPTIONS constants.
*/ */
public function __construct($input, $options = 0) function __construct($input, $options = 0) {
{
$data = VObject\Reader::read($input, $options); $data = VObject\Reader::read($input, $options);
if (!$data instanceof VObject\Component\VCalendar) { if (!$data instanceof VObject\Component\VCalendar) {
@ -57,16 +57,16 @@ class ICalendar implements SplitterInterface
} }
// Get all timezones // Get all timezones
if ('VTIMEZONE' === $component->name) { if ($component->name === 'VTIMEZONE') {
$this->vtimezones[(string) $component->TZID] = $component; $this->vtimezones[(string)$component->TZID] = $component;
continue; continue;
} }
// Get component UID for recurring Events search // Get component UID for recurring Events search
if (!$component->UID) { if (!$component->UID) {
$component->UID = sha1(microtime()).'-vobjectimport'; $component->UID = sha1(microtime()) . '-vobjectimport';
} }
$uid = (string) $component->UID; $uid = (string)$component->UID;
// Take care of recurring events // Take care of recurring events
if (!array_key_exists($uid, $this->objects)) { if (!array_key_exists($uid, $this->objects)) {
@ -75,6 +75,7 @@ class ICalendar implements SplitterInterface
$this->objects[$uid]->add(clone $component); $this->objects[$uid]->add(clone $component);
} }
} }
/** /**
@ -83,14 +84,15 @@ class ICalendar implements SplitterInterface
* *
* When the end is reached, null will be returned. * When the end is reached, null will be returned.
* *
* @return \Sabre\VObject\Component|null * @return Sabre\VObject\Component|null
*/ */
public function getNext() function getNext() {
{
if ($object = array_shift($this->objects)) { if ($object = array_shift($this->objects)) {
// create our baseobject // create our baseobject
$object->version = '2.0'; $object->version = '2.0';
$object->prodid = '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN'; $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
$object->calscale = 'GREGORIAN'; $object->calscale = 'GREGORIAN';
// add vtimezone information to obj (if we have it) // add vtimezone information to obj (if we have it)
@ -99,8 +101,13 @@ class ICalendar implements SplitterInterface
} }
return $object; return $object;
} else { } else {
return; return;
} }
} }
} }

View File

@ -15,16 +15,16 @@ namespace Sabre\VObject\Splitter;
* @author Dominik Tobschall (http://tobschall.de/) * @author Dominik Tobschall (http://tobschall.de/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
interface SplitterInterface interface SplitterInterface {
{
/** /**
* Constructor. * Constructor.
* *
* The splitter should receive an readable file stream as its input. * The splitter should receive an readable file stream as it's input.
* *
* @param resource $input * @param resource $input
*/ */
public function __construct($input); function __construct($input);
/** /**
* Every time getNext() is called, a new object will be parsed, until we * Every time getNext() is called, a new object will be parsed, until we
@ -32,7 +32,8 @@ interface SplitterInterface
* *
* When the end is reached, null will be returned. * When the end is reached, null will be returned.
* *
* @return \Sabre\VObject\Component|null * @return Sabre\VObject\Component|null
*/ */
public function getNext(); function getNext();
} }

View File

@ -19,8 +19,8 @@ use Sabre\VObject\Parser\MimeDir;
* @author Armin Hackmann * @author Armin Hackmann
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VCard implements SplitterInterface class VCard implements SplitterInterface {
{
/** /**
* File handle. * File handle.
* *
@ -38,15 +38,16 @@ class VCard implements SplitterInterface
/** /**
* Constructor. * Constructor.
* *
* The splitter should receive an readable file stream as its input. * The splitter should receive an readable file stream as it's input.
* *
* @param resource $input * @param resource $input
* @param int $options parser options, see the OPTIONS constants * @param int $options Parser options, see the OPTIONS constants.
*/ */
public function __construct($input, $options = 0) function __construct($input, $options = 0) {
{
$this->input = $input; $this->input = $input;
$this->parser = new MimeDir($input, $options); $this->parser = new MimeDir($input, $options);
} }
/** /**
@ -55,20 +56,23 @@ class VCard implements SplitterInterface
* *
* When the end is reached, null will be returned. * When the end is reached, null will be returned.
* *
* @return \Sabre\VObject\Component|null * @return Sabre\VObject\Component|null
*/ */
public function getNext() function getNext() {
{
try { try {
$object = $this->parser->parse(); $object = $this->parser->parse();
if (!$object instanceof VObject\Component\VCard) { if (!$object instanceof VObject\Component\VCard) {
throw new VObject\ParseException('The supplied input contained non-VCARD data.'); throw new VObject\ParseException('The supplied input contained non-VCARD data.');
} }
} catch (VObject\EofException $e) { } catch (VObject\EofException $e) {
return; return;
} }
return $object; return $object;
} }
} }

View File

@ -9,8 +9,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class StringUtil class StringUtil {
{
/** /**
* Returns true or false depending on if a string is valid UTF-8. * Returns true or false depending on if a string is valid UTF-8.
* *
@ -18,14 +18,15 @@ class StringUtil
* *
* @return bool * @return bool
*/ */
public static function isUTF8($str) static function isUTF8($str) {
{
// Control characters // Control characters
if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) { if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) {
return false; return false;
} }
return (bool) preg_match('%%u', $str); return (bool)preg_match('%%u', $str);
} }
/** /**
@ -38,12 +39,12 @@ class StringUtil
* *
* @return string * @return string
*/ */
public static function convertToUTF8($str) static function convertToUTF8($str) {
{
$encoding = mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'WINDOWS-1252'], true); $encoding = mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'WINDOWS-1252'], true);
switch ($encoding) { switch ($encoding) {
case 'ISO-8859-1': case 'ISO-8859-1' :
$newStr = utf8_encode($str); $newStr = utf8_encode($str);
break; break;
/* Unreachable code. Not sure yet how we can improve this /* Unreachable code. Not sure yet how we can improve this
@ -52,11 +53,14 @@ class StringUtil
$newStr = iconv('cp1252', 'UTF-8', $str); $newStr = iconv('cp1252', 'UTF-8', $str);
break; break;
*/ */
default: default :
$newStr = $str; $newStr = $str;
} }
// Removing any control characters // Removing any control characters
return preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $newStr); return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $newStr));
} }
} }

View File

@ -12,30 +12,30 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class TimeZoneUtil class TimeZoneUtil {
{
public static $map = null; static $map = null;
/** /**
* List of microsoft exchange timezone ids. * List of microsoft exchange timezone ids.
* *
* Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
*/ */
public static $microsoftExchangeMap = [ static $microsoftExchangeMap = [
0 => 'UTC', 0 => 'UTC',
31 => 'Africa/Casablanca', 31 => 'Africa/Casablanca',
// Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
// I'm not even kidding.. We handle this special case in the // I'm not even kidding.. We handle this special case in the
// getTimeZone method. // getTimeZone method.
2 => 'Europe/Lisbon', 2 => 'Europe/Lisbon',
1 => 'Europe/London', 1 => 'Europe/London',
4 => 'Europe/Berlin', 4 => 'Europe/Berlin',
6 => 'Europe/Prague', 6 => 'Europe/Prague',
3 => 'Europe/Paris', 3 => 'Europe/Paris',
69 => 'Africa/Luanda', // This was a best guess 69 => 'Africa/Luanda', // This was a best guess
7 => 'Europe/Athens', 7 => 'Europe/Athens',
5 => 'Europe/Bucharest', 5 => 'Europe/Bucharest',
49 => 'Africa/Cairo', 49 => 'Africa/Cairo',
50 => 'Africa/Harare', 50 => 'Africa/Harare',
59 => 'Europe/Helsinki', 59 => 'Europe/Helsinki',
@ -117,13 +117,13 @@ class TimeZoneUtil
* Alternatively, if $failIfUncertain is set to true, it will throw an * Alternatively, if $failIfUncertain is set to true, it will throw an
* exception if we cannot accurately determine the timezone. * exception if we cannot accurately determine the timezone.
* *
* @param string $tzid * @param string $tzid
* @param Sabre\VObject\Component $vcalendar * @param Sabre\VObject\Component $vcalendar
* *
* @return \DateTimeZone * @return DateTimeZone
*/ */
public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
{
// First we will just see if the tzid is a support timezone identifier. // 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 // The only exception is if the timezone starts with (. This is to
@ -135,7 +135,8 @@ class TimeZoneUtil
// Since PHP 5.5.10, the first bit will be used as the timezone and // 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 // this method will return just GMT+01:00. This is wrong, because it
// doesn't take DST into account. // doesn't take DST into account.
if ('(' !== $tzid[0]) { if ($tzid[0] !== '(') {
// PHP has a bug that logs PHP warnings even it shouldn't: // PHP has a bug that logs PHP warnings even it shouldn't:
// https://bugs.php.net/bug.php?id=67881 // https://bugs.php.net/bug.php?id=67881
// //
@ -154,6 +155,7 @@ class TimeZoneUtil
} }
} catch (\Exception $e) { } catch (\Exception $e) {
} }
} }
self::loadTzMaps(); self::loadTzMaps();
@ -176,40 +178,46 @@ class TimeZoneUtil
// Maybe the author was hyper-lazy and just included an offset. We // Maybe the author was hyper-lazy and just included an offset. We
// support it, but we aren't happy about it. // support it, but we aren't happy about it.
if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { 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 // 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 // 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 // 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 // for versions under PHP 5.5.10, this bit can be taken out of the
// source. // source.
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
return new \DateTimeZone('Etc/GMT'.$matches[1].ltrim(substr($matches[2], 0, 2), '0')); return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2], 0, 2), '0'));
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
if ($vcalendar) { if ($vcalendar) {
// If that didn't work, we will scan VTIMEZONE objects // If that didn't work, we will scan VTIMEZONE objects
foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) {
if ((string) $vtimezone->TZID === $tzid) {
if ((string)$vtimezone->TZID === $tzid) {
// Some clients add 'X-LIC-LOCATION' with the olson name. // Some clients add 'X-LIC-LOCATION' with the olson name.
if (isset($vtimezone->{'X-LIC-LOCATION'})) { if (isset($vtimezone->{'X-LIC-LOCATION'})) {
$lic = (string) $vtimezone->{'X-LIC-LOCATION'};
$lic = (string)$vtimezone->{'X-LIC-LOCATION'};
// Libical generators may specify strings like // Libical generators may specify strings like
// "SystemV/EST5EDT". For those we must remove the // "SystemV/EST5EDT". For those we must remove the
// SystemV part. // SystemV part.
if ('SystemV/' === substr($lic, 0, 8)) { if (substr($lic, 0, 8) === 'SystemV/') {
$lic = substr($lic, 8); $lic = substr($lic, 8);
} }
return self::getTimeZone($lic, null, $failIfUncertain); return self::getTimeZone($lic, null, $failIfUncertain);
} }
// Microsoft may add a magic number, which we also have an // Microsoft may add a magic number, which we also have an
// answer for. // answer for.
if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
// 2 can mean both Europe/Lisbon and Europe/Sarajevo. // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) { if ($cdoId === 2 && strpos((string)$vtimezone->TZID, 'Sarajevo') !== false) {
return new \DateTimeZone('Europe/Sarajevo'); return new \DateTimeZone('Europe/Sarajevo');
} }
@ -217,34 +225,37 @@ class TimeZoneUtil
return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
} }
} }
} }
} }
} }
if ($failIfUncertain) { if ($failIfUncertain) {
throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid); 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. // If we got all the way here, we default to UTC.
return new \DateTimeZone(date_default_timezone_get()); return new \DateTimeZone(date_default_timezone_get());
} }
/** /**
* This method will load in all the tz mapping information, if it's not yet * This method will load in all the tz mapping information, if it's not yet
* done. * done.
*/ */
public static function loadTzMaps() static function loadTzMaps() {
{
if (!is_null(self::$map)) { if (!is_null(self::$map)) return;
return;
}
self::$map = array_merge( self::$map = array_merge(
include __DIR__.'/timezonedata/windowszones.php', include __DIR__ . '/timezonedata/windowszones.php',
include __DIR__.'/timezonedata/lotuszones.php', include __DIR__ . '/timezonedata/lotuszones.php',
include __DIR__.'/timezonedata/exchangezones.php', include __DIR__ . '/timezonedata/exchangezones.php',
include __DIR__.'/timezonedata/php-workaround.php' include __DIR__ . '/timezonedata/php-workaround.php'
); );
} }
/** /**
@ -258,8 +269,8 @@ class TimeZoneUtil
* *
* @return array * @return array
*/ */
public static function getIdentifiersBC() static function getIdentifiersBC() {
{ return include __DIR__ . '/timezonedata/php-bc.php';
return include __DIR__.'/timezonedata/php-bc.php';
} }
} }

View File

@ -13,8 +13,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class UUIDUtil class UUIDUtil {
{
/** /**
* Returns a pseudo-random v4 UUID. * Returns a pseudo-random v4 UUID.
* *
@ -24,9 +24,10 @@ class UUIDUtil
* *
* @return string * @return string
*/ */
public static function getUUID() static function getUUID() {
{
return sprintf( return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low" // 32 bits for "time_low"
@ -56,11 +57,13 @@ class UUIDUtil
* *
* @return bool * @return bool
*/ */
public static function validateUUID($uuid) static function validateUUID($uuid) {
{
return 0 !== preg_match( return preg_match(
'/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
$uuid $uuid
); ) !== 0;
} }
} }

View File

@ -9,8 +9,8 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class VCardConverter class VCardConverter {
{
/** /**
* Converts a vCard object to a new version. * Converts a vCard object to a new version.
* *
@ -27,10 +27,10 @@ class VCardConverter
* If input and output version are identical, a clone is returned. * If input and output version are identical, a clone is returned.
* *
* @param Component\VCard $input * @param Component\VCard $input
* @param int $targetVersion * @param int $targetVersion
*/ */
public function convert(Component\VCard $input, $targetVersion) function convert(Component\VCard $input, $targetVersion) {
{
$inputVersion = $input->getDocumentType(); $inputVersion = $input->getDocumentType();
if ($inputVersion === $targetVersion) { if ($inputVersion === $targetVersion) {
return clone $input; return clone $input;
@ -43,7 +43,7 @@ class VCardConverter
throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
} }
$newVersion = Document::VCARD40 === $targetVersion ? '4.0' : '3.0'; $newVersion = $targetVersion === Document::VCARD40 ? '4.0' : '3.0';
$output = new Component\VCard([ $output = new Component\VCard([
'VERSION' => $newVersion, 'VERSION' => $newVersion,
@ -53,10 +53,13 @@ class VCardConverter
unset($output->UID); unset($output->UID);
foreach ($input->children() as $property) { foreach ($input->children() as $property) {
$this->convertProperty($input, $output, $property, $targetVersion); $this->convertProperty($input, $output, $property, $targetVersion);
} }
return $output; return $output;
} }
/** /**
@ -64,11 +67,13 @@ class VCardConverter
* *
* @param Component\VCard $input * @param Component\VCard $input
* @param Component\VCard $output * @param Component\VCard $output
* @param Property $property * @param Property $property
* @param int $targetVersion * @param int $targetVersion
*
* @return void
*/ */
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {
{
// Skipping these, those are automatically added. // Skipping these, those are automatically added.
if (in_array($property->name, ['VERSION', 'PRODID'])) { if (in_array($property->name, ['VERSION', 'PRODID'])) {
return; return;
@ -90,10 +95,15 @@ class VCardConverter
$valueType $valueType
); );
if (Document::VCARD30 === $targetVersion) {
if ($targetVersion === Document::VCARD30) {
if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {
$newProperty = $this->convertUriToBinary($output, $newProperty); $newProperty = $this->convertUriToBinary($output, $newProperty);
} elseif ($property instanceof Property\VCard\DateAndOrTime) { } elseif ($property instanceof Property\VCard\DateAndOrTime) {
// In vCard 4, the birth year may be optional. This is not the // In vCard 4, the birth year may be optional. This is not the
// case for vCard 3. Apple has a workaround for this that // case for vCard 3. Apple has a workaround for this that
// allows applications that support Apple's extension still // allows applications that support Apple's extension still
@ -103,12 +113,12 @@ class VCardConverter
// uses. // uses.
$parts = DateTimeParser::parseVCardDateTime($property->getValue()); $parts = DateTimeParser::parseVCardDateTime($property->getValue());
if (is_null($parts['year'])) { if (is_null($parts['year'])) {
$newValue = '1604-'.$parts['month'].'-'.$parts['date']; $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
$newProperty->setValue($newValue); $newProperty->setValue($newValue);
$newProperty['X-APPLE-OMIT-YEAR'] = '1604'; $newProperty['X-APPLE-OMIT-YEAR'] = '1604';
} }
if ('ANNIVERSARY' == $newProperty->name) { if ($newProperty->name == 'ANNIVERSARY') {
// Microsoft non-standard anniversary // Microsoft non-standard anniversary
$newProperty->name = 'X-ANNIVERSARY'; $newProperty->name = 'X-ANNIVERSARY';
@ -117,64 +127,74 @@ class VCardConverter
// group, so we first need to find a groupname that doesn't // group, so we first need to find a groupname that doesn't
// exist yet. // exist yet.
$x = 1; $x = 1;
while ($output->select('ITEM'.$x.'.')) { while ($output->select('ITEM' . $x . '.')) {
++$x; $x++;
} }
$output->add('ITEM'.$x.'.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']); $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']);
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_'); $output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_');
} }
} elseif ('KIND' === $property->name) {
} elseif ($property->name === 'KIND') {
switch (strtolower($property->getValue())) { switch (strtolower($property->getValue())) {
case 'org': case 'org' :
// vCard 3.0 does not have an equivalent to KIND:ORG, // vCard 3.0 does not have an equivalent to KIND:ORG,
// but apple has an extension that means the same // but apple has an extension that means the same
// thing. // thing.
$newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY'); $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY');
break; break;
case 'individual': case 'individual' :
// Individual is implicit, so we skip it. // Individual is implicit, so we skip it.
return; return;
case 'group': case 'group' :
// OS X addressbook property // OS X addressbook property
$newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP'); $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP');
break; break;
} }
} }
} elseif (Document::VCARD40 === $targetVersion) {
} elseif ($targetVersion === Document::VCARD40) {
// These properties were removed in vCard 4.0 // These properties were removed in vCard 4.0
if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) { if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) {
return; return;
} }
if ($property instanceof Property\Binary) { if ($property instanceof Property\Binary) {
$newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);
} elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
// If a property such as BDAY contained '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. // then we're stripping the year from the vcard 4 value.
$parts = DateTimeParser::parseVCardDateTime($property->getValue()); $parts = DateTimeParser::parseVCardDateTime($property->getValue());
if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) { if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) {
$newValue = '--'.$parts['month'].'-'.$parts['date']; $newValue = '--' . $parts['month'] . '-' . $parts['date'];
$newProperty->setValue($newValue); $newProperty->setValue($newValue);
} }
// Regardless if the year matched or not, we do need to strip // Regardless if the year matched or not, we do need to strip
// X-APPLE-OMIT-YEAR. // X-APPLE-OMIT-YEAR.
unset($parameters['X-APPLE-OMIT-YEAR']); unset($parameters['X-APPLE-OMIT-YEAR']);
} }
switch ($property->name) { switch ($property->name) {
case 'X-ABSHOWAS': case 'X-ABSHOWAS' :
if ('COMPANY' === strtoupper($property->getValue())) { if (strtoupper($property->getValue()) === 'COMPANY') {
$newProperty = $output->createProperty('KIND', 'ORG'); $newProperty = $output->createProperty('KIND', 'ORG');
} }
break; break;
case 'X-ADDRESSBOOKSERVER-KIND': case 'X-ADDRESSBOOKSERVER-KIND' :
if ('GROUP' === strtoupper($property->getValue())) { if (strtoupper($property->getValue()) === 'GROUP') {
$newProperty = $output->createProperty('KIND', 'GROUP'); $newProperty = $output->createProperty('KIND', 'GROUP');
} }
break; break;
case 'X-ANNIVERSARY': case 'X-ANNIVERSARY' :
$newProperty->name = 'ANNIVERSARY'; $newProperty->name = 'ANNIVERSARY';
// If we already have an anniversary property with the same // If we already have an anniversary property with the same
// value, ignore. // value, ignore.
@ -184,15 +204,15 @@ class VCardConverter
} }
} }
break; break;
case 'X-ABDATE': case 'X-ABDATE' :
// Find out what the label was, if it exists. // Find out what the label was, if it exists.
if (!$property->group) { if (!$property->group) {
break; break;
} }
$label = $input->{$property->group.'.X-ABLABEL'}; $label = $input->{$property->group . '.X-ABLABEL'};
// We only support converting anniversaries. // We only support converting anniversaries.
if (!$label || '_$!<Anniversary>!$_' !== $label->getValue()) { if (!$label || $label->getValue() !== '_$!<Anniversary>!$_') {
break; break;
} }
@ -206,20 +226,22 @@ class VCardConverter
$newProperty->name = 'ANNIVERSARY'; $newProperty->name = 'ANNIVERSARY';
break; break;
// Apple's per-property label system. // Apple's per-property label system.
case 'X-ABLABEL': case 'X-ABLABEL' :
if ('_$!<Anniversary>!$_' === $newProperty->getValue()) { if ($newProperty->getValue() === '_$!<Anniversary>!$_') {
// We can safely remove these, as they are converted to // We can safely remove these, as they are converted to
// ANNIVERSARY properties. // ANNIVERSARY properties.
return; return;
} }
break; break;
} }
} }
// set property group // set property group
$newProperty->group = $property->group; $newProperty->group = $property->group;
if (Document::VCARD40 === $targetVersion) { if ($targetVersion === Document::VCARD40) {
$this->convertParameters40($newProperty, $parameters); $this->convertParameters40($newProperty, $parameters);
} else { } else {
$this->convertParameters30($newProperty, $parameters); $this->convertParameters30($newProperty, $parameters);
@ -235,6 +257,8 @@ class VCardConverter
} }
$output->add($newProperty); $output->add($newProperty);
} }
/** /**
@ -243,14 +267,14 @@ class VCardConverter
* vCard 4.0 no longer supports BINARY properties. * vCard 4.0 no longer supports BINARY properties.
* *
* @param Component\VCard $output * @param Component\VCard $output
* @param Property\Uri $property the input property * @param Property\Uri $property The input property.
* @param $parameters list of parameters that will eventually be added to * @param $parameters List of parameters that will eventually be added to
* the new property * the new property.
* *
* @return Property\Uri * @return Property\Uri
*/ */
protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) {
{
$value = $newProperty->getValue(); $value = $newProperty->getValue();
$newProperty = $output->createProperty( $newProperty = $output->createProperty(
$newProperty->name, $newProperty->name,
@ -263,13 +287,14 @@ class VCardConverter
// See if we can find a better mimetype. // See if we can find a better mimetype.
if (isset($parameters['TYPE'])) { if (isset($parameters['TYPE'])) {
$newTypes = []; $newTypes = [];
foreach ($parameters['TYPE']->getParts() as $typePart) { foreach ($parameters['TYPE']->getParts() as $typePart) {
if (in_array( if (in_array(
strtoupper($typePart), strtoupper($typePart),
['JPEG', 'PNG', 'GIF'] ['JPEG', 'PNG', 'GIF']
)) { )) {
$mimeType = 'image/'.strtolower($typePart); $mimeType = 'image/' . strtolower($typePart);
} else { } else {
$newTypes[] = $typePart; $newTypes[] = $typePart;
} }
@ -282,11 +307,12 @@ class VCardConverter
} else { } else {
unset($parameters['TYPE']); unset($parameters['TYPE']);
} }
} }
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value)); $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
return $newProperty; return $newProperty;
} }
/** /**
@ -297,16 +323,16 @@ class VCardConverter
* possible, to improve compatibility. * possible, to improve compatibility.
* *
* @param Component\VCard $output * @param Component\VCard $output
* @param Property\Uri $property the input property * @param Property\Uri $property The input property.
* *
* @return Property\Binary|null * @return Property\Binary|null
*/ */
protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) {
{
$value = $newProperty->getValue(); $value = $newProperty->getValue();
// Only converting data: uris // Only converting data: uris
if ('data:' !== substr($value, 0, 5)) { if (substr($value, 0, 5) !== 'data:') {
return $newProperty; return $newProperty;
} }
@ -328,79 +354,92 @@ class VCardConverter
$newProperty['ENCODING'] = 'b'; $newProperty['ENCODING'] = 'b';
switch ($mimeType) { switch ($mimeType) {
case 'image/jpeg':
case 'image/jpeg' :
$newProperty['TYPE'] = 'JPEG'; $newProperty['TYPE'] = 'JPEG';
break; break;
case 'image/png': case 'image/png' :
$newProperty['TYPE'] = 'PNG'; $newProperty['TYPE'] = 'PNG';
break; break;
case 'image/gif': case 'image/gif' :
$newProperty['TYPE'] = 'GIF'; $newProperty['TYPE'] = 'GIF';
break; break;
} }
return $newProperty; return $newProperty;
} }
/** /**
* Adds parameters to a new property for vCard 4.0. * Adds parameters to a new property for vCard 4.0.
* *
* @param Property $newProperty * @param Property $newProperty
* @param array $parameters * @param array $parameters
*
* @return void
*/ */
protected function convertParameters40(Property $newProperty, array $parameters) protected function convertParameters40(Property $newProperty, array $parameters) {
{
// Adding all parameters. // Adding all parameters.
foreach ($parameters as $param) { foreach ($parameters as $param) {
// vCard 2.1 allowed parameters with no name // vCard 2.1 allowed parameters with no name
if ($param->noName) { if ($param->noName) $param->noName = false;
$param->noName = false;
}
switch ($param->name) { switch ($param->name) {
// We need to see if there's any TYPE=PREF, because in vCard 4 // We need to see if there's any TYPE=PREF, because in vCard 4
// that's now PREF=1. // that's now PREF=1.
case 'TYPE': case 'TYPE' :
foreach ($param->getParts() as $paramPart) { foreach ($param->getParts() as $paramPart) {
if ('PREF' === strtoupper($paramPart)) {
if (strtoupper($paramPart) === 'PREF') {
$newProperty->add('PREF', '1'); $newProperty->add('PREF', '1');
} else { } else {
$newProperty->add($param->name, $paramPart); $newProperty->add($param->name, $paramPart);
} }
} }
break; break;
// These no longer exist in vCard 4 // These no longer exist in vCard 4
case 'ENCODING': case 'ENCODING' :
case 'CHARSET': case 'CHARSET' :
break; break;
default: default :
$newProperty->add($param->name, $param->getParts()); $newProperty->add($param->name, $param->getParts());
break; break;
} }
} }
} }
/** /**
* Adds parameters to a new property for vCard 3.0. * Adds parameters to a new property for vCard 3.0.
* *
* @param Property $newProperty * @param Property $newProperty
* @param array $parameters * @param array $parameters
*
* @return void
*/ */
protected function convertParameters30(Property $newProperty, array $parameters) protected function convertParameters30(Property $newProperty, array $parameters) {
{
// Adding all parameters. // Adding all parameters.
foreach ($parameters as $param) { foreach ($parameters as $param) {
// vCard 2.1 allowed parameters with no name // vCard 2.1 allowed parameters with no name
if ($param->noName) { if ($param->noName) $param->noName = false;
$param->noName = false;
}
switch ($param->name) { switch ($param->name) {
case 'ENCODING':
case 'ENCODING' :
// This value only existed in vCard 2.1, and should be // This value only existed in vCard 2.1, and should be
// removed for anything else. // removed for anything else.
if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) { if (strtoupper($param->getValue()) !== 'QUOTED-PRINTABLE') {
$newProperty->add($param->name, $param->getParts()); $newProperty->add($param->name, $param->getParts());
} }
break; break;
@ -410,16 +449,19 @@ class VCardConverter
* *
* Any other PREF numbers we'll drop. * Any other PREF numbers we'll drop.
*/ */
case 'PREF': case 'PREF' :
if ('1' == $param->getValue()) { if ($param->getValue() == '1') {
$newProperty->add('TYPE', 'PREF'); $newProperty->add('TYPE', 'PREF');
} }
break; break;
default: default :
$newProperty->add($param->name, $param->getParts()); $newProperty->add($param->name, $param->getParts());
break; break;
} }
} }
} }
} }

View File

@ -9,10 +9,11 @@ namespace Sabre\VObject;
* @author Evert Pot (http://evertpot.com/) * @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Version class Version {
{
/** /**
* Full version number. * Full version number.
*/ */
const VERSION = '4.2.0'; const VERSION = '4.1.6';
} }

View File

@ -14,8 +14,8 @@ use Sabre\Xml;
* @author Ivan Enderlin * @author Ivan Enderlin
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
class Writer class Writer {
{
/** /**
* Serializes a vCard or iCalendar object. * Serializes a vCard or iCalendar object.
* *
@ -23,22 +23,24 @@ class Writer
* *
* @return string * @return string
*/ */
public static function write(Component $component) static function write(Component $component) {
{
return $component->serialize(); return $component->serialize();
} }
/** /**
* Serializes a jCal or jCard object. * Serializes a jCal or jCard object.
* *
* @param Component $component * @param Component $component
* @param int $options * @param int $options
* *
* @return string * @return string
*/ */
public static function writeJson(Component $component, $options = 0) static function writeJson(Component $component, $options = 0) {
{
return json_encode($component, $options); return json_encode($component, $options);
} }
/** /**
@ -48,8 +50,8 @@ class Writer
* *
* @return string * @return string
*/ */
public static function writeXml(Component $component) static function writeXml(Component $component) {
{
$writer = new Xml\Writer(); $writer = new Xml\Writer();
$writer->openMemory(); $writer->openMemory();
$writer->setIndent(true); $writer->setIndent(true);
@ -57,11 +59,15 @@ class Writer
$writer->startDocument('1.0', 'utf-8'); $writer->startDocument('1.0', 'utf-8');
if ($component instanceof Component\VCalendar) { if ($component instanceof Component\VCalendar) {
$writer->startElement('icalendar'); $writer->startElement('icalendar');
$writer->writeAttribute('xmlns', Parser\XML::XCAL_NAMESPACE); $writer->writeAttribute('xmlns', Parser\Xml::XCAL_NAMESPACE);
} else { } else {
$writer->startElement('vcards'); $writer->startElement('vcards');
$writer->writeAttribute('xmlns', Parser\XML::XCARD_NAMESPACE); $writer->writeAttribute('xmlns', Parser\Xml::XCARD_NAMESPACE);
} }
$component->xmlSerialize($writer); $component->xmlSerialize($writer);
@ -69,5 +75,7 @@ class Writer
$writer->endElement(); $writer->endElement();
return $writer->outputMemory(); return $writer->outputMemory();
} }
} }

View File

@ -12,83 +12,83 @@
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
return [ return [
'Universal Coordinated Time' => 'UTC', 'Universal Coordinated Time' => 'UTC',
'Casablanca, Monrovia' => 'Africa/Casablanca', 'Casablanca, Monrovia' => 'Africa/Casablanca',
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon', 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London', 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague', 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris', 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
'Prague, Central Europe' => 'Europe/Prague', 'Prague, Central Europe' => 'Europe/Prague',
'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo', 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
'West Central Africa' => 'Africa/Luanda', // This was a best guess 'West Central Africa' => 'Africa/Luanda', // This was a best guess
'Athens, Istanbul, Minsk' => 'Europe/Athens', 'Athens, Istanbul, Minsk' => 'Europe/Athens',
'Bucharest' => 'Europe/Bucharest', 'Bucharest' => 'Europe/Bucharest',
'Cairo' => 'Africa/Cairo', 'Cairo' => 'Africa/Cairo',
'Harare, Pretoria' => 'Africa/Harare', 'Harare, Pretoria' => 'Africa/Harare',
'Helsinki, Riga, Tallinn' => 'Europe/Helsinki', 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem', 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
'Baghdad' => 'Asia/Baghdad', 'Baghdad' => 'Asia/Baghdad',
'Arab, Kuwait, Riyadh' => 'Asia/Kuwait', 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
'East Africa, Nairobi' => 'Africa/Nairobi', 'East Africa, Nairobi' => 'Africa/Nairobi',
'Tehran' => 'Asia/Tehran', 'Tehran' => 'Asia/Tehran',
'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
'Baku, Tbilisi, Yerevan' => 'Asia/Baku', 'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
'Kabul' => 'Asia/Kabul', 'Kabul' => 'Asia/Kabul',
'Ekaterinburg' => 'Asia/Yekaterinburg', 'Ekaterinburg' => 'Asia/Yekaterinburg',
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi', 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta', 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
'Kathmandu, Nepal' => 'Asia/Kathmandu', 'Kathmandu, Nepal' => 'Asia/Kathmandu',
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty', 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
'Astana, Dhaka' => 'Asia/Dhaka', 'Astana, Dhaka' => 'Asia/Dhaka',
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo', 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
'Rangoon' => 'Asia/Rangoon', 'Rangoon' => 'Asia/Rangoon',
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
'Krasnoyarsk' => 'Asia/Krasnoyarsk', 'Krasnoyarsk' => 'Asia/Krasnoyarsk',
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai', 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk', 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
'Kuala Lumpur, Singapore' => 'Asia/Singapore', 'Kuala Lumpur, Singapore' => 'Asia/Singapore',
'Perth, Western Australia' => 'Australia/Perth', 'Perth, Western Australia' => 'Australia/Perth',
'Taipei' => 'Asia/Taipei', 'Taipei' => 'Asia/Taipei',
'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
'Seoul, Korea Standard time' => 'Asia/Seoul', 'Seoul, Korea Standard time' => 'Asia/Seoul',
'Yakutsk' => 'Asia/Yakutsk', 'Yakutsk' => 'Asia/Yakutsk',
'Adelaide, Central Australia' => 'Australia/Adelaide', 'Adelaide, Central Australia' => 'Australia/Adelaide',
'Darwin' => 'Australia/Darwin', 'Darwin' => 'Australia/Darwin',
'Brisbane, East Australia' => 'Australia/Brisbane', 'Brisbane, East Australia' => 'Australia/Brisbane',
'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney', 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
'Guam, Port Moresby' => 'Pacific/Guam', 'Guam, Port Moresby' => 'Pacific/Guam',
'Hobart, Tasmania' => 'Australia/Hobart', 'Hobart, Tasmania' => 'Australia/Hobart',
'Vladivostok' => 'Asia/Vladivostok', 'Vladivostok' => 'Asia/Vladivostok',
'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan', 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
'Auckland, Wellington' => 'Pacific/Auckland', 'Auckland, Wellington' => 'Pacific/Auckland',
'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji', 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu', 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
'Azores' => 'Atlantic/Azores', 'Azores' => 'Atlantic/Azores',
'Cape Verde Is.' => 'Atlantic/Cape_Verde', 'Cape Verde Is.' => 'Atlantic/Cape_Verde',
'Mid-Atlantic' => 'America/Noronha', 'Mid-Atlantic' => 'America/Noronha',
'Brasilia' => 'America/Sao_Paulo', // Best guess 'Brasilia' => 'America/Sao_Paulo', // Best guess
'Buenos Aires' => 'America/Argentina/Buenos_Aires', 'Buenos Aires' => 'America/Argentina/Buenos_Aires',
'Greenland' => 'America/Godthab', 'Greenland' => 'America/Godthab',
'Newfoundland' => 'America/St_Johns', 'Newfoundland' => 'America/St_Johns',
'Atlantic Time (Canada)' => 'America/Halifax', 'Atlantic Time (Canada)' => 'America/Halifax',
'Caracas, La Paz' => 'America/Caracas', 'Caracas, La Paz' => 'America/Caracas',
'Santiago' => 'America/Santiago', 'Santiago' => 'America/Santiago',
'Bogota, Lima, Quito' => 'America/Bogota', 'Bogota, Lima, Quito' => 'America/Bogota',
'Eastern Time (US & Canada)' => 'America/New_York', 'Eastern Time (US & Canada)' => 'America/New_York',
'Indiana (East)' => 'America/Indiana/Indianapolis', 'Indiana (East)' => 'America/Indiana/Indianapolis',
'Central America' => 'America/Guatemala', 'Central America' => 'America/Guatemala',
'Central Time (US & Canada)' => 'America/Chicago', 'Central Time (US & Canada)' => 'America/Chicago',
'Mexico City, Tegucigalpa' => 'America/Mexico_City', 'Mexico City, Tegucigalpa' => 'America/Mexico_City',
'Saskatchewan' => 'America/Edmonton', 'Saskatchewan' => 'America/Edmonton',
'Arizona' => 'America/Phoenix', 'Arizona' => 'America/Phoenix',
'Mountain Time (US & Canada)' => 'America/Denver', // Best guess 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
'Pacific Time (US & Canada)' => 'America/Los_Angeles', // Best guess 'Pacific Time (US & Canada)' => 'America/Los_Angeles', // Best guess
'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
'Alaska' => 'America/Anchorage', 'Alaska' => 'America/Anchorage',
'Hawaii' => 'Pacific/Honolulu', 'Hawaii' => 'Pacific/Honolulu',
'Midway Island, Samoa' => 'Pacific/Midway', 'Midway Island, Samoa' => 'Pacific/Midway',
'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
]; ];

View File

@ -8,94 +8,94 @@
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
return [ return [
'Dateline' => 'Etc/GMT-12', 'Dateline' => 'Etc/GMT-12',
'Samoa' => 'Pacific/Apia', 'Samoa' => 'Pacific/Apia',
'Hawaiian' => 'Pacific/Honolulu', 'Hawaiian' => 'Pacific/Honolulu',
'Alaskan' => 'America/Anchorage', 'Alaskan' => 'America/Anchorage',
'Pacific' => 'America/Los_Angeles', 'Pacific' => 'America/Los_Angeles',
'Pacific Standard Time' => 'America/Los_Angeles', 'Pacific Standard Time' => 'America/Los_Angeles',
'Mexico Standard Time 2' => 'America/Chihuahua', 'Mexico Standard Time 2' => 'America/Chihuahua',
'Mountain' => 'America/Denver', 'Mountain' => 'America/Denver',
// 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones. // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones.
'US Mountain' => 'America/Phoenix', 'US Mountain' => 'America/Phoenix',
'Canada Central' => 'America/Edmonton', 'Canada Central' => 'America/Edmonton',
'Central America' => 'America/Guatemala', 'Central America' => 'America/Guatemala',
'Central' => 'America/Chicago', 'Central' => 'America/Chicago',
// 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones. // 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones.
'Mexico' => 'America/Mexico_City', 'Mexico' => 'America/Mexico_City',
'Eastern' => 'America/New_York', 'Eastern' => 'America/New_York',
'SA Pacific' => 'America/Bogota', 'SA Pacific' => 'America/Bogota',
'US Eastern' => 'America/Indiana/Indianapolis', 'US Eastern' => 'America/Indiana/Indianapolis',
'Venezuela' => 'America/Caracas', 'Venezuela' => 'America/Caracas',
'Atlantic' => 'America/Halifax', 'Atlantic' => 'America/Halifax',
'Central Brazilian' => 'America/Manaus', 'Central Brazilian' => 'America/Manaus',
'Pacific SA' => 'America/Santiago', 'Pacific SA' => 'America/Santiago',
'SA Western' => 'America/La_Paz', 'SA Western' => 'America/La_Paz',
'Newfoundland' => 'America/St_Johns', 'Newfoundland' => 'America/St_Johns',
'Argentina' => 'America/Argentina/Buenos_Aires', 'Argentina' => 'America/Argentina/Buenos_Aires',
'E. South America' => 'America/Belem', 'E. South America' => 'America/Belem',
'Greenland' => 'America/Godthab', 'Greenland' => 'America/Godthab',
'Montevideo' => 'America/Montevideo', 'Montevideo' => 'America/Montevideo',
'SA Eastern' => 'America/Belem', 'SA Eastern' => 'America/Belem',
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones. // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
'Azores' => 'Atlantic/Azores', 'Azores' => 'Atlantic/Azores',
'Cape Verde' => 'Atlantic/Cape_Verde', 'Cape Verde' => 'Atlantic/Cape_Verde',
'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT. 'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
'Morocco' => 'Africa/Casablanca', 'Morocco' => 'Africa/Casablanca',
'Central Europe' => 'Europe/Prague', 'Central Europe' => 'Europe/Prague',
'Central European' => 'Europe/Sarajevo', 'Central European' => 'Europe/Sarajevo',
'Romance' => 'Europe/Paris', 'Romance' => 'Europe/Paris',
'W. Central Africa' => 'Africa/Lagos', // Best guess 'W. Central Africa' => 'Africa/Lagos', // Best guess
'W. Europe' => 'Europe/Amsterdam', 'W. Europe' => 'Europe/Amsterdam',
'E. Europe' => 'Europe/Minsk', 'E. Europe' => 'Europe/Minsk',
'Egypt' => 'Africa/Cairo', 'Egypt' => 'Africa/Cairo',
'FLE' => 'Europe/Helsinki', 'FLE' => 'Europe/Helsinki',
'GTB' => 'Europe/Athens', 'GTB' => 'Europe/Athens',
'Israel' => 'Asia/Jerusalem', 'Israel' => 'Asia/Jerusalem',
'Jordan' => 'Asia/Amman', 'Jordan' => 'Asia/Amman',
'Middle East' => 'Asia/Beirut', 'Middle East' => 'Asia/Beirut',
'Namibia' => 'Africa/Windhoek', 'Namibia' => 'Africa/Windhoek',
'South Africa' => 'Africa/Harare', 'South Africa' => 'Africa/Harare',
'Arab' => 'Asia/Kuwait', 'Arab' => 'Asia/Kuwait',
'Arabic' => 'Asia/Baghdad', 'Arabic' => 'Asia/Baghdad',
'E. Africa' => 'Africa/Nairobi', 'E. Africa' => 'Africa/Nairobi',
'Georgian' => 'Asia/Tbilisi', 'Georgian' => 'Asia/Tbilisi',
'Russian' => 'Europe/Moscow', 'Russian' => 'Europe/Moscow',
'Iran' => 'Asia/Tehran', 'Iran' => 'Asia/Tehran',
'Arabian' => 'Asia/Muscat', 'Arabian' => 'Asia/Muscat',
'Armenian' => 'Asia/Yerevan', 'Armenian' => 'Asia/Yerevan',
'Azerbijan' => 'Asia/Baku', 'Azerbijan' => 'Asia/Baku',
'Caucasus' => 'Asia/Yerevan', 'Caucasus' => 'Asia/Yerevan',
'Mauritius' => 'Indian/Mauritius', 'Mauritius' => 'Indian/Mauritius',
'Afghanistan' => 'Asia/Kabul', 'Afghanistan' => 'Asia/Kabul',
'Ekaterinburg' => 'Asia/Yekaterinburg', 'Ekaterinburg' => 'Asia/Yekaterinburg',
'Pakistan' => 'Asia/Karachi', 'Pakistan' => 'Asia/Karachi',
'West Asia' => 'Asia/Tashkent', 'West Asia' => 'Asia/Tashkent',
'India' => 'Asia/Calcutta', 'India' => 'Asia/Calcutta',
'Sri Lanka' => 'Asia/Colombo', 'Sri Lanka' => 'Asia/Colombo',
'Nepal' => 'Asia/Kathmandu', 'Nepal' => 'Asia/Kathmandu',
'Central Asia' => 'Asia/Dhaka', 'Central Asia' => 'Asia/Dhaka',
'N. Central Asia' => 'Asia/Almaty', 'N. Central Asia' => 'Asia/Almaty',
'Myanmar' => 'Asia/Rangoon', 'Myanmar' => 'Asia/Rangoon',
'North Asia' => 'Asia/Krasnoyarsk', 'North Asia' => 'Asia/Krasnoyarsk',
'SE Asia' => 'Asia/Bangkok', 'SE Asia' => 'Asia/Bangkok',
'China' => 'Asia/Shanghai', 'China' => 'Asia/Shanghai',
'North Asia East' => 'Asia/Irkutsk', 'North Asia East' => 'Asia/Irkutsk',
'Singapore' => 'Asia/Singapore', 'Singapore' => 'Asia/Singapore',
'Taipei' => 'Asia/Taipei', 'Taipei' => 'Asia/Taipei',
'W. Australia' => 'Australia/Perth', 'W. Australia' => 'Australia/Perth',
'Korea' => 'Asia/Seoul', 'Korea' => 'Asia/Seoul',
'Tokyo' => 'Asia/Tokyo', 'Tokyo' => 'Asia/Tokyo',
'Yakutsk' => 'Asia/Yakutsk', 'Yakutsk' => 'Asia/Yakutsk',
'AUS Central' => 'Australia/Darwin', 'AUS Central' => 'Australia/Darwin',
'Cen. Australia' => 'Australia/Adelaide', 'Cen. Australia' => 'Australia/Adelaide',
'AUS Eastern' => 'Australia/Sydney', 'AUS Eastern' => 'Australia/Sydney',
'E. Australia' => 'Australia/Brisbane', 'E. Australia' => 'Australia/Brisbane',
'Tasmania' => 'Australia/Hobart', 'Tasmania' => 'Australia/Hobart',
'Vladivostok' => 'Asia/Vladivostok', 'Vladivostok' => 'Asia/Vladivostok',
'West Pacific' => 'Pacific/Guam', 'West Pacific' => 'Pacific/Guam',
'Central Pacific' => 'Asia/Magadan', 'Central Pacific' => 'Asia/Magadan',
'Fiji' => 'Pacific/Fiji', 'Fiji' => 'Pacific/Fiji',
'New Zealand' => 'Pacific/Auckland', 'New Zealand' => 'Pacific/Auckland',
'Tonga' => 'Pacific/Tongatapu', 'Tonga' => 'Pacific/Tongatapu',
]; ];

View File

@ -15,32 +15,32 @@
* @license http://sabre.io/license/ Modified BSD License * @license http://sabre.io/license/ Modified BSD License
*/ */
return [ return [
'CST6CDT' => 'America/Chicago', 'CST6CDT' => 'America/Chicago',
'Cuba' => 'America/Havana', 'Cuba' => 'America/Havana',
'Egypt' => 'Africa/Cairo', 'Egypt' => 'Africa/Cairo',
'Eire' => 'Europe/Dublin', 'Eire' => 'Europe/Dublin',
'EST5EDT' => 'America/New_York', 'EST5EDT' => 'America/New_York',
'Factory' => 'UTC', 'Factory' => 'UTC',
'GB-Eire' => 'Europe/London', 'GB-Eire' => 'Europe/London',
'GMT0' => 'UTC', 'GMT0' => 'UTC',
'Greenwich' => 'UTC', 'Greenwich' => 'UTC',
'Hongkong' => 'Asia/Hong_Kong', 'Hongkong' => 'Asia/Hong_Kong',
'Iceland' => 'Atlantic/Reykjavik', 'Iceland' => 'Atlantic/Reykjavik',
'Iran' => 'Asia/Tehran', 'Iran' => 'Asia/Tehran',
'Israel' => 'Asia/Jerusalem', 'Israel' => 'Asia/Jerusalem',
'Jamaica' => 'America/Jamaica', 'Jamaica' => 'America/Jamaica',
'Japan' => 'Asia/Tokyo', 'Japan' => 'Asia/Tokyo',
'Kwajalein' => 'Pacific/Kwajalein', 'Kwajalein' => 'Pacific/Kwajalein',
'Libya' => 'Africa/Tripoli', 'Libya' => 'Africa/Tripoli',
'MST7MDT' => 'America/Denver', 'MST7MDT' => 'America/Denver',
'Navajo' => 'America/Denver', 'Navajo' => 'America/Denver',
'NZ-CHAT' => 'Pacific/Chatham', 'NZ-CHAT' => 'Pacific/Chatham',
'Poland' => 'Europe/Warsaw', 'Poland' => 'Europe/Warsaw',
'Portugal' => 'Europe/Lisbon', 'Portugal' => 'Europe/Lisbon',
'PST8PDT' => 'America/Los_Angeles', 'PST8PDT' => 'America/Los_Angeles',
'Singapore' => 'Asia/Singapore', 'Singapore' => 'Asia/Singapore',
'Turkey' => 'Europe/Istanbul', 'Turkey' => 'Europe/Istanbul',
'Universal' => 'UTC', 'Universal' => 'UTC',
'W-SU' => 'Europe/Moscow', 'W-SU' => 'Europe/Moscow',
'Zulu' => 'UTC', 'Zulu' => 'UTC',
]; ];

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Automatically generated timezone file. * Automatically generated timezone file
* *
* Last update: 2016-08-24T17:35:38-04:00 * Last update: 2016-08-24T17:35:38-04:00
* Source: http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml * Source: http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml
@ -11,133 +11,133 @@
*/ */
return [ return [
'AUS Central Standard Time' => 'Australia/Darwin', 'AUS Central Standard Time' => 'Australia/Darwin',
'AUS Eastern Standard Time' => 'Australia/Sydney', 'AUS Eastern Standard Time' => 'Australia/Sydney',
'Afghanistan Standard Time' => 'Asia/Kabul', 'Afghanistan Standard Time' => 'Asia/Kabul',
'Alaskan Standard Time' => 'America/Anchorage', 'Alaskan Standard Time' => 'America/Anchorage',
'Aleutian Standard Time' => 'America/Adak', 'Aleutian Standard Time' => 'America/Adak',
'Altai Standard Time' => 'Asia/Barnaul', 'Altai Standard Time' => 'Asia/Barnaul',
'Arab Standard Time' => 'Asia/Riyadh', 'Arab Standard Time' => 'Asia/Riyadh',
'Arabian Standard Time' => 'Asia/Dubai', 'Arabian Standard Time' => 'Asia/Dubai',
'Arabic Standard Time' => 'Asia/Baghdad', 'Arabic Standard Time' => 'Asia/Baghdad',
'Argentina Standard Time' => 'America/Buenos_Aires', 'Argentina Standard Time' => 'America/Buenos_Aires',
'Astrakhan Standard Time' => 'Europe/Astrakhan', 'Astrakhan Standard Time' => 'Europe/Astrakhan',
'Atlantic Standard Time' => 'America/Halifax', 'Atlantic Standard Time' => 'America/Halifax',
'Aus Central W. Standard Time' => 'Australia/Eucla', 'Aus Central W. Standard Time' => 'Australia/Eucla',
'Azerbaijan Standard Time' => 'Asia/Baku', 'Azerbaijan Standard Time' => 'Asia/Baku',
'Azores Standard Time' => 'Atlantic/Azores', 'Azores Standard Time' => 'Atlantic/Azores',
'Bahia Standard Time' => 'America/Bahia', 'Bahia Standard Time' => 'America/Bahia',
'Bangladesh Standard Time' => 'Asia/Dhaka', 'Bangladesh Standard Time' => 'Asia/Dhaka',
'Belarus Standard Time' => 'Europe/Minsk', 'Belarus Standard Time' => 'Europe/Minsk',
'Bougainville Standard Time' => 'Pacific/Bougainville', 'Bougainville Standard Time' => 'Pacific/Bougainville',
'Canada Central Standard Time' => 'America/Regina', 'Canada Central Standard Time' => 'America/Regina',
'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
'Caucasus Standard Time' => 'Asia/Yerevan', 'Caucasus Standard Time' => 'Asia/Yerevan',
'Cen. Australia Standard Time' => 'Australia/Adelaide', 'Cen. Australia Standard Time' => 'Australia/Adelaide',
'Central America Standard Time' => 'America/Guatemala', 'Central America Standard Time' => 'America/Guatemala',
'Central Asia Standard Time' => 'Asia/Almaty', 'Central Asia Standard Time' => 'Asia/Almaty',
'Central Brazilian Standard Time' => 'America/Cuiaba', 'Central Brazilian Standard Time' => 'America/Cuiaba',
'Central Europe Standard Time' => 'Europe/Budapest', 'Central Europe Standard Time' => 'Europe/Budapest',
'Central European Standard Time' => 'Europe/Warsaw', 'Central European Standard Time' => 'Europe/Warsaw',
'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal',
'Central Standard Time' => 'America/Chicago', 'Central Standard Time' => 'America/Chicago',
'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Central Standard Time (Mexico)' => 'America/Mexico_City',
'Chatham Islands Standard Time' => 'Pacific/Chatham', 'Chatham Islands Standard Time' => 'Pacific/Chatham',
'China Standard Time' => 'Asia/Shanghai', 'China Standard Time' => 'Asia/Shanghai',
'Cuba Standard Time' => 'America/Havana', 'Cuba Standard Time' => 'America/Havana',
'Dateline Standard Time' => 'Etc/GMT+12', 'Dateline Standard Time' => 'Etc/GMT+12',
'E. Africa Standard Time' => 'Africa/Nairobi', 'E. Africa Standard Time' => 'Africa/Nairobi',
'E. Australia Standard Time' => 'Australia/Brisbane', 'E. Australia Standard Time' => 'Australia/Brisbane',
'E. Europe Standard Time' => 'Europe/Chisinau', 'E. Europe Standard Time' => 'Europe/Chisinau',
'E. South America Standard Time' => 'America/Sao_Paulo', 'E. South America Standard Time' => 'America/Sao_Paulo',
'Easter Island Standard Time' => 'Pacific/Easter', 'Easter Island Standard Time' => 'Pacific/Easter',
'Eastern Standard Time' => 'America/New_York', 'Eastern Standard Time' => 'America/New_York',
'Eastern Standard Time (Mexico)' => 'America/Cancun', 'Eastern Standard Time (Mexico)' => 'America/Cancun',
'Egypt Standard Time' => 'Africa/Cairo', 'Egypt Standard Time' => 'Africa/Cairo',
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
'FLE Standard Time' => 'Europe/Kiev', 'FLE Standard Time' => 'Europe/Kiev',
'Fiji Standard Time' => 'Pacific/Fiji', 'Fiji Standard Time' => 'Pacific/Fiji',
'GMT Standard Time' => 'Europe/London', 'GMT Standard Time' => 'Europe/London',
'GTB Standard Time' => 'Europe/Bucharest', 'GTB Standard Time' => 'Europe/Bucharest',
'Georgian Standard Time' => 'Asia/Tbilisi', 'Georgian Standard Time' => 'Asia/Tbilisi',
'Greenland Standard Time' => 'America/Godthab', 'Greenland Standard Time' => 'America/Godthab',
'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Greenwich Standard Time' => 'Atlantic/Reykjavik',
'Haiti Standard Time' => 'America/Port-au-Prince', 'Haiti Standard Time' => 'America/Port-au-Prince',
'Hawaiian Standard Time' => 'Pacific/Honolulu', 'Hawaiian Standard Time' => 'Pacific/Honolulu',
'India Standard Time' => 'Asia/Calcutta', 'India Standard Time' => 'Asia/Calcutta',
'Iran Standard Time' => 'Asia/Tehran', 'Iran Standard Time' => 'Asia/Tehran',
'Israel Standard Time' => 'Asia/Jerusalem', 'Israel Standard Time' => 'Asia/Jerusalem',
'Jordan Standard Time' => 'Asia/Amman', 'Jordan Standard Time' => 'Asia/Amman',
'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 'Kaliningrad Standard Time' => 'Europe/Kaliningrad',
'Korea Standard Time' => 'Asia/Seoul', 'Korea Standard Time' => 'Asia/Seoul',
'Libya Standard Time' => 'Africa/Tripoli', 'Libya Standard Time' => 'Africa/Tripoli',
'Line Islands Standard Time' => 'Pacific/Kiritimati', 'Line Islands Standard Time' => 'Pacific/Kiritimati',
'Lord Howe Standard Time' => 'Australia/Lord_Howe', 'Lord Howe Standard Time' => 'Australia/Lord_Howe',
'Magadan Standard Time' => 'Asia/Magadan', 'Magadan Standard Time' => 'Asia/Magadan',
'Marquesas Standard Time' => 'Pacific/Marquesas', 'Marquesas Standard Time' => 'Pacific/Marquesas',
'Mauritius Standard Time' => 'Indian/Mauritius', 'Mauritius Standard Time' => 'Indian/Mauritius',
'Middle East Standard Time' => 'Asia/Beirut', 'Middle East Standard Time' => 'Asia/Beirut',
'Montevideo Standard Time' => 'America/Montevideo', 'Montevideo Standard Time' => 'America/Montevideo',
'Morocco Standard Time' => 'Africa/Casablanca', 'Morocco Standard Time' => 'Africa/Casablanca',
'Mountain Standard Time' => 'America/Denver', 'Mountain Standard Time' => 'America/Denver',
'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
'Myanmar Standard Time' => 'Asia/Rangoon', 'Myanmar Standard Time' => 'Asia/Rangoon',
'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
'Namibia Standard Time' => 'Africa/Windhoek', 'Namibia Standard Time' => 'Africa/Windhoek',
'Nepal Standard Time' => 'Asia/Katmandu', 'Nepal Standard Time' => 'Asia/Katmandu',
'New Zealand Standard Time' => 'Pacific/Auckland', 'New Zealand Standard Time' => 'Pacific/Auckland',
'Newfoundland Standard Time' => 'America/St_Johns', 'Newfoundland Standard Time' => 'America/St_Johns',
'Norfolk Standard Time' => 'Pacific/Norfolk', 'Norfolk Standard Time' => 'Pacific/Norfolk',
'North Asia East Standard Time' => 'Asia/Irkutsk', 'North Asia East Standard Time' => 'Asia/Irkutsk',
'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'North Asia Standard Time' => 'Asia/Krasnoyarsk',
'North Korea Standard Time' => 'Asia/Pyongyang', 'North Korea Standard Time' => 'Asia/Pyongyang',
'Pacific SA Standard Time' => 'America/Santiago', 'Pacific SA Standard Time' => 'America/Santiago',
'Pacific Standard Time' => 'America/Los_Angeles', 'Pacific Standard Time' => 'America/Los_Angeles',
'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'Pacific Standard Time (Mexico)' => 'America/Tijuana',
'Pakistan Standard Time' => 'Asia/Karachi', 'Pakistan Standard Time' => 'Asia/Karachi',
'Paraguay Standard Time' => 'America/Asuncion', 'Paraguay Standard Time' => 'America/Asuncion',
'Romance Standard Time' => 'Europe/Paris', 'Romance Standard Time' => 'Europe/Paris',
'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Russia Time Zone 10' => 'Asia/Srednekolymsk',
'Russia Time Zone 11' => 'Asia/Kamchatka', 'Russia Time Zone 11' => 'Asia/Kamchatka',
'Russia Time Zone 3' => 'Europe/Samara', 'Russia Time Zone 3' => 'Europe/Samara',
'Russian Standard Time' => 'Europe/Moscow', 'Russian Standard Time' => 'Europe/Moscow',
'SA Eastern Standard Time' => 'America/Cayenne', 'SA Eastern Standard Time' => 'America/Cayenne',
'SA Pacific Standard Time' => 'America/Bogota', 'SA Pacific Standard Time' => 'America/Bogota',
'SA Western Standard Time' => 'America/La_Paz', 'SA Western Standard Time' => 'America/La_Paz',
'SE Asia Standard Time' => 'Asia/Bangkok', 'SE Asia Standard Time' => 'Asia/Bangkok',
'Saint Pierre Standard Time' => 'America/Miquelon', 'Saint Pierre Standard Time' => 'America/Miquelon',
'Sakhalin Standard Time' => 'Asia/Sakhalin', 'Sakhalin Standard Time' => 'Asia/Sakhalin',
'Samoa Standard Time' => 'Pacific/Apia', 'Samoa Standard Time' => 'Pacific/Apia',
'Singapore Standard Time' => 'Asia/Singapore', 'Singapore Standard Time' => 'Asia/Singapore',
'South Africa Standard Time' => 'Africa/Johannesburg', 'South Africa Standard Time' => 'Africa/Johannesburg',
'Sri Lanka Standard Time' => 'Asia/Colombo', 'Sri Lanka Standard Time' => 'Asia/Colombo',
'Syria Standard Time' => 'Asia/Damascus', 'Syria Standard Time' => 'Asia/Damascus',
'Taipei Standard Time' => 'Asia/Taipei', 'Taipei Standard Time' => 'Asia/Taipei',
'Tasmania Standard Time' => 'Australia/Hobart', 'Tasmania Standard Time' => 'Australia/Hobart',
'Tocantins Standard Time' => 'America/Araguaina', 'Tocantins Standard Time' => 'America/Araguaina',
'Tokyo Standard Time' => 'Asia/Tokyo', 'Tokyo Standard Time' => 'Asia/Tokyo',
'Tomsk Standard Time' => 'Asia/Tomsk', 'Tomsk Standard Time' => 'Asia/Tomsk',
'Tonga Standard Time' => 'Pacific/Tongatapu', 'Tonga Standard Time' => 'Pacific/Tongatapu',
'Transbaikal Standard Time' => 'Asia/Chita', 'Transbaikal Standard Time' => 'Asia/Chita',
'Turkey Standard Time' => 'Europe/Istanbul', 'Turkey Standard Time' => 'Europe/Istanbul',
'Turks And Caicos Standard Time' => 'America/Grand_Turk', 'Turks And Caicos Standard Time' => 'America/Grand_Turk',
'US Eastern Standard Time' => 'America/Indianapolis', 'US Eastern Standard Time' => 'America/Indianapolis',
'US Mountain Standard Time' => 'America/Phoenix', 'US Mountain Standard Time' => 'America/Phoenix',
'UTC' => 'Etc/GMT', 'UTC' => 'Etc/GMT',
'UTC+12' => 'Etc/GMT-12', 'UTC+12' => 'Etc/GMT-12',
'UTC-02' => 'Etc/GMT+2', 'UTC-02' => 'Etc/GMT+2',
'UTC-08' => 'Etc/GMT+8', 'UTC-08' => 'Etc/GMT+8',
'UTC-09' => 'Etc/GMT+9', 'UTC-09' => 'Etc/GMT+9',
'UTC-11' => 'Etc/GMT+11', 'UTC-11' => 'Etc/GMT+11',
'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar',
'Venezuela Standard Time' => 'America/Caracas', 'Venezuela Standard Time' => 'America/Caracas',
'Vladivostok Standard Time' => 'Asia/Vladivostok', 'Vladivostok Standard Time' => 'Asia/Vladivostok',
'W. Australia Standard Time' => 'Australia/Perth', 'W. Australia Standard Time' => 'Australia/Perth',
'W. Central Africa Standard Time' => 'Africa/Lagos', 'W. Central Africa Standard Time' => 'Africa/Lagos',
'W. Europe Standard Time' => 'Europe/Berlin', 'W. Europe Standard Time' => 'Europe/Berlin',
'W. Mongolia Standard Time' => 'Asia/Hovd', 'W. Mongolia Standard Time' => 'Asia/Hovd',
'West Asia Standard Time' => 'Asia/Tashkent', 'West Asia Standard Time' => 'Asia/Tashkent',
'West Bank Standard Time' => 'Asia/Hebron', 'West Bank Standard Time' => 'Asia/Hebron',
'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'West Pacific Standard Time' => 'Pacific/Port_Moresby',
'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Yakutsk Standard Time' => 'Asia/Yakutsk',
]; ];

View File

@ -3,8 +3,8 @@
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
$try = [ $try = [
__DIR__.'/../vendor/autoload.php', __DIR__ . '/../vendor/autoload.php',
__DIR__.'/../../../autoload.php', __DIR__ . '/../../../autoload.php',
]; ];
foreach ($try as $path) { foreach ($try as $path) {
@ -14,12 +14,12 @@ foreach ($try as $path) {
} }
} }
$autoLoader->addPsr4('Sabre\\VObject\\', __DIR__.'/VObject'); $autoLoader->addPsr4('Sabre\\VObject\\', __DIR__ . '/VObject');
if (!defined('SABRE_TEMPDIR')) { if (!defined('SABRE_TEMPDIR')) {
define('SABRE_TEMPDIR', __DIR__.'/temp/'); define('SABRE_TEMPDIR', __DIR__ . '/temp/');
} }
if (!file_exists(SABRE_TEMPDIR)) { if (!file_exists(SABRE_TEMPDIR)) {
mkdir(SABRE_TEMPDIR); mkdir(SABRE_TEMPDIR);
} }

View File

@ -1,12 +1,6 @@
ChangeLog ChangeLog
========= =========
1.5.1 (2019-01-09)
------------------
* #161: Prevent infinite loop on empty xml elements
1.5.0 (2016-10-09) 1.5.0 (2016-10-09)
------------------ ------------------

View File

@ -45,7 +45,7 @@
}, },
"require-dev": { "require-dev": {
"sabre/cs": "~1.0.0", "sabre/cs": "~1.0.0",
"phpunit/phpunit" : "~4.8|~5.7" "phpunit/phpunit" : "*"
}, },
"config" : { "config" : {
"bin-dir" : "bin/" "bin-dir" : "bin/"

View File

@ -66,20 +66,9 @@ function keyValue(Reader $reader, $namespace = null) {
return []; return [];
} }
if (!$reader->read()) {
$reader->next();
return [];
}
if (Reader::END_ELEMENT === $reader->nodeType) {
$reader->next();
return [];
}
$values = []; $values = [];
$reader->read();
do { do {
if ($reader->nodeType === Reader::ELEMENT) { if ($reader->nodeType === Reader::ELEMENT) {
@ -90,9 +79,7 @@ function keyValue(Reader $reader, $namespace = null) {
$values[$clark] = $reader->parseCurrentElement()['value']; $values[$clark] = $reader->parseCurrentElement()['value'];
} }
} else { } else {
if (!$reader->read()) { $reader->read();
break;
}
} }
} while ($reader->nodeType !== Reader::END_ELEMENT); } while ($reader->nodeType !== Reader::END_ELEMENT);
@ -157,17 +144,7 @@ function enum(Reader $reader, $namespace = null) {
$reader->next(); $reader->next();
return []; return [];
} }
if (!$reader->read()) { $reader->read();
$reader->next();
return [];
}
if (Reader::END_ELEMENT === $reader->nodeType) {
$reader->next();
return [];
}
$currentDepth = $reader->depth; $currentDepth = $reader->depth;
$values = []; $values = [];
@ -227,9 +204,7 @@ function valueObject(Reader $reader, $className, $namespace) {
$reader->next(); $reader->next();
} }
} else { } else {
if (!$reader->read()) { $reader->read();
break;
}
} }
} while ($reader->nodeType !== Reader::END_ELEMENT); } while ($reader->nodeType !== Reader::END_ELEMENT);

View File

@ -138,8 +138,7 @@ class Service {
* @param string|string[] $rootElementName * @param string|string[] $rootElementName
* @param string|resource $input * @param string|resource $input
* @param string|null $contextUri * @param string|null $contextUri
* @throws ParseException * @return void
* @return array|object|string
*/ */
function expect($rootElementName, $input, $contextUri = null) { function expect($rootElementName, $input, $contextUri = null) {

Some files were not shown because too many files have changed in this diff Show More