mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Upgrade Sabre #304 (needs testing)
This commit is contained in:
parent
2fae820ee4
commit
b074a9eaf3
105 changed files with 12726 additions and 4587 deletions
|
|
@ -115,6 +115,7 @@ trait Raw
|
||||||
private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool
|
private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool
|
||||||
{
|
{
|
||||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||||
|
|
||||||
$aValues = $this->getDecodedRawKeyValue($sRawKey);
|
$aValues = $this->getDecodedRawKeyValue($sRawKey);
|
||||||
|
|
||||||
$sRange = $this->Http()->GetHeader('Range');
|
$sRange = $this->Http()->GetHeader('Range');
|
||||||
|
|
@ -139,8 +140,6 @@ trait Raw
|
||||||
$sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
|
$sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
|
||||||
$sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : '';
|
$sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : '';
|
||||||
|
|
||||||
$bDetectImageOrientation = !!$this->Config()->Get('labs', 'detect_image_exif_orientation', false);
|
|
||||||
|
|
||||||
if (!empty($sFileHashIn))
|
if (!empty($sFileHashIn))
|
||||||
{
|
{
|
||||||
$this->verifyCacheByKey($sRawKey);
|
$this->verifyCacheByKey($sRawKey);
|
||||||
|
|
@ -172,60 +171,49 @@ trait Raw
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else if (!empty($sFolder) && 0 < $iUid)
|
||||||
{
|
|
||||||
if (!empty($sFolder) && 0 < $iUid)
|
|
||||||
{
|
{
|
||||||
$this->verifyCacheByKey($sRawKey);
|
$this->verifyCacheByKey($sRawKey);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$oAccount = $this->initMailClientConnection();
|
$oAccount = $this->initMailClientConnection();
|
||||||
|
|
||||||
$self = $this;
|
$self = $this;
|
||||||
return $this->MailClient()->MessageMimeStream(
|
return $this->MailClient()->MessageMimeStream(
|
||||||
function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use (
|
function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use (
|
||||||
$self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail, $bDetectImageOrientation,
|
$self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail,
|
||||||
$bIsRangeRequest, $sRangeStart, $sRangeEnd
|
$bIsRangeRequest, $sRangeStart, $sRangeEnd
|
||||||
) {
|
) {
|
||||||
if ($oAccount && \is_resource($rResource))
|
if ($oAccount && \is_resource($rResource))
|
||||||
{
|
{
|
||||||
\MailSo\Base\Utils::ResetTimeLimit();
|
\MailSo\Base\Utils::ResetTimeLimit();
|
||||||
|
|
||||||
$sContentTypeOut = $sContentTypeIn;
|
|
||||||
if (empty($sContentTypeOut))
|
|
||||||
{
|
|
||||||
$sContentTypeOut = $sContentType;
|
|
||||||
if (empty($sContentTypeOut))
|
|
||||||
{
|
|
||||||
$sContentTypeOut = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sFileNameOut = $sFileNameIn;
|
|
||||||
if (empty($sFileNameOut))
|
|
||||||
{
|
|
||||||
$sFileNameOut = $sFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/the-djmaze/snappymail/issues/144
|
|
||||||
if ('.pdf' === \substr($sFileNameOut,-4)) {
|
|
||||||
$sContentTypeOut = 'application/pdf';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sFileNameOut = $self->MainClearFileName($sFileNameOut, $sContentTypeOut, $sMimeIndex);
|
|
||||||
|
|
||||||
$self->cacheByKey($sRawKey);
|
$self->cacheByKey($sRawKey);
|
||||||
|
|
||||||
|
if ($sFileNameIn) {
|
||||||
|
$sFileName = $sFileNameIn;
|
||||||
|
}
|
||||||
|
$sFileName = $self->MainClearFileName($sFileName, $sContentType, $sMimeIndex);
|
||||||
|
|
||||||
|
if ('.pdf' === \substr($sFileName, -4)) {
|
||||||
|
// https://github.com/the-djmaze/snappymail/issues/144
|
||||||
|
$sContentType = 'application/pdf';
|
||||||
|
} else if ($sContentTypeIn) {
|
||||||
|
$sContentType = $sContentTypeIn;
|
||||||
|
} else if (!$sContentType) {
|
||||||
|
$sContentType = $sFileName ? \MailSo\Base\Utils::MimeContentType($sFileName) : 'text/plain';
|
||||||
|
}
|
||||||
|
|
||||||
if (!$bDownload)
|
if (!$bDownload)
|
||||||
{
|
{
|
||||||
|
$bDetectImageOrientation = !!$self->Config()->Get('labs', 'detect_image_exif_orientation', false);
|
||||||
if ($bThumbnail)
|
if ($bThumbnail)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$oImage = static::loadImage($rResource, $bDetectImageOrientation, 60);
|
$oImage = static::loadImage($rResource, $bDetectImageOrientation, 60);
|
||||||
\header('Content-Disposition: inline; '.
|
\header('Content-Disposition: inline; '.
|
||||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut.'_thumb60x60.png')));
|
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileName.'_thumb60x60.png')));
|
||||||
$oImage->show('png');
|
$oImage->show('png');
|
||||||
// $oImage->show('webp'); // Little Britain: "Safari says NO"
|
// $oImage->show('webp'); // Little Britain: "Safari says NO"
|
||||||
}
|
}
|
||||||
|
|
@ -236,16 +224,15 @@ trait Raw
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
else if ($bDetectImageOrientation &&
|
else if ($bDetectImageOrientation &&
|
||||||
\in_array($sContentTypeOut, array('image/png', 'image/jpeg', 'image/jpg', 'image/webp')))
|
\in_array($sContentType, array('image/png', 'image/jpeg', 'image/jpg', 'image/webp')))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$oImage = static::loadImage($rResource, $bDetectImageOrientation);
|
$oImage = static::loadImage($rResource, $bDetectImageOrientation);
|
||||||
\header('Content-Disposition: inline; '.
|
\header('Content-Disposition: inline; '.
|
||||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)));
|
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileName)));
|
||||||
$oImage->show();
|
$oImage->show();
|
||||||
// $oImage->show('webp'); // Little Britain: "Safari says NO"
|
// $oImage->show('webp'); // Little Britain: "Safari says NO"
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
catch (\Throwable $oException)
|
catch (\Throwable $oException)
|
||||||
{
|
{
|
||||||
|
|
@ -256,9 +243,9 @@ trait Raw
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\headers_sent()) {
|
if (!\headers_sent()) {
|
||||||
\header('Content-Type: '.$sContentTypeOut);
|
\header('Content-Type: '.$sContentType);
|
||||||
\header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '.
|
\header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '.
|
||||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)));
|
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileName)));
|
||||||
|
|
||||||
\header('Accept-Ranges: bytes');
|
\header('Accept-Ranges: bytes');
|
||||||
\header('Content-Transfer-Encoding: binary');
|
\header('Content-Transfer-Encoding: binary');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
|
Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/)
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ are permitted provided that the following conditions are met:
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
* Neither the name of SabreDAV nor the names of its contributors
|
* Neither the name Sabre nor the names of its contributors
|
||||||
may be used to endorse or promote products derived from this software
|
may be used to endorse or promote products derived from this software
|
||||||
without specific prior written permission.
|
without specific prior written permission.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class generates birthday calendars.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Dominik Tobschall (http://tobschall.de/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class BirthdayCalendarGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Input objects.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $objects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default year.
|
||||||
|
* Used for dates without a year.
|
||||||
|
*/
|
||||||
|
const DEFAULT_YEAR = 2000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output format for the SUMMARY.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $format = '%1$s\'s Birthday';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the generator.
|
||||||
|
*
|
||||||
|
* Check the setTimeRange and setObjects methods for details about the
|
||||||
|
* arguments.
|
||||||
|
*
|
||||||
|
* @param mixed $objects
|
||||||
|
*/
|
||||||
|
public function __construct($objects = null)
|
||||||
|
{
|
||||||
|
if ($objects) {
|
||||||
|
$this->setObjects($objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input objects.
|
||||||
|
*
|
||||||
|
* You must either supply a vCard as a string or as a Component/VCard object.
|
||||||
|
* It's also possible to supply an array of strings or objects.
|
||||||
|
*
|
||||||
|
* @param mixed $objects
|
||||||
|
*/
|
||||||
|
public function setObjects($objects)
|
||||||
|
{
|
||||||
|
if (!is_array($objects)) {
|
||||||
|
$objects = [$objects];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->objects = [];
|
||||||
|
foreach ($objects as $object) {
|
||||||
|
if (is_string($object)) {
|
||||||
|
$vObj = Reader::read($object);
|
||||||
|
if (!$vObj instanceof Component\VCard) {
|
||||||
|
throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->objects[] = $vObj;
|
||||||
|
} elseif ($object instanceof Component\VCard) {
|
||||||
|
$this->objects[] = $object;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the output format for the SUMMARY.
|
||||||
|
*
|
||||||
|
* @param string $format
|
||||||
|
*/
|
||||||
|
public function setFormat($format)
|
||||||
|
{
|
||||||
|
$this->format = $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the input data and returns a VCALENDAR.
|
||||||
|
*
|
||||||
|
* @return Component/VCalendar
|
||||||
|
*/
|
||||||
|
public function getResult()
|
||||||
|
{
|
||||||
|
$calendar = new VCalendar();
|
||||||
|
|
||||||
|
foreach ($this->objects as $object) {
|
||||||
|
// Skip if there is no BDAY property.
|
||||||
|
if (!$object->select('BDAY')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've seen clients (ez-vcard) putting "BDAY:" properties
|
||||||
|
// without a value into vCards. If we come across those, we'll
|
||||||
|
// skip them.
|
||||||
|
if (empty($object->BDAY->getValue())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're always converting to vCard 4.0 so we can rely on the
|
||||||
|
// VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
|
||||||
|
$object = $object->convert(Document::VCARD40);
|
||||||
|
|
||||||
|
// Skip if the card has no FN property.
|
||||||
|
if (!isset($object->FN)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the BDAY property is not of the right type.
|
||||||
|
if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if we can't parse the BDAY value.
|
||||||
|
try {
|
||||||
|
$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
|
||||||
|
} catch (InvalidDataException $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a year if it's not set.
|
||||||
|
$unknownYear = false;
|
||||||
|
|
||||||
|
if (!$dateParts['year']) {
|
||||||
|
$object->BDAY = self::DEFAULT_YEAR.'-'.$dateParts['month'].'-'.$dateParts['date'];
|
||||||
|
|
||||||
|
$unknownYear = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create event.
|
||||||
|
$event = $calendar->add('VEVENT', [
|
||||||
|
'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
|
||||||
|
'DTSTART' => new \DateTime($object->BDAY->getValue()),
|
||||||
|
'RRULE' => 'FREQ=YEARLY',
|
||||||
|
'TRANSP' => 'TRANSPARENT',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// add VALUE=date
|
||||||
|
$event->DTSTART['VALUE'] = 'DATE';
|
||||||
|
|
||||||
|
// Add X-SABRE-BDAY property.
|
||||||
|
if ($unknownYear) {
|
||||||
|
$event->add('X-SABRE-BDAY', 'BDAY', [
|
||||||
|
'X-SABRE-VCARD-UID' => $object->UID->getValue(),
|
||||||
|
'X-SABRE-VCARD-FN' => $object->FN->getValue(),
|
||||||
|
'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$event->add('X-SABRE-BDAY', 'BDAY', [
|
||||||
|
'X-SABRE-VCARD-UID' => $object->UID->getValue(),
|
||||||
|
'X-SABRE-VCARD-FN' => $object->FN->getValue(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $calendar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,83 +2,82 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
use
|
use InvalidArgumentException;
|
||||||
InvalidArgumentException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the CLI interface for sabre-vobject.
|
* This is the CLI interface for sabre-vobject.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Cli {
|
class Cli
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* No output
|
* No output.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $quiet = false;
|
protected $quiet = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Help display
|
* Help display.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $showHelp = false;
|
protected $showHelp = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wether to spit out 'mimedir' or 'json' format.
|
* Whether to spit out 'mimedir' or 'json' format.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $format;
|
protected $format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON pretty print
|
* JSON pretty print.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $pretty;
|
protected $pretty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source file
|
* Source file.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $inputPath;
|
protected $inputPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destination file
|
* Destination file.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $outputPath;
|
protected $outputPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* output stream
|
* output stream.
|
||||||
*
|
*
|
||||||
* @var resource
|
* @var resource
|
||||||
*/
|
*/
|
||||||
protected $stdout;
|
protected $stdout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* stdin
|
* stdin.
|
||||||
*
|
*
|
||||||
* @var resource
|
* @var resource
|
||||||
*/
|
*/
|
||||||
protected $stdin;
|
protected $stdin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* stderr
|
* stderr.
|
||||||
*
|
*
|
||||||
* @var resource
|
* @var resource
|
||||||
*/
|
*/
|
||||||
protected $stderr;
|
protected $stderr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input format (one of json or mimedir)
|
* Input format (one of json or mimedir).
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -92,66 +91,59 @@ class Cli {
|
||||||
protected $forgiving = false;
|
protected $forgiving = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function
|
* Main function.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function main(array $argv) {
|
public 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.
|
||||||
|
|
||||||
if (!$this->stderr) {
|
if (!$this->stderr) {
|
||||||
$this->stderr = STDERR;
|
$this->stderr = fopen('php://stderr', 'w');
|
||||||
}
|
}
|
||||||
if (!$this->stdout) {
|
if (!$this->stdout) {
|
||||||
$this->stdout = STDOUT;
|
$this->stdout = fopen('php://stdout', 'w');
|
||||||
}
|
}
|
||||||
if (!$this->stdin) {
|
if (!$this->stdin) {
|
||||||
$this->stdin = STDIN;
|
$this->stdin = fopen('php://stdin', 'r');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @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':
|
||||||
|
|
@ -160,11 +152,12 @@ class Cli {
|
||||||
|
|
||||||
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) {
|
||||||
$this->pretty = true;
|
$this->pretty = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'forgiving':
|
case 'forgiving':
|
||||||
$this->forgiving = true;
|
$this->forgiving = true;
|
||||||
|
|
@ -186,28 +179,25 @@ class Cli {
|
||||||
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 (count($positional) === 1) {
|
if (1 === count($positional)) {
|
||||||
throw new InvalidArgumentException('Inputfile is a required argument');
|
throw new InvalidArgumentException('Inputfile is a required argument');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,13 +205,13 @@ class Cli {
|
||||||
throw new InvalidArgumentException('Too many arguments');
|
throw new InvalidArgumentException('Too many arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($positional[0], array('validate','repair','convert','color'))) {
|
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
|
||||||
throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
|
throw new InvalidArgumentException('Unknown 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,74 +220,73 @@ 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 (substr($this->inputPath,-5)==='.json') {
|
if ('.json' === substr($this->inputPath, -5)) {
|
||||||
$this->inputFormat = 'json';
|
$this->inputFormat = 'json';
|
||||||
} else {
|
} else {
|
||||||
$this->inputFormat = 'mimedir';
|
$this->inputFormat = 'mimedir';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$this->format) {
|
if (!$this->format) {
|
||||||
if (substr($this->outputPath,-5)==='.json') {
|
if ('.json' === substr($this->outputPath, -5)) {
|
||||||
$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 ($returnCode!==0) $realCode = $returnCode;
|
if (0 !== $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.');
|
||||||
$this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
|
// Only PHP 5.4 and up
|
||||||
|
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||||
|
$this->log($this->colorize('green', ' --pretty ').'json pretty-print.');
|
||||||
|
}
|
||||||
$this->log('');
|
$this->log('');
|
||||||
$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 debugging.');
|
||||||
$this->log(<<<HELP
|
$this->log(
|
||||||
|
<<<HELP
|
||||||
|
|
||||||
If source_file is set as '-', STDIN will be used.
|
If source_file is set as '-', STDIN will be used.
|
||||||
If output_file is omitted, STDOUT will be used.
|
If output_file is omitted, STDOUT will be used.
|
||||||
|
|
@ -313,91 +302,98 @@ 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');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a VObject file
|
* Validates a VObject file.
|
||||||
*
|
*
|
||||||
* @param Component $vObj
|
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function validate($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 = [
|
||||||
|
1 => 'REPAIRED',
|
||||||
|
2 => 'WARNING',
|
||||||
|
3 => 'ERROR',
|
||||||
|
];
|
||||||
$returnCode = 2;
|
$returnCode = 2;
|
||||||
foreach ($warnings as $warn) {
|
foreach ($warnings as $warn) {
|
||||||
|
$extra = '';
|
||||||
$this->log(" " . $warn['message']);
|
if ($warn['node'] instanceof Property) {
|
||||||
|
$extra = ' (property: "'.$warn['node']->name.'")';
|
||||||
|
}
|
||||||
|
$this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $returnCode;
|
return $returnCode;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repairs a VObject file
|
* Repairs a VObject file.
|
||||||
*
|
*
|
||||||
* @param Component $vObj
|
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function repair($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 {
|
||||||
foreach($warnings as $warn) {
|
$levels = [
|
||||||
|
1 => 'REPAIRED',
|
||||||
|
2 => 'WARNING',
|
||||||
|
3 => 'ERROR',
|
||||||
|
];
|
||||||
$returnCode = 2;
|
$returnCode = 2;
|
||||||
$this->log(" " . $warn['message']);
|
foreach ($warnings as $warn) {
|
||||||
|
$extra = '';
|
||||||
|
if ($warn['node'] instanceof Property) {
|
||||||
|
$extra = ' (property: "'.$warn['node']->name.'")';
|
||||||
|
}
|
||||||
|
$this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
fwrite($this->stdout, $vObj->serialize());
|
fwrite($this->stdout, $vObj->serialize());
|
||||||
|
|
||||||
return $returnCode;
|
return $returnCode;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a vObject file to a new format.
|
* Converts a vObject file to a new format.
|
||||||
*
|
*
|
||||||
* @param Component $vObj
|
* @param Component $vObj
|
||||||
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function convert($vObj) {
|
protected function convert($vObj)
|
||||||
|
{
|
||||||
$json = false;
|
$json = false;
|
||||||
$convertVersion = null;
|
$convertVersion = null;
|
||||||
$forceInput = null;
|
$forceInput = null;
|
||||||
|
|
@ -405,7 +401,7 @@ HELP
|
||||||
switch ($this->format) {
|
switch ($this->format) {
|
||||||
case 'json':
|
case 'json':
|
||||||
$json = true;
|
$json = true;
|
||||||
if ($vObj->name === 'VCARD') {
|
if ('VCARD' === $vObj->name) {
|
||||||
$convertVersion = Document::VCARD40;
|
$convertVersion = Document::VCARD40;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -432,7 +428,6 @@ HELP
|
||||||
case 'vcard40':
|
case 'vcard40':
|
||||||
$convertVersion = Document::VCARD40;
|
$convertVersion = Document::VCARD40;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($forceInput && $vObj->name !== $forceInput) {
|
if ($forceInput && $vObj->name !== $forceInput) {
|
||||||
|
|
@ -452,30 +447,28 @@ HELP
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Colorizes a file
|
* Colorizes a file.
|
||||||
*
|
*
|
||||||
* @param Component $vObj
|
* @param Component $vObj
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
protected function color($vObj) {
|
protected function color($vObj)
|
||||||
|
{
|
||||||
fwrite($this->stdout, $this->serializeComponent($vObj));
|
fwrite($this->stdout, $this->serializeComponent($vObj));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an ansi color string for a color name.
|
* Returns an ansi color string for a color name.
|
||||||
*
|
*
|
||||||
* @param string $color
|
* @param string $color
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function colorize($color, $str, $resetTo = 'default') {
|
protected function colorize($color, $str, $resetTo = 'default')
|
||||||
|
{
|
||||||
$colors = array(
|
$colors = [
|
||||||
'cyan' => '1;36',
|
'cyan' => '1;36',
|
||||||
'red' => '1;31',
|
'red' => '1;31',
|
||||||
'yellow' => '1;33',
|
'yellow' => '1;33',
|
||||||
|
|
@ -483,9 +476,9 @@ HELP
|
||||||
'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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -493,16 +486,14 @@ 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");
|
||||||
|
|
@ -514,54 +505,58 @@ HELP
|
||||||
*
|
*
|
||||||
* A higher score means the item will be lower in the list.
|
* A higher score means the item will be lower in the list.
|
||||||
* To avoid score collisions, each "score category" has a reasonable
|
* To avoid score collisions, each "score category" has a reasonable
|
||||||
* space to accomodate elements. The $key is added to the $score to
|
* space to accommodate 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 ($array[$key]->name === 'VTIMEZONE') {
|
if ('VTIMEZONE' === $array[$key]->name) {
|
||||||
$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 ($array[$key]->name === 'VERSION') {
|
if ('VERSION' === $array[$key]->name) {
|
||||||
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$tmp = $vObj->children;
|
$children = $vObj->children();
|
||||||
uksort($vObj->children, function($a, $b) use ($sortScore, $tmp) {
|
$tmp = $children;
|
||||||
|
uksort(
|
||||||
|
$children,
|
||||||
|
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($vObj->children as $child) {
|
|
||||||
if ($child instanceof Component) {
|
if ($child instanceof Component) {
|
||||||
$this->serializeComponent($child);
|
$this->serializeComponent($child);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -572,39 +567,29 @@ 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
|
|
||||||
* @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', '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$str = '';
|
|
||||||
$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
|
||||||
|
|
@ -624,42 +609,41 @@ HELP
|
||||||
$this->cWrite('red', ',');
|
$this->cWrite('red', ',');
|
||||||
}
|
}
|
||||||
|
|
||||||
$subPart = strtr($subPart, array(
|
$subPart = strtr(
|
||||||
|
$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
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected function parseArguments(array $argv) {
|
protected function parseArguments(array $argv)
|
||||||
|
{
|
||||||
$positional = array();
|
$positional = [];
|
||||||
$options = array();
|
$options = [];
|
||||||
|
|
||||||
for($ii=0; $ii < count($argv); $ii++) {
|
|
||||||
|
|
||||||
|
for ($ii = 0; $ii < count($argv); ++$ii) {
|
||||||
// Skipping the first argument.
|
// Skipping the first argument.
|
||||||
if ($ii===0) continue;
|
if (0 === $ii) {
|
||||||
|
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;
|
||||||
|
|
@ -667,39 +651,34 @@ 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 array($options, $positional);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $parser;
|
protected $parser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the input file
|
* Reads the input file.
|
||||||
*
|
*
|
||||||
* @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 ($this->inputFormat === 'mimedir') {
|
if ('mimedir' === $this->inputFormat) {
|
||||||
$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));
|
||||||
|
|
@ -707,24 +686,20 @@ 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 ($color!=='default') {
|
if ('default' !== $color) {
|
||||||
$msg = $this->colorize($color, $msg);
|
$msg = $this->colorize($color, $msg);
|
||||||
}
|
}
|
||||||
fwrite($this->stderr, $msg."\n");
|
fwrite($this->stderr, $msg."\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component
|
* Component.
|
||||||
*
|
*
|
||||||
* A component represents a group of properties, such as VCALENDAR, VEVENT, or
|
* A component represents a group of properties, such as VCALENDAR, VEVENT, or
|
||||||
* VCARD.
|
* VCARD.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Component extends Node {
|
class Component extends Node
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Component name.
|
* Component name.
|
||||||
*
|
*
|
||||||
|
|
@ -28,7 +30,7 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $children = array();
|
protected $children = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new component.
|
* Creates a new component.
|
||||||
|
|
@ -41,33 +43,48 @@ class Component extends Node {
|
||||||
* an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
|
* an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
|
||||||
* ensure that this does not happen, set $defaults to false.
|
* ensure that this does not happen, set $defaults to false.
|
||||||
*
|
*
|
||||||
* @param Document $root
|
* @param string $name such as VCALENDAR, VEVENT
|
||||||
* @param string $name such as VCALENDAR, VEVENT.
|
|
||||||
* @param array $children
|
|
||||||
* @param bool $defaults
|
* @param bool $defaults
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __construct(Document $root, $name, array $children = array(), $defaults = true) {
|
public function __construct(Document $root, $name, array $children = [], $defaults = true)
|
||||||
|
{
|
||||||
$this->name = strtoupper($name);
|
$this->name = strtoupper($name);
|
||||||
$this->root = $root;
|
$this->root = $root;
|
||||||
|
|
||||||
if ($defaults) {
|
if ($defaults) {
|
||||||
$children = array_merge($this->getDefaults(), $children);
|
// This is a terribly convoluted way to do this, but this ensures
|
||||||
|
// that the order of properties as they are specified in both
|
||||||
|
// defaults and the childrens list, are inserted in the object in a
|
||||||
|
// natural way.
|
||||||
|
$list = $this->getDefaults();
|
||||||
|
$nodes = [];
|
||||||
|
foreach ($children as $key => $value) {
|
||||||
|
if ($value instanceof Node) {
|
||||||
|
if (isset($list[$value->name])) {
|
||||||
|
unset($list[$value->name]);
|
||||||
}
|
}
|
||||||
|
$nodes[] = $value;
|
||||||
|
} else {
|
||||||
|
$list[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($list as $key => $value) {
|
||||||
|
$this->add($key, $value);
|
||||||
|
}
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
$this->add($node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
foreach ($children as $k => $child) {
|
foreach ($children as $k => $child) {
|
||||||
if ($child instanceof Node) {
|
if ($child instanceof 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -77,37 +94,36 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* add(Component $comp) // Adds a new component
|
* add(Component $comp) // Adds a new component
|
||||||
* add(Property $prop) // Adds a new property
|
* add(Property $prop) // Adds a new property
|
||||||
* add($name, $value, array $parameters = array()) // Adds a new property
|
* add($name, $value, array $parameters = []) // Adds a new property
|
||||||
* add($name, array $children = array()) // Adds a new component
|
* add($name, array $children = []) // Adds a new component
|
||||||
* by name.
|
* by name.
|
||||||
*
|
*
|
||||||
* @return Node
|
* @return Node
|
||||||
*/
|
*/
|
||||||
public function add($a1, $a2 = null, $a3 = null) {
|
public function add()
|
||||||
|
{
|
||||||
|
$arguments = func_get_args();
|
||||||
|
|
||||||
if ($a1 instanceof Node) {
|
if ($arguments[0] instanceof Node) {
|
||||||
if (!is_null($a2)) {
|
if (isset($arguments[1])) {
|
||||||
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
|
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
|
||||||
}
|
}
|
||||||
$a1->parent = $this;
|
$arguments[0]->parent = $this;
|
||||||
$this->children[] = $a1;
|
$newNode = $arguments[0];
|
||||||
|
} elseif (is_string($arguments[0])) {
|
||||||
return $a1;
|
$newNode = call_user_func_array([$this->root, 'create'], $arguments);
|
||||||
|
|
||||||
} elseif(is_string($a1)) {
|
|
||||||
|
|
||||||
$item = $this->root->create($a1, $a2, $a3);
|
|
||||||
$item->parent = $this;
|
|
||||||
$this->children[] = $item;
|
|
||||||
|
|
||||||
return $item;
|
|
||||||
|
|
||||||
} 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;
|
||||||
|
if (isset($this->children[$name])) {
|
||||||
|
$this->children[$name][] = $newNode;
|
||||||
|
} else {
|
||||||
|
$this->children[$name] = [$newNode];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,43 +134,51 @@ class Component extends Node {
|
||||||
* pass an instance of a property or component, in which case only that
|
* pass an instance of a property or component, in which case only that
|
||||||
* exact item will be removed.
|
* exact item will be removed.
|
||||||
*
|
*
|
||||||
* The removed item will be returned. In case there were more than 1 items
|
* @param string|Property|Component $item
|
||||||
* removed, only the last one will be returned.
|
|
||||||
*
|
|
||||||
* @param mixed $item
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function remove($item) {
|
public function remove($item)
|
||||||
|
{
|
||||||
if (is_string($item)) {
|
if (is_string($item)) {
|
||||||
$children = $this->select($item);
|
// If there's no dot in the name, it's an exact property name and
|
||||||
foreach($children as $k=>$child) {
|
// we can just wipe out all those properties.
|
||||||
unset($this->children[$k]);
|
//
|
||||||
|
if (false === strpos($item, '.')) {
|
||||||
|
unset($this->children[strtoupper($item)]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If there was a dot, we need to ask select() to help us out and
|
||||||
|
// then we just call remove recursively.
|
||||||
|
foreach ($this->select($item) as $child) {
|
||||||
|
$this->remove($child);
|
||||||
}
|
}
|
||||||
return $child;
|
|
||||||
} else {
|
} else {
|
||||||
foreach($this->children as $k => $child) {
|
foreach ($this->select($item->name) as $k => $child) {
|
||||||
if ($child === $item) {
|
if ($child === $item) {
|
||||||
unset($this->children[$k]);
|
unset($this->children[$item->name][$k]);
|
||||||
return $child;
|
|
||||||
|
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');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterable list of children
|
* Returns a flat list of all the properties and components in this
|
||||||
|
* component.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function children() {
|
public function children()
|
||||||
|
{
|
||||||
return $this->children;
|
$result = [];
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
$result = array_merge($result, $childGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,17 +187,19 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getComponents() {
|
public function getComponents()
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
$result = array();
|
foreach ($this->children as $childGroup) {
|
||||||
foreach($this->children as $child) {
|
foreach ($childGroup as $child) {
|
||||||
if ($child instanceof Component) {
|
if ($child instanceof self) {
|
||||||
$result[] = $child;
|
$result[] = $child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -186,36 +212,50 @@ class Component extends Node {
|
||||||
* string ("HOME.EMAIL"). If you want to search on a specific property that
|
* string ("HOME.EMAIL"). If you want to search on a specific property that
|
||||||
* has not been assigned a group, specify ".EMAIL".
|
* has not been assigned a group, specify ".EMAIL".
|
||||||
*
|
*
|
||||||
* Keys are retained from the 'children' array, which may be confusing in
|
|
||||||
* certain cases.
|
|
||||||
*
|
|
||||||
* @param string $name
|
* @param string $name
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function select($name) {
|
public function select($name)
|
||||||
|
{
|
||||||
$group = null;
|
$group = null;
|
||||||
$name = strtoupper($name);
|
$name = strtoupper($name);
|
||||||
if (strpos($name,'.')!==false) {
|
if (false !== strpos($name, '.')) {
|
||||||
list($group, $name) = explode('.', $name, 2);
|
list($group, $name) = explode('.', $name, 2);
|
||||||
}
|
}
|
||||||
|
if ('' === $name) {
|
||||||
$result = array();
|
$name = null;
|
||||||
foreach($this->children as $key=>$child) {
|
|
||||||
|
|
||||||
if (
|
|
||||||
strtoupper($child->name) === $name &&
|
|
||||||
(is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
|
|
||||||
) {
|
|
||||||
|
|
||||||
$result[$key] = $child;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset($result);
|
if (!is_null($name)) {
|
||||||
|
$result = isset($this->children[$name]) ? $this->children[$name] : [];
|
||||||
|
|
||||||
|
if (is_null($group)) {
|
||||||
return $result;
|
return $result;
|
||||||
|
} else {
|
||||||
|
// If we have a group filter as well, we need to narrow it down
|
||||||
|
// more.
|
||||||
|
return array_filter(
|
||||||
|
$result,
|
||||||
|
function ($child) use ($group) {
|
||||||
|
return $child instanceof Property && strtoupper($child->group) === $group;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got to this point, it means there was no 'name' specified for
|
||||||
|
// searching, implying that this is a group-only search.
|
||||||
|
$result = [];
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
foreach ($childGroup as $child) {
|
||||||
|
if ($child instanceof Property && strtoupper($child->group) === $group) {
|
||||||
|
$result[] = $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -223,9 +263,9 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function serialize() {
|
public 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.
|
||||||
|
|
@ -234,85 +274,138 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* A higher score means the item will be lower in the list.
|
* A higher score means the item will be lower in the list.
|
||||||
* To avoid score collisions, each "score category" has a reasonable
|
* To avoid score collisions, each "score category" has a reasonable
|
||||||
* space to accomodate elements. The $key is added to the $score to
|
* space to accommodate 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 ($array[$key]->name === 'VTIMEZONE') {
|
if ('VTIMEZONE' === $array[$key]->name) {
|
||||||
$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 ($array[$key]->name === 'VERSION') {
|
if ('VERSION' === $array[$key]->name) {
|
||||||
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$tmp = $this->children;
|
$children = $this->children();
|
||||||
uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
|
$tmp = $children;
|
||||||
|
uksort(
|
||||||
|
$children,
|
||||||
|
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) {
|
||||||
|
$str .= $child->serialize();
|
||||||
foreach($this->children as $child) $str.=$child->serialize();
|
}
|
||||||
$str.= "END:" . $this->name . "\r\n";
|
$str .= 'END:'.$this->name."\r\n";
|
||||||
|
|
||||||
return $str;
|
return $str;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns an array, with the representation as it should be
|
* This method returns an array, with the representation as it should be
|
||||||
* encoded in json. This is used to create jCard or jCal documents.
|
* encoded in JSON. This is used to create jCard or jCal documents.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
$components = [];
|
||||||
|
$properties = [];
|
||||||
|
|
||||||
$components = array();
|
foreach ($this->children as $childGroup) {
|
||||||
$properties = array();
|
foreach ($childGroup as $child) {
|
||||||
|
if ($child instanceof self) {
|
||||||
foreach($this->children as $child) {
|
|
||||||
if ($child instanceof Component) {
|
|
||||||
$components[] = $child->jsonSerialize();
|
$components[] = $child->jsonSerialize();
|
||||||
} else {
|
} else {
|
||||||
$properties[] = $child->jsonSerialize();
|
$properties[] = $child->jsonSerialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
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
|
||||||
|
* xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$components = [];
|
||||||
|
$properties = [];
|
||||||
|
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
foreach ($childGroup as $child) {
|
||||||
|
if ($child instanceof self) {
|
||||||
|
$components[] = $child;
|
||||||
|
} else {
|
||||||
|
$properties[] = $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->startElement(strtolower($this->name));
|
||||||
|
|
||||||
|
if (!empty($properties)) {
|
||||||
|
$writer->startElement('properties');
|
||||||
|
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
$property->xmlSerialize($writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($components)) {
|
||||||
|
$writer->startElement('components');
|
||||||
|
|
||||||
|
foreach ($components as $component) {
|
||||||
|
$component->xmlSerialize($writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -320,10 +413,9 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getDefaults() {
|
protected function getDefaults()
|
||||||
|
{
|
||||||
return array();
|
return [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Magic property accessors {{{ */
|
/* Magic property accessors {{{ */
|
||||||
|
|
@ -339,37 +431,43 @@ class Component extends Node {
|
||||||
* $event = $calendar->VEVENT;
|
* $event = $calendar->VEVENT;
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return Property
|
*
|
||||||
|
* @return Property|null
|
||||||
*/
|
*/
|
||||||
public function __get($name) {
|
public function __get($name)
|
||||||
|
{
|
||||||
$matches = $this->select($name);
|
if ('children' === $name) {
|
||||||
if (count($matches)===0) {
|
throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
$firstMatch = current($matches);
|
|
||||||
/** @var $firstMatch Property */
|
|
||||||
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
|
||||||
return $firstMatch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$matches = $this->select($name);
|
||||||
|
if (0 === count($matches)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$firstMatch = current($matches);
|
||||||
|
/* @var $firstMatch Property */
|
||||||
|
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
||||||
|
|
||||||
|
return $firstMatch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method checks if a sub-element with the specified name exists.
|
* This method checks if a sub-element with the specified name exists.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function __isset($name) {
|
public function __isset($name)
|
||||||
|
{
|
||||||
$matches = $this->select($name);
|
$matches = $this->select($name);
|
||||||
return count($matches)>0;
|
|
||||||
|
|
||||||
|
return count($matches) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using the setter method you can add properties or subcomponents
|
* Using the setter method you can add properties or subcomponents.
|
||||||
*
|
*
|
||||||
* You can either pass a Component, Property
|
* You can either pass a Component, Property
|
||||||
* object, or a string to automatically create a Property.
|
* object, or a string to automatically create a Property.
|
||||||
|
|
@ -379,32 +477,16 @@ class Component extends Node {
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __set($name, $value) {
|
public function __set($name, $value)
|
||||||
|
{
|
||||||
$matches = $this->select($name);
|
$name = strtoupper($name);
|
||||||
$overWrite = count($matches)?key($matches):null;
|
$this->remove($name);
|
||||||
|
if ($value instanceof self || $value instanceof Property) {
|
||||||
if ($value instanceof Component || $value instanceof Property) {
|
$this->add($value);
|
||||||
$value->parent = $this;
|
|
||||||
if (!is_null($overWrite)) {
|
|
||||||
$this->children[$overWrite] = $value;
|
|
||||||
} else {
|
} else {
|
||||||
$this->children[] = $value;
|
$this->add($name, $value);
|
||||||
}
|
}
|
||||||
} elseif (is_scalar($value) || is_array($value) || is_null($value)) {
|
|
||||||
$property = $this->root->create($name,$value);
|
|
||||||
$property->parent = $this;
|
|
||||||
if (!is_null($overWrite)) {
|
|
||||||
$this->children[$overWrite] = $property;
|
|
||||||
} else {
|
|
||||||
$this->children[] = $property;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new \InvalidArgumentException('You must pass a \\Sabre\\VObject\\Component, \\Sabre\\VObject\\Property or scalar type');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -412,18 +494,10 @@ class Component extends Node {
|
||||||
* specified name.
|
* specified name.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __unset($name) {
|
public function __unset($name)
|
||||||
|
{
|
||||||
$matches = $this->select($name);
|
$this->remove($name);
|
||||||
foreach($matches as $k=>$child) {
|
|
||||||
|
|
||||||
unset($this->children[$k]);
|
|
||||||
$child->parent = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
@ -431,43 +505,168 @@ 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() {
|
public function __clone()
|
||||||
|
{
|
||||||
foreach($this->children as $key=>$child) {
|
foreach ($this->children as $childName => $childGroup) {
|
||||||
$this->children[$key] = clone $child;
|
foreach ($childGroup as $key => $child) {
|
||||||
$this->children[$key]->parent = $this;
|
$clonedChild = clone $child;
|
||||||
|
$clonedChild->parent = $this;
|
||||||
|
$clonedChild->root = $this->root;
|
||||||
|
$this->children[$childName][$key] = $clonedChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* It is also possible to specify defaults and severity levels for
|
||||||
|
* violating the rule.
|
||||||
|
*
|
||||||
|
* See the VEVENT implementation for getValidationRules for a more complex
|
||||||
|
* example.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the node for correctness.
|
* Validates the node for correctness.
|
||||||
*
|
*
|
||||||
* The following options are supported:
|
* The following options are supported:
|
||||||
* - Node::REPAIR - If something is broken, an automatic repair may
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
* be attempted.
|
* Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
|
||||||
|
* Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
|
||||||
*
|
*
|
||||||
* An array is returned with warnings.
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
*
|
*
|
||||||
* Every item in the array has the following properties:
|
* * level - problem level.
|
||||||
* * level - (number between 1 and 3 with severity information)
|
* * message - A human-readable string describing the issue.
|
||||||
* * message - (human readable message)
|
* * node - A reference to the problematic node.
|
||||||
* * node - (reference to the offending node)
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on).
|
||||||
|
* 2 - A warning.
|
||||||
|
* 3 - An error.
|
||||||
*
|
*
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$rules = $this->getValidationRules();
|
||||||
|
$defaults = $this->getDefaults();
|
||||||
|
|
||||||
$result = array();
|
$propertyCounters = [];
|
||||||
foreach($this->children as $child) {
|
|
||||||
$result = array_merge($result, $child->validate($options));
|
$messages = [];
|
||||||
|
|
||||||
|
foreach ($this->children() as $child) {
|
||||||
|
$name = strtoupper($child->name);
|
||||||
|
if (!isset($propertyCounters[$name])) {
|
||||||
|
$propertyCounters[$name] = 1;
|
||||||
|
} else {
|
||||||
|
++$propertyCounters[$name];
|
||||||
}
|
}
|
||||||
return $result;
|
$messages = array_merge($messages, $child->validate($options));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($rules as $propName => $rule) {
|
||||||
|
switch ($rule) {
|
||||||
|
case '0':
|
||||||
|
if (isset($propertyCounters[$propName])) {
|
||||||
|
$messages[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => $propName.' MUST NOT appear in a '.$this->name.' component',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '1':
|
||||||
|
if (!isset($propertyCounters[$propName]) || 1 !== $propertyCounters[$propName]) {
|
||||||
|
$repaired = false;
|
||||||
|
if ($options & self::REPAIR && isset($defaults[$propName])) {
|
||||||
|
$this->add($propName, $defaults[$propName]);
|
||||||
|
$repaired = true;
|
||||||
|
}
|
||||||
|
$messages[] = [
|
||||||
|
'level' => $repaired ? 1 : 3,
|
||||||
|
'message' => $propName.' MUST appear exactly once in a '.$this->name.' component',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
|
||||||
|
$messages[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => $propName.' MUST appear at least once in a '.$this->name.' component',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
|
||||||
|
$level = 3;
|
||||||
|
|
||||||
|
// We try to repair the same property appearing multiple times with the exact same value
|
||||||
|
// by removing the duplicates and keeping only one property
|
||||||
|
if ($options & self::REPAIR) {
|
||||||
|
$properties = array_unique($this->select($propName), SORT_REGULAR);
|
||||||
|
|
||||||
|
if (1 === count($properties)) {
|
||||||
|
$this->remove($propName);
|
||||||
|
$this->add($properties[0]);
|
||||||
|
|
||||||
|
$level = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[] = [
|
||||||
|
'level' => $level,
|
||||||
|
'message' => $propName.' MUST NOT appear more than once in a '.$this->name.' component',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 up.
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
parent::destroy();
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
foreach ($childGroup as $child) {
|
||||||
|
$child->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->children = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Available sub-component.
|
||||||
|
*
|
||||||
|
* This component adds functionality to a component, specific for AVAILABLE
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Ivan Enderlin
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Available extends VObject\Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the 'effective start' and 'effective end' of this VAVAILABILITY
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* We use the DTSTART and DTEND or DURATION to determine this.
|
||||||
|
*
|
||||||
|
* The returned value is an array containing DateTimeImmutable instances.
|
||||||
|
* If either the start or end is 'unbounded' its value will be null
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getEffectiveStartEnd()
|
||||||
|
{
|
||||||
|
$effectiveStart = $this->DTSTART->getDateTime();
|
||||||
|
if (isset($this->DTEND)) {
|
||||||
|
$effectiveEnd = $this->DTEND->getDateTime();
|
||||||
|
} else {
|
||||||
|
$effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$effectiveStart, $effectiveEnd];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTART' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
|
||||||
|
'DTEND' => '?',
|
||||||
|
'DURATION' => '?',
|
||||||
|
|
||||||
|
'CREATED' => '?',
|
||||||
|
'DESCRIPTION' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'RECURRENCE-ID' => '?',
|
||||||
|
'RRULE' => '?',
|
||||||
|
'SUMMARY' => '?',
|
||||||
|
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'CONTACT' => '*',
|
||||||
|
'EXDATE' => '*',
|
||||||
|
'RDATE' => '*',
|
||||||
|
|
||||||
|
'AVAILABLE' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
* Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
|
||||||
|
* Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on).
|
||||||
|
* 2 - A warning.
|
||||||
|
* 3 - An error.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$result = parent::validate($options);
|
||||||
|
|
||||||
|
if (isset($this->DTEND) && isset($this->DURATION)) {
|
||||||
|
$result[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'DTEND and DURATION cannot both be present',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,71 +1,74 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
use Sabre\VObject;
|
use Sabre\VObject;
|
||||||
|
use Sabre\VObject\InvalidDataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VAlarm component
|
* VAlarm component.
|
||||||
*
|
*
|
||||||
* This component contains some additional functionality specific for VALARMs.
|
* This component contains some additional functionality specific for VALARMs.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* This ignores repeated alarm, only the first trigger is returned.
|
* This ignores repeated alarm, only the first trigger is returned.
|
||||||
*
|
*
|
||||||
* @return DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getEffectiveTriggerTime() {
|
public function getEffectiveTriggerTime()
|
||||||
|
{
|
||||||
$trigger = $this->TRIGGER;
|
$trigger = $this->TRIGGER;
|
||||||
if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
|
if (!isset($trigger['VALUE']) || 'DURATION' === strtoupper($trigger['VALUE'])) {
|
||||||
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
||||||
$related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
|
$related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START';
|
||||||
|
|
||||||
$parentComponent = $this->parent;
|
$parentComponent = $this->parent;
|
||||||
if ($related === 'START') {
|
if ('START' === $related) {
|
||||||
|
if ('VTODO' === $parentComponent->name) {
|
||||||
if ($parentComponent->name === 'VTODO') {
|
|
||||||
$propName = 'DUE';
|
$propName = 'DUE';
|
||||||
} else {
|
} else {
|
||||||
$propName = 'DTSTART';
|
$propName = 'DTSTART';
|
||||||
}
|
}
|
||||||
|
|
||||||
$effectiveTrigger = clone $parentComponent->$propName->getDateTime();
|
$effectiveTrigger = $parentComponent->$propName->getDateTime();
|
||||||
$effectiveTrigger->add($triggerDuration);
|
$effectiveTrigger = $effectiveTrigger->add($triggerDuration);
|
||||||
} else {
|
} else {
|
||||||
if ($parentComponent->name === 'VTODO') {
|
if ('VTODO' === $parentComponent->name) {
|
||||||
$endProp = 'DUE';
|
$endProp = 'DUE';
|
||||||
} elseif ($parentComponent->name === 'VEVENT') {
|
} elseif ('VEVENT' === $parentComponent->name) {
|
||||||
$endProp = 'DTEND';
|
$endProp = 'DTEND';
|
||||||
} else {
|
} else {
|
||||||
throw new \LogicException('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');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($parentComponent->$endProp)) {
|
if (isset($parentComponent->$endProp)) {
|
||||||
$effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
|
$effectiveTrigger = $parentComponent->$endProp->getDateTime();
|
||||||
$effectiveTrigger->add($triggerDuration);
|
$effectiveTrigger = $effectiveTrigger->add($triggerDuration);
|
||||||
} elseif (isset($parentComponent->DURATION)) {
|
} elseif (isset($parentComponent->DURATION)) {
|
||||||
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
|
$effectiveTrigger = $parentComponent->DTSTART->getDateTime();
|
||||||
$duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
|
$duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
|
||||||
$effectiveTrigger->add($duration);
|
$effectiveTrigger = $effectiveTrigger->add($duration);
|
||||||
$effectiveTrigger->add($triggerDuration);
|
$effectiveTrigger = $effectiveTrigger->add($triggerDuration);
|
||||||
} else {
|
} else {
|
||||||
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
|
$effectiveTrigger = $parentComponent->DTSTART->getDateTime();
|
||||||
$effectiveTrigger->add($triggerDuration);
|
$effectiveTrigger = $effectiveTrigger->add($triggerDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$effectiveTrigger = $trigger->getDateTime();
|
$effectiveTrigger = $trigger->getDateTime();
|
||||||
}
|
}
|
||||||
return $effectiveTrigger;
|
|
||||||
|
|
||||||
|
return $effectiveTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,17 +78,18 @@ class VAlarm extends VObject\Component {
|
||||||
* The rules used to determine if an event falls within the specified
|
* The rules used to determine if an event falls within the specified
|
||||||
* time-range is based on the CalDAV specification.
|
* time-range is based on the CalDAV specification.
|
||||||
*
|
*
|
||||||
* @param \DateTime $start
|
* @param DateTime $start
|
||||||
* @param \DateTime $end
|
* @param DateTime $end
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
public 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;
|
||||||
}
|
}
|
||||||
|
|
@ -93,16 +97,42 @@ class VAlarm extends VObject\Component {
|
||||||
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ACTION' => 1,
|
||||||
|
'TRIGGER' => 1,
|
||||||
|
|
||||||
|
'DURATION' => '?',
|
||||||
|
'REPEAT' => '?',
|
||||||
|
|
||||||
|
'ATTACH' => '?',
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The VAvailability component.
|
||||||
|
*
|
||||||
|
* This component adds functionality to a component, specific for VAVAILABILITY
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Ivan Enderlin
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class VAvailability extends VObject\Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns true or false depending on if the event falls in the specified
|
||||||
|
* time-range. This is used for filtering purposes.
|
||||||
|
*
|
||||||
|
* The rules used to determine if an event falls within the specified
|
||||||
|
* time-range is based on:
|
||||||
|
*
|
||||||
|
* https://tools.ietf.org/html/draft-daboo-calendar-availability-05#section-3.1
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end)
|
||||||
|
{
|
||||||
|
list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
|
||||||
|
|
||||||
|
return
|
||||||
|
(is_null($effectiveStart) || $start < $effectiveEnd) &&
|
||||||
|
(is_null($effectiveEnd) || $end > $effectiveStart)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the 'effective start' and 'effective end' of this VAVAILABILITY
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* We use the DTSTART and DTEND or DURATION to determine this.
|
||||||
|
*
|
||||||
|
* The returned value is an array containing DateTimeImmutable instances.
|
||||||
|
* If either the start or end is 'unbounded' its value will be null
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getEffectiveStartEnd()
|
||||||
|
{
|
||||||
|
$effectiveStart = null;
|
||||||
|
$effectiveEnd = null;
|
||||||
|
|
||||||
|
if (isset($this->DTSTART)) {
|
||||||
|
$effectiveStart = $this->DTSTART->getDateTime();
|
||||||
|
}
|
||||||
|
if (isset($this->DTEND)) {
|
||||||
|
$effectiveEnd = $this->DTEND->getDateTime();
|
||||||
|
} elseif ($effectiveStart && isset($this->DURATION)) {
|
||||||
|
$effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$effectiveStart, $effectiveEnd];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
|
||||||
|
'BUSYTYPE' => '?',
|
||||||
|
'CLASS' => '?',
|
||||||
|
'CREATED' => '?',
|
||||||
|
'DESCRIPTION' => '?',
|
||||||
|
'DTSTART' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'ORGANIZER' => '?',
|
||||||
|
'PRIORITY' => '?',
|
||||||
|
'SEQUENCE' => '?',
|
||||||
|
'SUMMARY' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
'DTEND' => '?',
|
||||||
|
'DURATION' => '?',
|
||||||
|
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'CONTACT' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
* Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
|
||||||
|
* Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on).
|
||||||
|
* 2 - A warning.
|
||||||
|
* 3 - An error.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$result = parent::validate($options);
|
||||||
|
|
||||||
|
if (isset($this->DTEND) && isset($this->DURATION)) {
|
||||||
|
$result[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'DTEND and DURATION cannot both be present',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,21 +2,26 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
use
|
use DateTimeInterface;
|
||||||
Sabre\VObject,
|
use DateTimeZone;
|
||||||
Sabre\VObject\Component;
|
use Sabre\VObject;
|
||||||
|
use Sabre\VObject\Component;
|
||||||
|
use Sabre\VObject\InvalidDataException;
|
||||||
|
use Sabre\VObject\Property;
|
||||||
|
use Sabre\VObject\Recur\EventIterator;
|
||||||
|
use Sabre\VObject\Recur\NoInstancesException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The VCalendar component
|
* The VCalendar component.
|
||||||
*
|
*
|
||||||
* This component adds functionality to a component, specific for a VCALENDAR.
|
* This component adds functionality to a component, specific for a VCALENDAR.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
|
|
@ -24,132 +29,137 @@ class VCalendar extends VObject\Document {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
static public $defaultName = 'VCALENDAR';
|
public 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
|
||||||
*/
|
*/
|
||||||
static public $componentMap = array(
|
public static $componentMap = [
|
||||||
'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
|
'VCALENDAR' => self::class,
|
||||||
'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
|
'VALARM' => VAlarm::class,
|
||||||
'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
|
'VEVENT' => VEvent::class,
|
||||||
'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
|
'VFREEBUSY' => VFreeBusy::class,
|
||||||
'VALARM' => 'Sabre\\VObject\\Component\\VAlarm',
|
'VAVAILABILITY' => VAvailability::class,
|
||||||
);
|
'AVAILABLE' => Available::class,
|
||||||
|
'VJOURNAL' => VJournal::class,
|
||||||
|
'VTIMEZONE' => VTimeZone::class,
|
||||||
|
'VTODO' => VTodo::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of value-types, and which classes they map to.
|
* List of value-types, and which classes they map to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
static public $valueMap = array(
|
public static $valueMap = [
|
||||||
'BINARY' => 'Sabre\\VObject\\Property\\Binary',
|
'BINARY' => VObject\Property\Binary::class,
|
||||||
'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
|
'BOOLEAN' => VObject\Property\Boolean::class,
|
||||||
'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
|
'CAL-ADDRESS' => VObject\Property\ICalendar\CalAddress::class,
|
||||||
'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date',
|
'DATE' => VObject\Property\ICalendar\Date::class,
|
||||||
'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'DATE-TIME' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
|
'DURATION' => VObject\Property\ICalendar\Duration::class,
|
||||||
'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
|
'FLOAT' => VObject\Property\FloatValue::class,
|
||||||
'INTEGER' => 'Sabre\\VObject\\Property\\Integer',
|
'INTEGER' => VObject\Property\IntegerValue::class,
|
||||||
'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
|
'PERIOD' => VObject\Property\ICalendar\Period::class,
|
||||||
'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
|
'RECUR' => VObject\Property\ICalendar\Recur::class,
|
||||||
'TEXT' => 'Sabre\\VObject\\Property\\Text',
|
'TEXT' => VObject\Property\Text::class,
|
||||||
'TIME' => 'Sabre\\VObject\\Property\\Time',
|
'TIME' => VObject\Property\Time::class,
|
||||||
'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
|
'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only.
|
||||||
'URI' => 'Sabre\\VObject\\Property\\Uri',
|
'URI' => VObject\Property\Uri::class,
|
||||||
'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
|
'UTC-OFFSET' => VObject\Property\UtcOffset::class,
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of properties, and which classes they map to.
|
* List of properties, and which classes they map to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
static public $propertyMap = array(
|
public static $propertyMap = [
|
||||||
// Calendar properties
|
// Calendar properties
|
||||||
'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
|
'CALSCALE' => VObject\Property\FlatText::class,
|
||||||
'METHOD' => 'Sabre\\VObject\\Property\\FlatText',
|
'METHOD' => VObject\Property\FlatText::class,
|
||||||
'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
|
'PRODID' => VObject\Property\FlatText::class,
|
||||||
'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
|
'VERSION' => VObject\Property\FlatText::class,
|
||||||
|
|
||||||
// Component properties
|
// Component properties
|
||||||
'ATTACH' => 'Sabre\\VObject\\Property\\Binary',
|
'ATTACH' => VObject\Property\Uri::class,
|
||||||
'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
|
'CATEGORIES' => VObject\Property\Text::class,
|
||||||
'CLASS' => 'Sabre\\VObject\\Property\\FlatText',
|
'CLASS' => VObject\Property\FlatText::class,
|
||||||
'COMMENT' => 'Sabre\\VObject\\Property\\FlatText',
|
'COMMENT' => VObject\Property\FlatText::class,
|
||||||
'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText',
|
'DESCRIPTION' => VObject\Property\FlatText::class,
|
||||||
'GEO' => 'Sabre\\VObject\\Property\\FloatValue',
|
'GEO' => VObject\Property\FloatValue::class,
|
||||||
'LOCATION' => 'Sabre\\VObject\\Property\\FlatText',
|
'LOCATION' => VObject\Property\FlatText::class,
|
||||||
'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\Integer',
|
'PERCENT-COMPLETE' => VObject\Property\IntegerValue::class,
|
||||||
'PRIORITY' => 'Sabre\\VObject\\Property\\Integer',
|
'PRIORITY' => VObject\Property\IntegerValue::class,
|
||||||
'RESOURCES' => 'Sabre\\VObject\\Property\\Text',
|
'RESOURCES' => VObject\Property\Text::class,
|
||||||
'STATUS' => 'Sabre\\VObject\\Property\\FlatText',
|
'STATUS' => VObject\Property\FlatText::class,
|
||||||
'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText',
|
'SUMMARY' => VObject\Property\FlatText::class,
|
||||||
|
|
||||||
// Date and Time Component Properties
|
// Date and Time Component Properties
|
||||||
'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'COMPLETED' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'DTEND' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'DUE' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'DTSTART' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
|
'DURATION' => VObject\Property\ICalendar\Duration::class,
|
||||||
'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
|
'FREEBUSY' => VObject\Property\ICalendar\Period::class,
|
||||||
'TRANSP' => 'Sabre\\VObject\\Property\\FlatText',
|
'TRANSP' => VObject\Property\FlatText::class,
|
||||||
|
|
||||||
// Time Zone Component Properties
|
// Time Zone Component Properties
|
||||||
'TZID' => 'Sabre\\VObject\\Property\\FlatText',
|
'TZID' => VObject\Property\FlatText::class,
|
||||||
'TZNAME' => 'Sabre\\VObject\\Property\\FlatText',
|
'TZNAME' => VObject\Property\FlatText::class,
|
||||||
'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
|
'TZOFFSETFROM' => VObject\Property\UtcOffset::class,
|
||||||
'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset',
|
'TZOFFSETTO' => VObject\Property\UtcOffset::class,
|
||||||
'TZURL' => 'Sabre\\VObject\\Property\\Uri',
|
'TZURL' => VObject\Property\Uri::class,
|
||||||
|
|
||||||
// Relationship Component Properties
|
// Relationship Component Properties
|
||||||
'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
|
'ATTENDEE' => VObject\Property\ICalendar\CalAddress::class,
|
||||||
'CONTACT' => 'Sabre\\VObject\\Property\\FlatText',
|
'CONTACT' => VObject\Property\FlatText::class,
|
||||||
'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
|
'ORGANIZER' => VObject\Property\ICalendar\CalAddress::class,
|
||||||
'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'RECURRENCE-ID' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText',
|
'RELATED-TO' => VObject\Property\FlatText::class,
|
||||||
'URL' => 'Sabre\\VObject\\Property\\Uri',
|
'URL' => VObject\Property\Uri::class,
|
||||||
'UID' => 'Sabre\\VObject\\Property\\FlatText',
|
'UID' => VObject\Property\FlatText::class,
|
||||||
|
|
||||||
// Recurrence Component Properties
|
// Recurrence Component Properties
|
||||||
'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'EXDATE' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'RDATE' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
|
'RRULE' => VObject\Property\ICalendar\Recur::class,
|
||||||
'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
|
'EXRULE' => VObject\Property\ICalendar\Recur::class, // Deprecated since rfc5545
|
||||||
|
|
||||||
// Alarm Component Properties
|
// Alarm Component Properties
|
||||||
'ACTION' => 'Sabre\\VObject\\Property\\FlatText',
|
'ACTION' => VObject\Property\FlatText::class,
|
||||||
'REPEAT' => 'Sabre\\VObject\\Property\\Integer',
|
'REPEAT' => VObject\Property\IntegerValue::class,
|
||||||
'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
|
'TRIGGER' => VObject\Property\ICalendar\Duration::class,
|
||||||
|
|
||||||
// Change Management Component Properties
|
// Change Management Component Properties
|
||||||
'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'CREATED' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'DTSTAMP' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'LAST-MODIFIED' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'SEQUENCE' => 'Sabre\\VObject\\Property\\Integer',
|
'SEQUENCE' => VObject\Property\IntegerValue::class,
|
||||||
|
|
||||||
// Request Status
|
// Request Status
|
||||||
'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
|
'REQUEST-STATUS' => VObject\Property\Text::class,
|
||||||
|
|
||||||
// Additions from draft-daboo-valarm-extensions-04
|
// Additions from draft-daboo-valarm-extensions-04
|
||||||
'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text',
|
'ALARM-AGENT' => VObject\Property\Text::class,
|
||||||
'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
|
'ACKNOWLEDGED' => VObject\Property\ICalendar\DateTime::class,
|
||||||
'PROXIMITY' => 'Sabre\\VObject\\Property\\Text',
|
'PROXIMITY' => VObject\Property\Text::class,
|
||||||
'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',
|
'DEFAULT-ALARM' => VObject\Property\Boolean::class,
|
||||||
|
|
||||||
);
|
// Additions from draft-daboo-calendar-availability-05
|
||||||
|
'BUSYTYPE' => VObject\Property\Text::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current document type.
|
* Returns the current document type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getDocumentType() {
|
public function getDocumentType()
|
||||||
|
{
|
||||||
return self::ICALENDAR20;
|
return self::ICALENDAR20;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -160,118 +170,188 @@ class VCalendar extends VObject\Document {
|
||||||
* VTIMEZONE components will always be excluded.
|
* VTIMEZONE components will always be excluded.
|
||||||
*
|
*
|
||||||
* @param string $componentName filter by component name
|
* @param string $componentName filter by component name
|
||||||
* @return array
|
*
|
||||||
|
* @return VObject\Component[]
|
||||||
*/
|
*/
|
||||||
public function getBaseComponents($componentName = null) {
|
public function getBaseComponents($componentName = null)
|
||||||
|
{
|
||||||
|
$isBaseComponent = function ($component) {
|
||||||
|
if (!$component instanceof VObject\Component) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ('VTIMEZONE' === $component->name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($component->{'RECURRENCE-ID'})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$components = array();
|
return true;
|
||||||
foreach($this->children as $component) {
|
};
|
||||||
|
|
||||||
if (!$component instanceof VObject\Component)
|
if ($componentName) {
|
||||||
continue;
|
// Early exit
|
||||||
|
return array_filter(
|
||||||
if (isset($component->{'RECURRENCE-ID'}))
|
$this->select($componentName),
|
||||||
continue;
|
$isBaseComponent
|
||||||
|
);
|
||||||
if ($componentName && $component->name !== strtoupper($componentName))
|
}
|
||||||
continue;
|
|
||||||
|
|
||||||
if ($component->name === 'VTIMEZONE')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
$components[] = $component;
|
|
||||||
|
|
||||||
|
$components = [];
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
foreach ($childGroup as $child) {
|
||||||
|
if (!$child instanceof Component) {
|
||||||
|
// If one child is not a component, they all are so we skip
|
||||||
|
// the entire group.
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
if ($isBaseComponent($child)) {
|
||||||
|
$components[] = $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $components;
|
return $components;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns the first component that is not a VTIMEZONE, and does not have
|
||||||
|
* an RECURRENCE-ID.
|
||||||
|
*
|
||||||
|
* If there is no such component, null will be returned.
|
||||||
|
*
|
||||||
|
* @param string $componentName filter by component name
|
||||||
|
*
|
||||||
|
* @return VObject\Component|null
|
||||||
|
*/
|
||||||
|
public function getBaseComponent($componentName = null)
|
||||||
|
{
|
||||||
|
$isBaseComponent = function ($component) {
|
||||||
|
if (!$component instanceof VObject\Component) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ('VTIMEZONE' === $component->name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($component->{'RECURRENCE-ID'})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($componentName) {
|
||||||
|
foreach ($this->select($componentName) as $child) {
|
||||||
|
if ($isBaseComponent($child)) {
|
||||||
|
return $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searching all components
|
||||||
|
foreach ($this->children as $childGroup) {
|
||||||
|
foreach ($childGroup as $child) {
|
||||||
|
if ($isBaseComponent($child)) {
|
||||||
|
return $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand all events in this VCalendar object and return a new VCalendar
|
||||||
|
* with the expanded events.
|
||||||
|
*
|
||||||
* 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 it's recurrence information, and only
|
* Each event will be stripped from its 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.
|
||||||
*
|
*
|
||||||
* In addition, this method will cause timezone information to be stripped,
|
* In addition, this method will cause timezone information to be stripped,
|
||||||
* and normalized to UTC.
|
* and normalized to UTC.
|
||||||
*
|
*
|
||||||
* This method will alter the VCalendar. This cannot be reversed.
|
* @param DateTimeZone $timeZone reference timezone for floating dates and
|
||||||
|
* times
|
||||||
*
|
*
|
||||||
* This functionality is specifically used by the CalDAV standard. It is
|
* @return VCalendar
|
||||||
* possible for clients to request expand events, if they are rather simple
|
|
||||||
* clients and do not have the possibility to calculate recurrences.
|
|
||||||
*
|
|
||||||
* @param DateTime $start
|
|
||||||
* @param DateTime $end
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function expand(\DateTime $start, \DateTime $end) {
|
public function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null)
|
||||||
|
{
|
||||||
|
$newChildren = [];
|
||||||
|
$recurringEvents = [];
|
||||||
|
|
||||||
$newEvents = array();
|
if (!$timeZone) {
|
||||||
|
$timeZone = new DateTimeZone('UTC');
|
||||||
foreach($this->select('VEVENT') as $key=>$vevent) {
|
|
||||||
|
|
||||||
if (isset($vevent->{'RECURRENCE-ID'})) {
|
|
||||||
unset($this->children[$key]);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stripTimezones = function (Component $component) use ($timeZone, &$stripTimezones) {
|
||||||
if (!$vevent->rrule) {
|
foreach ($component->children() as $componentChild) {
|
||||||
unset($this->children[$key]);
|
if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {
|
||||||
if ($vevent->isInTimeRange($start, $end)) {
|
$dt = $componentChild->getDateTimes($timeZone);
|
||||||
$newEvents[] = $vevent;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$uid = (string)$vevent->uid;
|
|
||||||
if (!$uid) {
|
|
||||||
throw new \LogicException('Event did not have a UID!');
|
|
||||||
}
|
|
||||||
|
|
||||||
$it = new VObject\RecurrenceIterator($this, $vevent->uid);
|
|
||||||
$it->fastForward($start);
|
|
||||||
|
|
||||||
while($it->valid() && $it->getDTStart() < $end) {
|
|
||||||
|
|
||||||
if ($it->getDTEnd() > $start) {
|
|
||||||
|
|
||||||
$newEvents[] = $it->getEventObject();
|
|
||||||
|
|
||||||
}
|
|
||||||
$it->next();
|
|
||||||
|
|
||||||
}
|
|
||||||
unset($this->children[$key]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting all properties to UTC time.
|
|
||||||
foreach($newEvents as $newEvent) {
|
|
||||||
|
|
||||||
foreach($newEvent->children as $child) {
|
|
||||||
if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
|
|
||||||
$dt = $child->getDateTimes();
|
|
||||||
// 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
|
||||||
// first.
|
// first.
|
||||||
$dt[0]->setTimeZone(new \DateTimeZone('UTC'));
|
$dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC'));
|
||||||
$child->setDateTimes($dt);
|
$componentChild->setDateTimes($dt);
|
||||||
|
} elseif ($componentChild instanceof Component) {
|
||||||
|
$stripTimezones($componentChild);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $component;
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach ($this->children() as $child) {
|
||||||
|
if ($child instanceof Property && 'PRODID' !== $child->name) {
|
||||||
|
// We explicitly want to ignore PRODID, because we want to
|
||||||
|
// overwrite it with our own.
|
||||||
|
$newChildren[] = clone $child;
|
||||||
|
} elseif ($child instanceof Component && 'VTIMEZONE' !== $child->name) {
|
||||||
|
// We're also stripping all VTIMEZONE objects because we're
|
||||||
|
// converting everything to UTC.
|
||||||
|
if ('VEVENT' === $child->name && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
|
||||||
|
// Handle these a bit later.
|
||||||
|
$uid = (string) $child->UID;
|
||||||
|
if (!$uid) {
|
||||||
|
throw new InvalidDataException('Every VEVENT object must have a UID property');
|
||||||
|
}
|
||||||
|
if (isset($recurringEvents[$uid])) {
|
||||||
|
$recurringEvents[$uid][] = clone $child;
|
||||||
|
} else {
|
||||||
|
$recurringEvents[$uid] = [clone $child];
|
||||||
|
}
|
||||||
|
} elseif ('VEVENT' === $child->name && $child->isInTimeRange($start, $end)) {
|
||||||
|
$newChildren[] = $stripTimezones(clone $child);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->add($newEvent);
|
foreach ($recurringEvents as $events) {
|
||||||
|
try {
|
||||||
|
$it = new EventIterator($events, null, $timeZone);
|
||||||
|
} catch (NoInstancesException $e) {
|
||||||
|
// This event is recurring, but it doesn't have a single
|
||||||
|
// instance. We are skipping this event from the output
|
||||||
|
// entirely.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$it->fastForward($start);
|
||||||
|
|
||||||
|
while ($it->valid() && $it->getDTStart() < $end) {
|
||||||
|
if ($it->getDTEnd() > $start) {
|
||||||
|
$newChildren[] = $stripTimezones($it->getEventObject());
|
||||||
|
}
|
||||||
|
$it->next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removing all VTIMEZONE components
|
return new self($newChildren);
|
||||||
unset($this->VTIMEZONE);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -279,91 +359,170 @@ class VCalendar extends VObject\Document {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getDefaults() {
|
protected function getDefaults()
|
||||||
|
{
|
||||||
return array(
|
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',
|
||||||
);
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'PRODID' => 1,
|
||||||
|
'VERSION' => 1,
|
||||||
|
|
||||||
|
'CALSCALE' => '?',
|
||||||
|
'METHOD' => '?',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the node for correctness.
|
* Validates the node for correctness.
|
||||||
* An array is returned with warnings.
|
|
||||||
*
|
*
|
||||||
* Every item in the array has the following properties:
|
* The following options are supported:
|
||||||
* * level - (number between 1 and 3 with severity information)
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
* * message - (human readable message)
|
* Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
|
||||||
* * node - (reference to the offending node)
|
* Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on).
|
||||||
|
* 2 - A warning.
|
||||||
|
* 3 - An error.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$warnings = parent::validate($options);
|
||||||
|
|
||||||
$warnings = array();
|
if ($ver = $this->VERSION) {
|
||||||
|
if ('2.0' !== (string) $ver) {
|
||||||
$version = $this->select('VERSION');
|
$warnings[] = [
|
||||||
if (count($version)!==1) {
|
'level' => 3,
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 1,
|
|
||||||
'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if ((string)$this->VERSION !== '2.0') {
|
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 1,
|
|
||||||
'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,
|
||||||
);
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$version = $this->select('PRODID');
|
|
||||||
if (count($version)!==1) {
|
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 2,
|
|
||||||
'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (count($this->CALSCALE) > 1) {
|
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 2,
|
|
||||||
'message' => 'The CALSCALE property must not be specified more than once.',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (count($this->METHOD) > 1) {
|
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 2,
|
|
||||||
'message' => 'The METHOD property must not be specified more than once.',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$uidList = [];
|
||||||
$componentsFound = 0;
|
$componentsFound = 0;
|
||||||
foreach($this->children as $child) {
|
$componentTypes = [];
|
||||||
|
|
||||||
|
foreach ($this->children() as $child) {
|
||||||
if ($child instanceof Component) {
|
if ($child instanceof Component) {
|
||||||
$componentsFound++;
|
++$componentsFound;
|
||||||
|
|
||||||
|
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$componentTypes[] = $child->name;
|
||||||
|
|
||||||
|
$uid = (string) $child->UID;
|
||||||
|
$isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1;
|
||||||
|
if (isset($uidList[$uid])) {
|
||||||
|
++$uidList[$uid]['count'];
|
||||||
|
if ($isMaster && $uidList[$uid]['hasMaster']) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'More than one master object was found for the object with UID '.$uid,
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$uidList[$uid]['hasMaster'] += $isMaster;
|
||||||
|
} else {
|
||||||
|
$uidList[$uid] = [
|
||||||
|
'count' => 1,
|
||||||
|
'hasMaster' => $isMaster,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($componentsFound===0) {
|
if (0 === $componentsFound) {
|
||||||
$warnings[] = array(
|
$warnings[] = [
|
||||||
'level' => 1,
|
'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,
|
||||||
);
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge(
|
if ($options & self::PROFILE_CALDAV) {
|
||||||
$warnings,
|
if (count($uidList) > 1) {
|
||||||
parent::validate()
|
$warnings[] = [
|
||||||
);
|
'level' => 3,
|
||||||
|
'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (0 === count($componentTypes)) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count(array_unique($componentTypes)) > 1) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->METHOD)) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all components with a specific UID value.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getByUID($uid)
|
||||||
|
{
|
||||||
|
return array_filter($this->getComponents(), function ($item) use ($uid) {
|
||||||
|
if (!$itemUid = $item->select('UID')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$itemUid = current($itemUid)->getValue();
|
||||||
|
|
||||||
|
return $uid === $itemUid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,21 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
use
|
use Sabre\VObject;
|
||||||
Sabre\VObject;
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The VCard component
|
* The VCard component.
|
||||||
*
|
*
|
||||||
* This component represents the BEGIN:VCARD and END:VCARD found in every
|
* This component represents the BEGIN:VCARD and END:VCARD found in every
|
||||||
* vcard.
|
* vcard.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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,106 +24,130 @@ class VCard extends VObject\Document {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
static public $defaultName = 'VCARD';
|
public static $defaultName = 'VCARD';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caching the version number
|
* Caching the version number.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private $version = null;
|
private $version = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a list of components, and which classes they should map to.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $componentMap = [
|
||||||
|
'VCARD' => VCard::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of value-types, and which classes they map to.
|
* List of value-types, and which classes they map to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
static public $valueMap = array(
|
public static $valueMap = [
|
||||||
'BINARY' => 'Sabre\\VObject\\Property\\Binary',
|
'BINARY' => VObject\Property\Binary::class,
|
||||||
'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
|
'BOOLEAN' => VObject\Property\Boolean::class,
|
||||||
'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only
|
'CONTENT-ID' => VObject\Property\FlatText::class, // vCard 2.1 only
|
||||||
'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date',
|
'DATE' => VObject\Property\VCard\Date::class,
|
||||||
'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime',
|
'DATE-TIME' => VObject\Property\VCard\DateTime::class,
|
||||||
'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
|
'DATE-AND-OR-TIME' => VObject\Property\VCard\DateAndOrTime::class, // vCard only
|
||||||
'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
|
'FLOAT' => VObject\Property\FloatValue::class,
|
||||||
'INTEGER' => 'Sabre\\VObject\\Property\\Integer',
|
'INTEGER' => VObject\Property\IntegerValue::class,
|
||||||
'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
|
'LANGUAGE-TAG' => VObject\Property\VCard\LanguageTag::class,
|
||||||
'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
|
'PHONE-NUMBER' => VObject\Property\VCard\PhoneNumber::class, // vCard 3.0 only
|
||||||
'TEXT' => 'Sabre\\VObject\\Property\\Text',
|
'TIMESTAMP' => VObject\Property\VCard\TimeStamp::class,
|
||||||
'TIME' => 'Sabre\\VObject\\Property\\Time',
|
'TEXT' => VObject\Property\Text::class,
|
||||||
'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
|
'TIME' => VObject\Property\Time::class,
|
||||||
'URI' => 'Sabre\\VObject\\Property\\Uri',
|
'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only.
|
||||||
'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
|
'URI' => VObject\Property\Uri::class,
|
||||||
'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
|
'URL' => VObject\Property\Uri::class, // vCard 2.1 only
|
||||||
);
|
'UTC-OFFSET' => VObject\Property\UtcOffset::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of properties, and which classes they map to.
|
* List of properties, and which classes they map to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
static public $propertyMap = array(
|
public static $propertyMap = [
|
||||||
|
|
||||||
// vCard 2.1 properties and up
|
// vCard 2.1 properties and up
|
||||||
'N' => 'Sabre\\VObject\\Property\\Text',
|
'N' => VObject\Property\Text::class,
|
||||||
'FN' => 'Sabre\\VObject\\Property\\FlatText',
|
'FN' => VObject\Property\FlatText::class,
|
||||||
'PHOTO' => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values.
|
'PHOTO' => VObject\Property\Binary::class,
|
||||||
'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
|
'BDAY' => VObject\Property\VCard\DateAndOrTime::class,
|
||||||
'ADR' => 'Sabre\\VObject\\Property\\Text',
|
'ADR' => VObject\Property\Text::class,
|
||||||
'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
'LABEL' => VObject\Property\FlatText::class, // Removed in vCard 4.0
|
||||||
'TEL' => 'Sabre\\VObject\\Property\\FlatText',
|
'TEL' => VObject\Property\FlatText::class,
|
||||||
'EMAIL' => 'Sabre\\VObject\\Property\\FlatText',
|
'EMAIL' => VObject\Property\FlatText::class,
|
||||||
'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
'MAILER' => VObject\Property\FlatText::class, // Removed in vCard 4.0
|
||||||
'GEO' => 'Sabre\\VObject\\Property\\FlatText',
|
'GEO' => VObject\Property\FlatText::class,
|
||||||
'TITLE' => 'Sabre\\VObject\\Property\\FlatText',
|
'TITLE' => VObject\Property\FlatText::class,
|
||||||
'ROLE' => 'Sabre\\VObject\\Property\\FlatText',
|
'ROLE' => VObject\Property\FlatText::class,
|
||||||
'LOGO' => 'Sabre\\VObject\\Property\\Binary',
|
'LOGO' => VObject\Property\Binary::class,
|
||||||
// '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' => VObject\Property\Text::class,
|
||||||
'NOTE' => 'Sabre\\VObject\\Property\\FlatText',
|
'NOTE' => VObject\Property\FlatText::class,
|
||||||
'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
|
'REV' => VObject\Property\VCard\TimeStamp::class,
|
||||||
'SOUND' => 'Sabre\\VObject\\Property\\FlatText',
|
'SOUND' => VObject\Property\FlatText::class,
|
||||||
'URL' => 'Sabre\\VObject\\Property\\Uri',
|
'URL' => VObject\Property\Uri::class,
|
||||||
'UID' => 'Sabre\\VObject\\Property\\FlatText',
|
'UID' => VObject\Property\FlatText::class,
|
||||||
'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
|
'VERSION' => VObject\Property\FlatText::class,
|
||||||
'KEY' => 'Sabre\\VObject\\Property\\FlatText',
|
'KEY' => VObject\Property\FlatText::class,
|
||||||
'TZ' => 'Sabre\\VObject\\Property\\Text',
|
'TZ' => VObject\Property\Text::class,
|
||||||
|
|
||||||
// vCard 3.0 properties
|
// vCard 3.0 properties
|
||||||
'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
|
'CATEGORIES' => VObject\Property\Text::class,
|
||||||
'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
|
'SORT-STRING' => VObject\Property\FlatText::class,
|
||||||
'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
|
'PRODID' => VObject\Property\FlatText::class,
|
||||||
'NICKNAME' => 'Sabre\\VObject\\Property\\Text',
|
'NICKNAME' => VObject\Property\Text::class,
|
||||||
'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
'CLASS' => VObject\Property\FlatText::class, // Removed in vCard 4.0
|
||||||
|
|
||||||
// rfc2739 properties
|
// rfc2739 properties
|
||||||
'FBURL' => 'Sabre\\VObject\\Property\\Uri',
|
'FBURL' => VObject\Property\Uri::class,
|
||||||
'CAPURI' => 'Sabre\\VObject\\Property\\Uri',
|
'CAPURI' => VObject\Property\Uri::class,
|
||||||
'CALURI' => 'Sabre\\VObject\\Property\\Uri',
|
'CALURI' => VObject\Property\Uri::class,
|
||||||
|
'CALADRURI' => VObject\Property\Uri::class,
|
||||||
|
|
||||||
// rfc4770 properties
|
// rfc4770 properties
|
||||||
'IMPP' => 'Sabre\\VObject\\Property\\Uri',
|
'IMPP' => VObject\Property\Uri::class,
|
||||||
|
|
||||||
// vCard 4.0 properties
|
// vCard 4.0 properties
|
||||||
'XML' => 'Sabre\\VObject\\Property\\FlatText',
|
'SOURCE' => VObject\Property\Uri::class,
|
||||||
'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
|
'XML' => VObject\Property\FlatText::class,
|
||||||
'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
|
'ANNIVERSARY' => VObject\Property\VCard\DateAndOrTime::class,
|
||||||
'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
|
'CLIENTPIDMAP' => VObject\Property\Text::class,
|
||||||
'GENDER' => 'Sabre\\VObject\\Property\\Text',
|
'LANG' => VObject\Property\VCard\LanguageTag::class,
|
||||||
'KIND' => 'Sabre\\VObject\\Property\\FlatText',
|
'GENDER' => VObject\Property\Text::class,
|
||||||
|
'KIND' => VObject\Property\FlatText::class,
|
||||||
|
'MEMBER' => VObject\Property\Uri::class,
|
||||||
|
'RELATED' => VObject\Property\Uri::class,
|
||||||
|
|
||||||
);
|
// rfc6474 properties
|
||||||
|
'BIRTHPLACE' => VObject\Property\FlatText::class,
|
||||||
|
'DEATHPLACE' => VObject\Property\FlatText::class,
|
||||||
|
'DEATHDATE' => VObject\Property\VCard\DateAndOrTime::class,
|
||||||
|
|
||||||
|
// rfc6715 properties
|
||||||
|
'EXPERTISE' => VObject\Property\FlatText::class,
|
||||||
|
'HOBBY' => VObject\Property\FlatText::class,
|
||||||
|
'INTEREST' => VObject\Property\FlatText::class,
|
||||||
|
'ORG-DIRECTORY' => VObject\Property\FlatText::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current document type.
|
* Returns the current document type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getDocumentType() {
|
public 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;
|
||||||
|
|
@ -135,14 +159,13 @@ class VCard extends VObject\Document {
|
||||||
$this->version = self::VCARD40;
|
$this->version = self::VCARD40;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$this->version = self::UNKNOWN;
|
// We don't want to cache the version if it's unknown,
|
||||||
break;
|
// because we might get a version property in a bit.
|
||||||
|
return self::UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->version;
|
return $this->version;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -156,13 +179,14 @@ class VCard extends VObject\Document {
|
||||||
* If input and output version are identical, a clone is returned.
|
* If input and output version are identical, a clone is returned.
|
||||||
*
|
*
|
||||||
* @param int $target
|
* @param int $target
|
||||||
|
*
|
||||||
* @return VCard
|
* @return VCard
|
||||||
*/
|
*/
|
||||||
public function convert($target) {
|
public function convert($target)
|
||||||
|
{
|
||||||
$converter = new VObject\VCardConverter();
|
$converter = new VObject\VCardConverter();
|
||||||
return $converter->convert($this, $target);
|
|
||||||
|
|
||||||
|
return $converter->convert($this, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,67 +196,85 @@ class VCard extends VObject\Document {
|
||||||
*/
|
*/
|
||||||
const DEFAULT_VERSION = self::VCARD21;
|
const DEFAULT_VERSION = self::VCARD21;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the node for correctness.
|
* Validates the node for correctness.
|
||||||
*
|
*
|
||||||
* The following options are supported:
|
* The following options are supported:
|
||||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
* be attempted.
|
|
||||||
*
|
*
|
||||||
* An array is returned with warnings.
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
*
|
*
|
||||||
* Every item in the array has the following properties:
|
* * level - problem level.
|
||||||
* * level - (number between 1 and 3 with severity information)
|
* * message - A human-readable string describing the issue.
|
||||||
* * message - (human readable message)
|
* * node - A reference to the problematic node.
|
||||||
* * node - (reference to the offending node)
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
*
|
*
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$warnings = [];
|
||||||
|
|
||||||
$warnings = array();
|
$versionMap = [
|
||||||
|
|
||||||
$versionMap = array(
|
|
||||||
self::VCARD21 => '2.1',
|
self::VCARD21 => '2.1',
|
||||||
self::VCARD30 => '3.0',
|
self::VCARD30 => '3.0',
|
||||||
self::VCARD40 => '4.0',
|
self::VCARD40 => '4.0',
|
||||||
);
|
];
|
||||||
|
|
||||||
$version = $this->select('VERSION');
|
$version = $this->select('VERSION');
|
||||||
if (count($version)!==1) {
|
if (1 === count($version)) {
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 1,
|
|
||||||
'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
if ($options & self::REPAIR) {
|
|
||||||
$this->VERSION = $versionMap[self::DEFAULT_VERSION];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$version = (string) $this->VERSION;
|
$version = (string) $this->VERSION;
|
||||||
if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
|
if ('2.1' !== $version && '3.0' !== $version && '4.0' !== $version) {
|
||||||
$warnings[] = array(
|
$warnings[] = [
|
||||||
'level' => 1,
|
'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)) {
|
||||||
}
|
$warnings[] = [
|
||||||
$fn = $this->select('FN');
|
'level' => 3,
|
||||||
if (count($fn)!==1) {
|
'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
|
||||||
$warnings[] = array(
|
|
||||||
'level' => 1,
|
|
||||||
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
|
|
||||||
'node' => $this,
|
'node' => $this,
|
||||||
);
|
];
|
||||||
if (($options & self::REPAIR) && count($fn) === 0) {
|
}
|
||||||
|
}
|
||||||
|
$uid = $this->select('UID');
|
||||||
|
if (0 === count($uid)) {
|
||||||
|
if ($options & self::PROFILE_CARDDAV) {
|
||||||
|
// Required for CardDAV
|
||||||
|
$warningLevel = 3;
|
||||||
|
$message = 'vCards on CardDAV servers MUST have a UID property.';
|
||||||
|
} else {
|
||||||
|
// Not required for regular vcards
|
||||||
|
$warningLevel = 2;
|
||||||
|
$message = 'Adding a UID to a vCard property is recommended.';
|
||||||
|
}
|
||||||
|
if ($options & self::REPAIR) {
|
||||||
|
$this->UID = VObject\UUIDUtil::getUUID();
|
||||||
|
$warningLevel = 1;
|
||||||
|
}
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $warningLevel,
|
||||||
|
'message' => $message,
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fn = $this->select('FN');
|
||||||
|
if (1 !== count($fn)) {
|
||||||
|
$repaired = false;
|
||||||
|
if (($options & self::REPAIR) && 0 === count($fn)) {
|
||||||
// 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)) {
|
||||||
|
|
@ -242,26 +284,96 @@ class VCard extends VObject\Document {
|
||||||
} else {
|
} else {
|
||||||
$this->FN = $value[0];
|
$this->FN = $value[0];
|
||||||
}
|
}
|
||||||
|
$repaired = true;
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
|
// Otherwise, the EMAIL property may work
|
||||||
|
} elseif (isset($this->EMAIL)) {
|
||||||
|
$this->FN = (string) $this->EMAIL;
|
||||||
|
$repaired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repaired ? 1 : 3,
|
||||||
|
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return array_merge(
|
return array_merge(
|
||||||
parent::validate($options),
|
parent::validate($options),
|
||||||
$warnings
|
$warnings
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ADR' => '*',
|
||||||
|
'ANNIVERSARY' => '?',
|
||||||
|
'BDAY' => '?',
|
||||||
|
'CALADRURI' => '*',
|
||||||
|
'CALURI' => '*',
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'CLIENTPIDMAP' => '*',
|
||||||
|
'EMAIL' => '*',
|
||||||
|
'FBURL' => '*',
|
||||||
|
'IMPP' => '*',
|
||||||
|
'GENDER' => '?',
|
||||||
|
'GEO' => '*',
|
||||||
|
'KEY' => '*',
|
||||||
|
'KIND' => '?',
|
||||||
|
'LANG' => '*',
|
||||||
|
'LOGO' => '*',
|
||||||
|
'MEMBER' => '*',
|
||||||
|
'N' => '?',
|
||||||
|
'NICKNAME' => '*',
|
||||||
|
'NOTE' => '*',
|
||||||
|
'ORG' => '*',
|
||||||
|
'PHOTO' => '*',
|
||||||
|
'PRODID' => '?',
|
||||||
|
'RELATED' => '*',
|
||||||
|
'REV' => '?',
|
||||||
|
'ROLE' => '*',
|
||||||
|
'SOUND' => '*',
|
||||||
|
'SOURCE' => '*',
|
||||||
|
'TEL' => '*',
|
||||||
|
'TITLE' => '*',
|
||||||
|
'TZ' => '*',
|
||||||
|
'URL' => '*',
|
||||||
|
'VERSION' => '1',
|
||||||
|
'XML' => '*',
|
||||||
|
|
||||||
|
// FN is commented out, because it's already handled by the
|
||||||
|
// validate function, which may also try to repair it.
|
||||||
|
// 'FN' => '+',
|
||||||
|
'UID' => '?',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a preferred field.
|
* Returns a preferred field.
|
||||||
*
|
*
|
||||||
* VCards can indicate wether a field such as ADR, TEL or EMAIL is
|
* VCards can indicate whether a field such as ADR, TEL or EMAIL is
|
||||||
* preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
|
* preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
|
||||||
* being a number between 1 and 100).
|
* being a number between 1 and 100).
|
||||||
*
|
*
|
||||||
|
|
@ -269,14 +381,14 @@ class VCard extends VObject\Document {
|
||||||
* a field with that name does not exist, null is returned.
|
* a field with that name does not exist, null is returned.
|
||||||
*
|
*
|
||||||
* @param string $fieldName
|
* @param string $fieldName
|
||||||
|
*
|
||||||
* @return VObject\Property|null
|
* @return VObject\Property|null
|
||||||
*/
|
*/
|
||||||
public function preferred($propertyName) {
|
public 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;
|
||||||
|
|
@ -288,10 +400,29 @@ class VCard extends VObject\Document {
|
||||||
$preferred = $field;
|
$preferred = $field;
|
||||||
$lastPref = $pref;
|
$lastPref = $pref;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return $preferred;
|
|
||||||
|
|
||||||
|
return $preferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
|
||||||
|
*
|
||||||
|
* This function will return null if the property does not exist. If there are
|
||||||
|
* multiple properties with the same TYPE value, only one will be returned.
|
||||||
|
*
|
||||||
|
* @param string $propertyName
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return VObject\Property|null
|
||||||
|
*/
|
||||||
|
public function getByType($propertyName, $type)
|
||||||
|
{
|
||||||
|
foreach ($this->select($propertyName) as $field) {
|
||||||
|
if (isset($field['TYPE']) && $field['TYPE']->has($type)) {
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -299,13 +430,13 @@ class VCard extends VObject\Document {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getDefaults() {
|
protected function getDefaults()
|
||||||
|
{
|
||||||
return array(
|
return [
|
||||||
'VERSION' => '3.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(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -314,40 +445,92 @@ class VCard extends VObject\Document {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public 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 = array();
|
$properties = [];
|
||||||
|
|
||||||
foreach($this->children as $child) {
|
foreach ($this->children() as $child) {
|
||||||
$properties[] = $child->jsonSerialize();
|
$properties[] = $child->jsonSerialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return array(
|
return [
|
||||||
strtolower($this->name),
|
strtolower($this->name),
|
||||||
$properties,
|
$properties,
|
||||||
);
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes the data into XML. This is used to create xCard or
|
||||||
|
* xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$propertiesByGroup = [];
|
||||||
|
|
||||||
|
foreach ($this->children() as $property) {
|
||||||
|
$group = $property->group;
|
||||||
|
|
||||||
|
if (!isset($propertiesByGroup[$group])) {
|
||||||
|
$propertiesByGroup[$group] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$propertiesByGroup[$group][] = $property;
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->startElement(strtolower($this->name));
|
||||||
|
|
||||||
|
foreach ($propertiesByGroup as $group => $properties) {
|
||||||
|
if (!empty($group)) {
|
||||||
|
$writer->startElement('group');
|
||||||
|
$writer->writeAttribute('name', strtolower($group));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
switch ($property->name) {
|
||||||
|
case 'VERSION':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'XML':
|
||||||
|
$value = $property->getParts();
|
||||||
|
$fragment = new Xml\Element\XmlFragment($value[0]);
|
||||||
|
$writer->write($fragment);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$property->xmlSerialize($writer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($group)) {
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the default class for a property name.
|
* Returns the default class for a property name.
|
||||||
*
|
*
|
||||||
* @param string $propertyName
|
* @param string $propertyName
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getClassNameForPropertyName($propertyName) {
|
public 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 (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) {
|
||||||
if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) {
|
return VObject\Property\Uri::class;
|
||||||
return 'Sabre\\VObject\\Property\\Uri';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $className;
|
return $className;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
use Sabre\VObject;
|
use Sabre\VObject;
|
||||||
|
use Sabre\VObject\Recur\EventIterator;
|
||||||
|
use Sabre\VObject\Recur\NoInstancesException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VEvent component
|
* VEvent component.
|
||||||
*
|
*
|
||||||
* This component contains some additional functionality specific for VEVENT's.
|
* This component contains some additional functionality specific for VEVENT's.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -21,14 +25,19 @@ class VEvent extends VObject\Component {
|
||||||
* The rules used to determine if an event falls within the specified
|
* The rules used to determine if an event falls within the specified
|
||||||
* time-range is based on the CalDAV specification.
|
* time-range is based on the CalDAV specification.
|
||||||
*
|
*
|
||||||
* @param \DateTime $start
|
|
||||||
* @param \DateTime $end
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end)
|
||||||
|
{
|
||||||
if ($this->RRULE) {
|
if ($this->RRULE) {
|
||||||
$it = new VObject\RecurrenceIterator($this);
|
try {
|
||||||
|
$it = new EventIterator($this, null, $start->getTimezone());
|
||||||
|
} catch (NoInstancesException $e) {
|
||||||
|
// If we've caught this exception, there are no instances
|
||||||
|
// for the event that fall into the specified time-range.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$it->fastForward($start);
|
$it->fastForward($start);
|
||||||
|
|
||||||
// We fast-forwarded to a spot where the end-time of the
|
// We fast-forwarded to a spot where the end-time of the
|
||||||
|
|
@ -37,34 +46,95 @@ 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();
|
$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.
|
||||||
//
|
//
|
||||||
// See:
|
// See:
|
||||||
// http://tools.ietf.org/html/rfc5545#page-54
|
// http://tools.ietf.org/html/rfc5545#page-54
|
||||||
$effectiveEnd = $this->DTEND->getDateTime();
|
$effectiveEnd = $this->DTEND->getDateTime($end->getTimezone());
|
||||||
|
|
||||||
} elseif (isset($this->DURATION)) {
|
} elseif (isset($this->DURATION)) {
|
||||||
$effectiveEnd = clone $effectiveStart;
|
$effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
|
||||||
$effectiveEnd->add( VObject\DateTimeParser::parseDuration($this->DURATION) );
|
|
||||||
} elseif (!$this->DTSTART->hasTime()) {
|
} elseif (!$this->DTSTART->hasTime()) {
|
||||||
$effectiveEnd = clone $effectiveStart;
|
$effectiveEnd = $effectiveStart->modify('+1 day');
|
||||||
$effectiveEnd->modify('+1 day');
|
|
||||||
} else {
|
} else {
|
||||||
$effectiveEnd = clone $effectiveStart;
|
$effectiveEnd = $effectiveStart;
|
||||||
}
|
|
||||||
return (
|
|
||||||
($start <= $effectiveEnd) && ($end > $effectiveStart)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
($start < $effectiveEnd) && ($end > $effectiveStart)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should return a list of default property values.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getDefaults()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(),
|
||||||
|
'DTSTAMP' => gmdate('Ymd\\THis\\Z'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
$hasMethod = isset($this->parent->METHOD);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
'DTSTART' => $hasMethod ? '?' : '1',
|
||||||
|
'CLASS' => '?',
|
||||||
|
'CREATED' => '?',
|
||||||
|
'DESCRIPTION' => '?',
|
||||||
|
'GEO' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'LOCATION' => '?',
|
||||||
|
'ORGANIZER' => '?',
|
||||||
|
'PRIORITY' => '?',
|
||||||
|
'SEQUENCE' => '?',
|
||||||
|
'STATUS' => '?',
|
||||||
|
'SUMMARY' => '?',
|
||||||
|
'TRANSP' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
'RECURRENCE-ID' => '?',
|
||||||
|
'RRULE' => '?',
|
||||||
|
'DTEND' => '?',
|
||||||
|
'DURATION' => '?',
|
||||||
|
|
||||||
|
'ATTACH' => '*',
|
||||||
|
'ATTENDEE' => '*',
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'CONTACT' => '*',
|
||||||
|
'EXDATE' => '*',
|
||||||
|
'REQUEST-STATUS' => '*',
|
||||||
|
'RELATED-TO' => '*',
|
||||||
|
'RESOURCES' => '*',
|
||||||
|
'RDATE' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,33 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
use Sabre\VObject;
|
use Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The VFreeBusy component
|
* The VFreeBusy component.
|
||||||
*
|
*
|
||||||
* This component adds functionality to a component, specific for VFREEBUSY
|
* This component adds functionality to a component, specific for VFREEBUSY
|
||||||
* components.
|
* components.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @param DateTime $start
|
|
||||||
* @param Datetime $end
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isFree(\DateTime $start, \Datetime $end) {
|
public 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']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
|
if (isset($freebusy['FBTYPE']) && 'BUSY' !== strtoupper(substr((string) $freebusy['FBTYPE'], 0, 4))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,22 +45,49 @@ class VFreeBusy extends VObject\Component {
|
||||||
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
||||||
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
||||||
if ($busyEnd instanceof \DateInterval) {
|
if ($busyEnd instanceof \DateInterval) {
|
||||||
$tmp = clone $busyStart;
|
$busyEnd = $busyStart->add($busyEnd);
|
||||||
$tmp->add($busyEnd);
|
|
||||||
$busyEnd = $tmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($start < $busyEnd && $end > $busyStart) {
|
if ($start < $busyEnd && $end > $busyStart) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
|
||||||
|
'CONTACT' => '?',
|
||||||
|
'DTSTART' => '?',
|
||||||
|
'DTEND' => '?',
|
||||||
|
'ORGANIZER' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
|
||||||
|
'ATTENDEE' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'FREEBUSY' => '*',
|
||||||
|
'REQUEST-STATUS' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,20 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
use Sabre\VObject;
|
use Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VJournal component
|
* VJournal component.
|
||||||
*
|
*
|
||||||
* This component contains some additional functionality specific for VJOURNALs.
|
* This component contains some additional functionality specific for VJOURNALs.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -22,25 +23,79 @@ class VJournal extends VObject\Component {
|
||||||
* The rules used to determine if an event falls within the specified
|
* The rules used to determine if an event falls within the specified
|
||||||
* time-range is based on the CalDAV specification.
|
* time-range is based on the CalDAV specification.
|
||||||
*
|
*
|
||||||
* @param DateTime $start
|
|
||||||
* @param DateTime $end
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
public 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 = clone $dtstart;
|
$effectiveEnd = $dtstart;
|
||||||
if (!$this->DTSTART->hasTime()) {
|
if (!$this->DTSTART->hasTime()) {
|
||||||
$effectiveEnd->modify('+1 day');
|
$effectiveEnd = $effectiveEnd->modify('+1 day');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($start <= $effectiveEnd && $end > $dtstart);
|
return $start <= $effectiveEnd && $end > $dtstart;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
|
||||||
|
'CLASS' => '?',
|
||||||
|
'CREATED' => '?',
|
||||||
|
'DTSTART' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'ORGANIZER' => '?',
|
||||||
|
'RECURRENCE-ID' => '?',
|
||||||
|
'SEQUENCE' => '?',
|
||||||
|
'STATUS' => '?',
|
||||||
|
'SUMMARY' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
|
||||||
|
'RRULE' => '?',
|
||||||
|
|
||||||
|
'ATTACH' => '*',
|
||||||
|
'ATTENDEE' => '*',
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'CONTACT' => '*',
|
||||||
|
'DESCRIPTION' => '*',
|
||||||
|
'EXDATE' => '*',
|
||||||
|
'RELATED-TO' => '*',
|
||||||
|
'RDATE' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should return a list of default property values.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getDefaults()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(),
|
||||||
|
'DTSTAMP' => gmdate('Ymd\\THis\\Z'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The VTimeZone component.
|
||||||
|
*
|
||||||
|
* This component adds functionality to a component, specific for VTIMEZONE
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class VTimeZone extends VObject\Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the PHP DateTimeZone for this VTIMEZONE component.
|
||||||
|
*
|
||||||
|
* If we can't accurately determine the timezone, this method will return
|
||||||
|
* UTC.
|
||||||
|
*
|
||||||
|
* @return \DateTimeZone
|
||||||
|
*/
|
||||||
|
public function getTimeZone()
|
||||||
|
{
|
||||||
|
return VObject\TimeZoneUtil::getTimeZone((string) $this->TZID, $this->root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'TZID' => 1,
|
||||||
|
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'TZURL' => '?',
|
||||||
|
|
||||||
|
// At least 1 STANDARD or DAYLIGHT must appear.
|
||||||
|
//
|
||||||
|
// The validator is not specific yet to pick this up, so these
|
||||||
|
// rules are too loose.
|
||||||
|
'STANDARD' => '*',
|
||||||
|
'DAYLIGHT' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,19 +2,20 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Component;
|
namespace Sabre\VObject\Component;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
use Sabre\VObject;
|
use Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VTodo component
|
* VTodo component.
|
||||||
*
|
*
|
||||||
* This component contains some additional functionality specific for VTODOs.
|
* This component contains some additional functionality specific for VTODOs.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -22,12 +23,10 @@ class VTodo extends VObject\Component {
|
||||||
* The rules used to determine if an event falls within the specified
|
* The rules used to determine if an event falls within the specified
|
||||||
* time-range is based on the CalDAV specification.
|
* time-range is based on the CalDAV specification.
|
||||||
*
|
*
|
||||||
* @param DateTime $start
|
|
||||||
* @param DateTime $end
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
public 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;
|
||||||
|
|
@ -36,8 +35,8 @@ class VTodo extends VObject\Component {
|
||||||
|
|
||||||
if ($dtstart) {
|
if ($dtstart) {
|
||||||
if ($duration) {
|
if ($duration) {
|
||||||
$effectiveEnd = clone $dtstart;
|
$effectiveEnd = $dtstart->add($duration);
|
||||||
$effectiveEnd->add($duration);
|
|
||||||
return $start <= $effectiveEnd && $end > $dtstart;
|
return $start <= $effectiveEnd && $end > $dtstart;
|
||||||
} elseif ($due) {
|
} elseif ($due) {
|
||||||
return
|
return
|
||||||
|
|
@ -48,7 +47,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
|
||||||
|
|
@ -56,13 +55,127 @@ 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple list of validation rules.
|
||||||
|
*
|
||||||
|
* This is simply a list of properties, and how many times they either
|
||||||
|
* must or must not appear.
|
||||||
|
*
|
||||||
|
* Possible values per property:
|
||||||
|
* * 0 - Must not appear.
|
||||||
|
* * 1 - Must appear exactly once.
|
||||||
|
* * + - Must appear at least once.
|
||||||
|
* * * - Can appear any number of times.
|
||||||
|
* * ? - May appear, but not more than once.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public function getValidationRules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 1,
|
||||||
|
'DTSTAMP' => 1,
|
||||||
|
|
||||||
|
'CLASS' => '?',
|
||||||
|
'COMPLETED' => '?',
|
||||||
|
'CREATED' => '?',
|
||||||
|
'DESCRIPTION' => '?',
|
||||||
|
'DTSTART' => '?',
|
||||||
|
'GEO' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'LOCATION' => '?',
|
||||||
|
'ORGANIZER' => '?',
|
||||||
|
'PERCENT' => '?',
|
||||||
|
'PRIORITY' => '?',
|
||||||
|
'RECURRENCE-ID' => '?',
|
||||||
|
'SEQUENCE' => '?',
|
||||||
|
'STATUS' => '?',
|
||||||
|
'SUMMARY' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
|
||||||
|
'RRULE' => '?',
|
||||||
|
'DUE' => '?',
|
||||||
|
'DURATION' => '?',
|
||||||
|
|
||||||
|
'ATTACH' => '*',
|
||||||
|
'ATTENDEE' => '*',
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'COMMENT' => '*',
|
||||||
|
'CONTACT' => '*',
|
||||||
|
'EXDATE' => '*',
|
||||||
|
'REQUEST-STATUS' => '*',
|
||||||
|
'RELATED-TO' => '*',
|
||||||
|
'RESOURCES' => '*',
|
||||||
|
'RDATE' => '*',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$result = parent::validate($options);
|
||||||
|
if (isset($this->DUE) && isset($this->DTSTART)) {
|
||||||
|
$due = $this->DUE;
|
||||||
|
$dtStart = $this->DTSTART;
|
||||||
|
|
||||||
|
if ($due->getValueType() !== $dtStart->getValueType()) {
|
||||||
|
$result[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART',
|
||||||
|
'node' => $due,
|
||||||
|
];
|
||||||
|
} elseif ($due->getDateTime() < $dtStart->getDateTime()) {
|
||||||
|
$result[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'DUE must occur after DTSTART',
|
||||||
|
'node' => $due,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should return a list of default property values.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getDefaults()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(),
|
||||||
|
'DTSTAMP' => date('Ymd\\THis\\Z'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,67 +2,85 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DateTimeParser
|
* DateTimeParser.
|
||||||
*
|
*
|
||||||
* This class is responsible for parsing the several different date and time
|
* This class is responsible for parsing the several different date and time
|
||||||
* formats iCalendar and vCards have.
|
* formats iCalendar and vCards have.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class DateTimeParser {
|
class DateTimeParser
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
|
* Parses an iCalendar (rfc5545) formatted datetime and returns a
|
||||||
|
* DateTimeImmutable object.
|
||||||
*
|
*
|
||||||
* Specifying a reference timezone is optional. It will only be used
|
* Specifying a reference timezone is optional. It will only be used
|
||||||
* 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 DateTime 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 DateTime
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
static public function parseDateTime($dt,\DateTimeZone $tz = null) {
|
public 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 \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
|
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($matches[7]==='Z' || is_null($tz)) {
|
if ('Z' === $matches[7] || is_null($tz)) {
|
||||||
$tz = new \DateTimeZone('UTC');
|
$tz = new DateTimeZone('UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$date = new DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt);
|
||||||
}
|
}
|
||||||
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
|
|
||||||
|
|
||||||
// Still resetting the timezone, to normalize everything to UTC
|
|
||||||
// $date->setTimeZone(new \DateTimeZone('UTC'));
|
|
||||||
return $date;
|
return $date;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
|
* Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object.
|
||||||
*
|
*
|
||||||
* @param string $date
|
* @param string $date
|
||||||
* @return DateTime
|
* @param DateTimeZone $tz
|
||||||
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
static public function parseDate($date) {
|
public 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 \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
|
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||||
}
|
}
|
||||||
|
|
||||||
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
|
if (is_null($tz)) {
|
||||||
return $date;
|
$tz = new DateTimeZone('UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$date = new DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,108 +91,119 @@ class DateTimeParser {
|
||||||
*
|
*
|
||||||
* @param string $duration
|
* @param string $duration
|
||||||
* @param bool $asString
|
* @param bool $asString
|
||||||
|
*
|
||||||
* @return DateInterval|string
|
* @return DateInterval|string
|
||||||
*/
|
*/
|
||||||
static public function parseDuration($duration, $asString = false) {
|
public static function parseDuration($duration, $asString = false)
|
||||||
|
{
|
||||||
$result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<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 \LogicException('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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$parts = [
|
||||||
$parts = array(
|
|
||||||
'week',
|
'week',
|
||||||
'day',
|
'day',
|
||||||
'hour',
|
'hour',
|
||||||
'minute',
|
'minute',
|
||||||
'second',
|
'second',
|
||||||
);
|
];
|
||||||
|
|
||||||
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
|
||||||
// days are not supported by DateInterval in the same string.
|
// days are not supported by DateInterval in the same string.
|
||||||
$duration = 'P';
|
$duration = 'P';
|
||||||
$days = $matches['day'];
|
$days = $matches['day'];
|
||||||
|
|
||||||
if ($matches['week']) {
|
if ($matches['week']) {
|
||||||
$days += $matches['week'] * 7;
|
$days += $matches['week'] * 7;
|
||||||
}
|
}
|
||||||
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'])
|
|
||||||
$duration.=$matches['minute'].'M';
|
|
||||||
|
|
||||||
if ($matches['second'])
|
|
||||||
$duration.=$matches['second'].'S';
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($duration==='P') {
|
if ($matches['minute']) {
|
||||||
|
$duration .= $matches['minute'].'M';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matches['second']) {
|
||||||
|
$duration .= $matches['second'].'S';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('P' === $duration) {
|
||||||
$duration = 'PT0S';
|
$duration = 'PT0S';
|
||||||
}
|
}
|
||||||
$iv = new \DateInterval($duration);
|
|
||||||
if ($invert) $iv->invert = true;
|
|
||||||
|
|
||||||
return $iv;
|
$iv = new DateInterval($duration);
|
||||||
|
|
||||||
|
if ($invert) {
|
||||||
|
$iv->invert = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = [
|
||||||
$parts = array(
|
|
||||||
'week',
|
'week',
|
||||||
'day',
|
'day',
|
||||||
'hour',
|
'hour',
|
||||||
'minute',
|
'minute',
|
||||||
'second',
|
'second',
|
||||||
);
|
];
|
||||||
|
|
||||||
$newDur = '';
|
$newDur = '';
|
||||||
|
|
||||||
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 === '+') { $newDur = '+0 seconds'; };
|
|
||||||
return $newDur;
|
|
||||||
|
|
||||||
|
if ('+' === $newDur) {
|
||||||
|
$newDur = '+0 seconds';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 DateTime|DateInterval
|
*
|
||||||
|
* @return DateTimeImmutable|DateInterval
|
||||||
*/
|
*/
|
||||||
static public function parse($date, $referenceTZ = null) {
|
public static function parse($date, $referenceTz = null)
|
||||||
|
{
|
||||||
if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
|
if ('P' === $date[0] || ('-' === $date[0] && 'P' === $date[1])) {
|
||||||
return self::parseDuration($date);
|
return self::parseDuration($date);
|
||||||
} elseif (strlen($date)===8) {
|
} elseif (8 === strlen($date)) {
|
||||||
return self::parseDate($date);
|
return self::parseDate($date, $referenceTz);
|
||||||
} else {
|
} else {
|
||||||
return self::parseDateTime($date, $referenceTZ);
|
return self::parseDateTime($date, $referenceTz);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -191,7 +220,7 @@ class DateTimeParser {
|
||||||
* Almost any part of the string may be omitted. It's for example legal to
|
* Almost any part of the string may be omitted. It's for example legal to
|
||||||
* just specify seconds, leave out the year, etc.
|
* just specify seconds, leave out the year, etc.
|
||||||
*
|
*
|
||||||
* Timezone is either returned as 'Z' or as '+08:00'
|
* Timezone is either returned as 'Z' or as '+0800'
|
||||||
*
|
*
|
||||||
* For any non-specified values null is returned.
|
* For any non-specified values null is returned.
|
||||||
*
|
*
|
||||||
|
|
@ -230,23 +259,25 @@ class DateTimeParser {
|
||||||
* UTC, or a string like -0500 or +1100.
|
* UTC, or a string like -0500 or +1100.
|
||||||
*
|
*
|
||||||
* @param string $date
|
* @param string $date
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
static public function parseVCardDateTime($date) {
|
public static function parseVCardDateTime($date)
|
||||||
|
{
|
||||||
$regex = '/^
|
$regex = '/^
|
||||||
(?: # date part
|
(?: # date part
|
||||||
(?:
|
(?:
|
||||||
(?: (?P<year> [0-9]{4}) (?: -)?| --)
|
(?: (?<year> [0-9]{4}) (?: -)?| --)
|
||||||
(?P<month> [0-9]{2})?
|
(?<month> [0-9]{2})?
|
||||||
|---)
|
|---)
|
||||||
(?P<date> [0-9]{2})?
|
(?<date> [0-9]{2})?
|
||||||
)?
|
)?
|
||||||
(?:T # time part
|
(?:T # time part
|
||||||
(?P<hour> [0-9]{2} | -)
|
(?<hour> [0-9]{2} | -)
|
||||||
(?P<minute> [0-9]{2} | -)?
|
(?<minute> [0-9]{2} | -)?
|
||||||
(?P<second> [0-9]{2})?
|
(?<second> [0-9]{2})?
|
||||||
|
|
||||||
|
(?: \.[0-9]{3})? # milliseconds
|
||||||
(?P<timezone> # timezone offset
|
(?P<timezone> # timezone offset
|
||||||
|
|
||||||
Z | (?: \+|-)(?: [0-9]{4})
|
Z | (?: \+|-)(?: [0-9]{4})
|
||||||
|
|
@ -256,22 +287,21 @@ 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
|
||||||
(?: (?P<year> [0-9]{4}) - | -- )
|
(?: (?<year> [0-9]{4}) - | -- )
|
||||||
(?P<month> [0-9]{2}) -
|
(?<month> [0-9]{2}) -
|
||||||
(?P<date> [0-9]{2})
|
(?<date> [0-9]{2})
|
||||||
)?
|
)?
|
||||||
(?:T # time part
|
(?:T # time part
|
||||||
|
|
||||||
(?: (?P<hour> [0-9]{2}) : | -)
|
(?: (?<hour> [0-9]{2}) : | -)
|
||||||
(?: (?P<minute> [0-9]{2}) : | -)?
|
(?: (?<minute> [0-9]{2}) : | -)?
|
||||||
(?P<second> [0-9]{2})?
|
(?<second> [0-9]{2})?
|
||||||
|
|
||||||
|
(?: \.[0-9]{3})? # milliseconds
|
||||||
(?P<timezone> # timezone offset
|
(?P<timezone> # timezone offset
|
||||||
|
|
||||||
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
||||||
|
|
@ -282,35 +312,31 @@ class DateTimeParser {
|
||||||
$/x';
|
$/x';
|
||||||
|
|
||||||
if (!preg_match($regex, $date, $matches)) {
|
if (!preg_match($regex, $date, $matches)) {
|
||||||
throw new \InvalidArgumentException('Invalid vCard date-time string: ' . $date);
|
throw new InvalidDataException('Invalid vCard date-time string: '.$date);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$parts = array(
|
$parts = [
|
||||||
'year',
|
'year',
|
||||||
'month',
|
'month',
|
||||||
'date',
|
'date',
|
||||||
'hour',
|
'hour',
|
||||||
'minute',
|
'minute',
|
||||||
'second',
|
'second',
|
||||||
'timezone'
|
'timezone',
|
||||||
);
|
];
|
||||||
|
|
||||||
$result = array();
|
$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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -352,15 +378,17 @@ class DateTimeParser {
|
||||||
* UTC, or a string like -0500 or +11:00.
|
* UTC, or a string like -0500 or +11:00.
|
||||||
*
|
*
|
||||||
* @param string $date
|
* @param string $date
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
static public function parseVCardTime($date) {
|
public static function parseVCardTime($date)
|
||||||
|
{
|
||||||
$regex = '/^
|
$regex = '/^
|
||||||
(?P<hour> [0-9]{2} | -)
|
(?<hour> [0-9]{2} | -)
|
||||||
(?P<minute> [0-9]{2} | -)?
|
(?<minute> [0-9]{2} | -)?
|
||||||
(?P<second> [0-9]{2})?
|
(?<second> [0-9]{2})?
|
||||||
|
|
||||||
|
(?: \.[0-9]{3})? # milliseconds
|
||||||
(?P<timezone> # timezone offset
|
(?P<timezone> # timezone offset
|
||||||
|
|
||||||
Z | (?: \+|-)(?: [0-9]{4})
|
Z | (?: \+|-)(?: [0-9]{4})
|
||||||
|
|
@ -368,15 +396,14 @@ 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 = '/^
|
||||||
(?: (?P<hour> [0-9]{2}) : | -)
|
(?: (?<hour> [0-9]{2}) : | -)
|
||||||
(?: (?P<minute> [0-9]{2}) : | -)?
|
(?: (?<minute> [0-9]{2}) : | -)?
|
||||||
(?P<second> [0-9]{2})?
|
(?<second> [0-9]{2})?
|
||||||
|
|
||||||
|
(?: \.[0-9]{3})? # milliseconds
|
||||||
(?P<timezone> # timezone offset
|
(?P<timezone> # timezone offset
|
||||||
|
|
||||||
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
||||||
|
|
@ -385,31 +412,149 @@ class DateTimeParser {
|
||||||
$/x';
|
$/x';
|
||||||
|
|
||||||
if (!preg_match($regex, $date, $matches)) {
|
if (!preg_match($regex, $date, $matches)) {
|
||||||
throw new \InvalidArgumentException('Invalid vCard time string: ' . $date);
|
throw new InvalidDataException('Invalid vCard time string: '.$date);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$parts = array(
|
$parts = [
|
||||||
'hour',
|
'hour',
|
||||||
'minute',
|
'minute',
|
||||||
'second',
|
'second',
|
||||||
'timezone'
|
'timezone',
|
||||||
);
|
];
|
||||||
|
|
||||||
$result = array();
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method parses a vCard date and or time value.
|
||||||
|
*
|
||||||
|
* This can be used for the DATE, DATE-TIME and
|
||||||
|
* DATE-AND-OR-TIME value.
|
||||||
|
*
|
||||||
|
* This method returns an array, not a DateTime value.
|
||||||
|
* The elements in the array are in the following order:
|
||||||
|
* year, month, date, hour, minute, second, timezone
|
||||||
|
* Almost any part of the string may be omitted. It's for example legal to
|
||||||
|
* just specify seconds, leave out the year, etc.
|
||||||
|
*
|
||||||
|
* Timezone is either returned as 'Z' or as '+0800'
|
||||||
|
*
|
||||||
|
* For any non-specified values null is returned.
|
||||||
|
*
|
||||||
|
* List of date formats that are supported:
|
||||||
|
* 20150128
|
||||||
|
* 2015-01
|
||||||
|
* --01
|
||||||
|
* --0128
|
||||||
|
* ---28
|
||||||
|
*
|
||||||
|
* List of supported time formats:
|
||||||
|
* 13
|
||||||
|
* 1353
|
||||||
|
* 135301
|
||||||
|
* -53
|
||||||
|
* -5301
|
||||||
|
* --01 (unreachable, see the tests)
|
||||||
|
* --01Z
|
||||||
|
* --01+1234
|
||||||
|
*
|
||||||
|
* List of supported date-time formats:
|
||||||
|
* 20150128T13
|
||||||
|
* --0128T13
|
||||||
|
* ---28T13
|
||||||
|
* ---28T1353
|
||||||
|
* ---28T135301
|
||||||
|
* ---28T13Z
|
||||||
|
* ---28T13+1234
|
||||||
|
*
|
||||||
|
* See the regular expressions for all the possible patterns.
|
||||||
|
*
|
||||||
|
* Times may be postfixed by a timezone offset. This can be either 'Z' for
|
||||||
|
* UTC, or a string like -0500 or +1100.
|
||||||
|
*
|
||||||
|
* @param string $date
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function parseVCardDateAndOrTime($date)
|
||||||
|
{
|
||||||
|
// \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d
|
||||||
|
$valueDate = '/^(?J)(?:'.
|
||||||
|
'(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)'.
|
||||||
|
'|(?<year>\d{4})-(?<month>\d\d)'.
|
||||||
|
'|--(?<month>\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)?)?
|
||||||
|
$valueTime = '/^(?J)(?:'.
|
||||||
|
'((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?'.
|
||||||
|
'|-(?<minute>\d\d)(?<second>\d\d)?'.
|
||||||
|
'|--(?<second>\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?)?
|
||||||
|
$valueDateTime = '/^(?:'.
|
||||||
|
'((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)'.
|
||||||
|
'|--(?<month1>\d\d)(?<date1>\d\d)'.
|
||||||
|
'|---(?<date2>\d\d))'.
|
||||||
|
'T'.
|
||||||
|
'(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?'.
|
||||||
|
'(?<timezone>(Z|[+\-]\d\d(\d\d?)))?'.
|
||||||
|
')$/';
|
||||||
|
|
||||||
|
// date-and-or-time is date | date-time | time
|
||||||
|
// in this strict order.
|
||||||
|
|
||||||
|
if (0 === preg_match($valueDate, $date, $matches)
|
||||||
|
&& 0 === preg_match($valueDateTime, $date, $matches)
|
||||||
|
&& 0 === preg_match($valueTime, $date, $matches)) {
|
||||||
|
throw new InvalidDataException('Invalid vCard date-time string: '.$date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = [
|
||||||
|
'year' => null,
|
||||||
|
'month' => null,
|
||||||
|
'date' => null,
|
||||||
|
'hour' => null,
|
||||||
|
'minute' => null,
|
||||||
|
'second' => null,
|
||||||
|
'timezone' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
// The $valueDateTime expression has a bug with (?J) so we simulate it.
|
||||||
|
$parts['date0'] = &$parts['date'];
|
||||||
|
$parts['date1'] = &$parts['date'];
|
||||||
|
$parts['date2'] = &$parts['date'];
|
||||||
|
$parts['month0'] = &$parts['month'];
|
||||||
|
$parts['month1'] = &$parts['month'];
|
||||||
|
$parts['year0'] = &$parts['year'];
|
||||||
|
|
||||||
|
foreach ($parts as $part => &$value) {
|
||||||
|
if (!empty($matches[$part])) {
|
||||||
|
$value = $matches[$part];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($parts['date0']);
|
||||||
|
unset($parts['date1']);
|
||||||
|
unset($parts['date2']);
|
||||||
|
unset($parts['month0']);
|
||||||
|
unset($parts['month1']);
|
||||||
|
unset($parts['year0']);
|
||||||
|
|
||||||
|
return $parts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document
|
* Document.
|
||||||
*
|
*
|
||||||
* A document is just like a component, except that it's also the top level
|
* A document is just like a component, except that it's also the top level
|
||||||
* element.
|
* element.
|
||||||
|
|
@ -12,39 +12,39 @@ namespace Sabre\VObject;
|
||||||
*
|
*
|
||||||
* This class also provides a registry for document types.
|
* This class also provides a registry for document types.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
abstract class Document extends Component {
|
abstract class Document extends Component
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Unknown document type
|
* Unknown document type.
|
||||||
*/
|
*/
|
||||||
const UNKNOWN = 1;
|
const UNKNOWN = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vCalendar 1.0
|
* vCalendar 1.0.
|
||||||
*/
|
*/
|
||||||
const VCALENDAR10 = 2;
|
const VCALENDAR10 = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iCalendar 2.0
|
* iCalendar 2.0.
|
||||||
*/
|
*/
|
||||||
const ICALENDAR20 = 3;
|
const ICALENDAR20 = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vCard 2.1
|
* vCard 2.1.
|
||||||
*/
|
*/
|
||||||
const VCARD21 = 4;
|
const VCARD21 = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vCard 3.0
|
* vCard 3.0.
|
||||||
*/
|
*/
|
||||||
const VCARD30 = 5;
|
const VCARD30 = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vCard 4.0
|
* vCard 4.0.
|
||||||
*/
|
*/
|
||||||
const VCARD40 = 6;
|
const VCARD40 = 6;
|
||||||
|
|
||||||
|
|
@ -55,28 +55,28 @@ abstract class Document extends Component {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
static public $defaultName;
|
public static $defaultName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of properties, and which classes they map to.
|
* List of properties, and which classes they map to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
static public $propertyMap = array();
|
public 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
|
||||||
*/
|
*/
|
||||||
static public $componentMap = array();
|
public 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
|
||||||
*/
|
*/
|
||||||
static public $valueMap = array();
|
public static $valueMap = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new document.
|
* Creates a new document.
|
||||||
|
|
@ -89,33 +89,32 @@ abstract class Document extends Component {
|
||||||
*
|
*
|
||||||
* So the two sigs:
|
* So the two sigs:
|
||||||
*
|
*
|
||||||
* new Document(array $children = array(), $defaults = true);
|
* new Document(array $children = [], $defaults = true);
|
||||||
* new Document(string $name, array $children = array(), $defaults = true)
|
* new Document(string $name, array $children = [], $defaults = true)
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct()
|
||||||
|
{
|
||||||
$args = func_get_args();
|
$args = func_get_args();
|
||||||
if (count($args)===0 || is_array($args[0])) {
|
$name = static::$defaultName;
|
||||||
array_unshift($args, $this, static::$defaultName);
|
if (0 === count($args) || is_array($args[0])) {
|
||||||
call_user_func_array(array('parent', '__construct'), $args);
|
$children = isset($args[0]) ? $args[0] : [];
|
||||||
|
$defaults = isset($args[1]) ? $args[1] : true;
|
||||||
} else {
|
} else {
|
||||||
array_unshift($args, $this);
|
$name = $args[0];
|
||||||
call_user_func_array(array('parent', '__construct'), $args);
|
$children = isset($args[1]) ? $args[1] : [];
|
||||||
|
$defaults = isset($args[2]) ? $args[2] : true;
|
||||||
}
|
}
|
||||||
|
parent::__construct($this, $name, $children, $defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current document type.
|
* Returns the current document type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getDocumentType() {
|
public function getDocumentType()
|
||||||
|
{
|
||||||
return self::UNKNOWN;
|
return self::UNKNOWN;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -126,24 +125,20 @@ abstract class Document extends Component {
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $arg1,... Unlimited number of args
|
* @param string $arg1,... Unlimited number of args
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function create($name) {
|
public 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(array($this,'createComponent'), func_get_args());
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
return call_user_func_array([$this, 'createProperty'], func_get_args());
|
||||||
return call_user_func_array(array($this,'createProperty'), func_get_args());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new component
|
* Creates a new component.
|
||||||
*
|
*
|
||||||
* This method automatically searches for the correct component class, based
|
* This method automatically searches for the correct component class, based
|
||||||
* on its name.
|
* on its name.
|
||||||
|
|
@ -159,23 +154,26 @@ abstract class Document extends Component {
|
||||||
* @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) {
|
public function createComponent($name, array $children = null, $defaults = true)
|
||||||
|
{
|
||||||
$name = strtoupper($name);
|
$name = strtoupper($name);
|
||||||
$class = 'Sabre\\VObject\\Component';
|
$class = Component::class;
|
||||||
|
|
||||||
if (isset(static::$componentMap[$name])) {
|
if (isset(static::$componentMap[$name])) {
|
||||||
$class = static::$componentMap[$name];
|
$class = static::$componentMap[$name];
|
||||||
}
|
}
|
||||||
if (is_null($children)) $children = array();
|
if (is_null($children)) {
|
||||||
return new $class($this, $name, $children, $defaults);
|
$children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $class($this, $name, $children, $defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for creating new properties
|
* Factory method for creating new properties.
|
||||||
*
|
*
|
||||||
* This method automatically searches for the correct property class, based
|
* This method automatically searches for the correct property class, based
|
||||||
* on its name.
|
* on its name.
|
||||||
|
|
@ -188,12 +186,13 @@ abstract class Document extends Component {
|
||||||
* @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) {
|
public function createProperty($name, $value = null, array $parameters = null, $valueType = null)
|
||||||
|
{
|
||||||
// If there's a . in the name, it means it's prefixed by a groupname.
|
// If there's a . in the name, it means it's prefixed by a groupname.
|
||||||
if (($i=strpos($name,'.'))!==false) {
|
if (false !== ($i = strpos($name, '.'))) {
|
||||||
$group = substr($name, 0, $i);
|
$group = substr($name, 0, $i);
|
||||||
$name = strtoupper(substr($name, $i + 1));
|
$name = strtoupper(substr($name, $i + 1));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -209,17 +208,22 @@ abstract class Document extends Component {
|
||||||
$class = $this->getClassNameForPropertyValue($valueType);
|
$class = $this->getClassNameForPropertyValue($valueType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($class) && isset($parameters['VALUE'])) {
|
|
||||||
// If a VALUE parameter is supplied, we should use that.
|
|
||||||
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
|
|
||||||
}
|
|
||||||
if (is_null($class)) {
|
if (is_null($class)) {
|
||||||
|
// If a VALUE parameter is supplied, we should use that.
|
||||||
|
if (isset($parameters['VALUE'])) {
|
||||||
|
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
|
||||||
|
if (is_null($class)) {
|
||||||
|
throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
$class = $this->getClassNameForPropertyName($name);
|
$class = $this->getClassNameForPropertyName($name);
|
||||||
}
|
}
|
||||||
if (is_null($parameters)) $parameters = array();
|
}
|
||||||
|
if (is_null($parameters)) {
|
||||||
|
$parameters = [];
|
||||||
|
}
|
||||||
|
|
||||||
return new $class($this, $name, $value, $parameters, $group);
|
return new $class($this, $name, $value, $parameters, $group);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -231,31 +235,30 @@ 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 void
|
*
|
||||||
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getClassNameForPropertyValue($valueParam) {
|
public 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];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the default class for a property name.
|
* Returns the default class for a property name.
|
||||||
*
|
*
|
||||||
* @param string $propertyName
|
* @param string $propertyName
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getClassNameForPropertyName($propertyName) {
|
public 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 Property\Unknown::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,155 +2,33 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VObject ElementList
|
* VObject ElementList.
|
||||||
*
|
*
|
||||||
* This class represents a list of elements. Lists are the result of queries,
|
* This class represents a list of elements. Lists are the result of queries,
|
||||||
* such as doing $vcalendar->vevent where there's multiple VEVENT objects.
|
* such as doing $vcalendar->vevent where there's multiple VEVENT objects.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class ElementList implements \Iterator, \Countable, \ArrayAccess {
|
class ElementList extends ArrayIterator
|
||||||
|
{
|
||||||
/**
|
|
||||||
* Inner elements
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $elements = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the element list.
|
|
||||||
*
|
|
||||||
* @param array $elements
|
|
||||||
*/
|
|
||||||
public function __construct(array $elements) {
|
|
||||||
|
|
||||||
$this->elements = $elements;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* {{{ Iterator interface */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current position
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $key = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns current item in iteration
|
|
||||||
*
|
|
||||||
* @return Element
|
|
||||||
*/
|
|
||||||
public function current() {
|
|
||||||
|
|
||||||
return $this->elements[$this->key];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To the next item in the iterator
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function next() {
|
|
||||||
|
|
||||||
$this->key++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current iterator key
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function key() {
|
|
||||||
|
|
||||||
return $this->key;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the current position in the iterator is a valid one
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function valid() {
|
|
||||||
|
|
||||||
return isset($this->elements[$this->key]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewinds the iterator
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function rewind() {
|
|
||||||
|
|
||||||
$this->key = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* }}} */
|
|
||||||
|
|
||||||
/* {{{ Countable interface */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of elements
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function count() {
|
|
||||||
|
|
||||||
return count($this->elements);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* }}} */
|
|
||||||
|
|
||||||
/* {{{ ArrayAccess Interface */
|
/* {{{ ArrayAccess Interface */
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an item exists through ArrayAccess.
|
|
||||||
*
|
|
||||||
* @param int $offset
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function offsetExists($offset) {
|
|
||||||
|
|
||||||
return isset($this->elements[$offset]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an item through ArrayAccess.
|
|
||||||
*
|
|
||||||
* @param int $offset
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset) {
|
|
||||||
|
|
||||||
return $this->elements[$offset];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an item through ArrayAccess.
|
* Sets an item through ArrayAccess.
|
||||||
*
|
*
|
||||||
* @param int $offset
|
* @param int $offset
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function offsetSet($offset,$value) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,14 +37,12 @@ class ElementList implements \Iterator, \Countable, \ArrayAccess {
|
||||||
* 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) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ namespace Sabre\VObject;
|
||||||
* Exception thrown by parser when the end of the stream has been reached,
|
* Exception thrown by parser when the end of the stream has been reached,
|
||||||
* before this was expected.
|
* before this was expected.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class EofException extends ParseException { }
|
class EofException extends ParseException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
||||||
185
snappymail/v/0.0.0/app/libraries/Sabre/VObject/FreeBusyData.php
Normal file
185
snappymail/v/0.0.0/app/libraries/Sabre/VObject/FreeBusyData.php
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FreeBusyData is a helper class that manages freebusy information.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class FreeBusyData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Start timestamp.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End timestamp.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of free-busy times.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
public function __construct($start, $end)
|
||||||
|
{
|
||||||
|
$this->start = $start;
|
||||||
|
$this->end = $end;
|
||||||
|
$this->data = [];
|
||||||
|
|
||||||
|
$this->data[] = [
|
||||||
|
'start' => $this->start,
|
||||||
|
'end' => $this->end,
|
||||||
|
'type' => 'FREE',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds free or busytime to the data.
|
||||||
|
*
|
||||||
|
* @param int $start
|
||||||
|
* @param int $end
|
||||||
|
* @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE
|
||||||
|
*/
|
||||||
|
public function add($start, $end, $type)
|
||||||
|
{
|
||||||
|
if ($start > $this->end || $end < $this->start) {
|
||||||
|
// This new data is outside our timerange.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($start < $this->start) {
|
||||||
|
// The item starts before our requested time range
|
||||||
|
$start = $this->start;
|
||||||
|
}
|
||||||
|
if ($end > $this->end) {
|
||||||
|
// The item ends after our requested time range
|
||||||
|
$end = $this->end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finding out where we need to insert the new item.
|
||||||
|
$currentIndex = 0;
|
||||||
|
while ($start > $this->data[$currentIndex]['end']) {
|
||||||
|
++$currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The standard insertion point will be one _after_ the first
|
||||||
|
// overlapping item.
|
||||||
|
$insertStartIndex = $currentIndex + 1;
|
||||||
|
|
||||||
|
$newItem = [
|
||||||
|
'start' => $start,
|
||||||
|
'end' => $end,
|
||||||
|
'type' => $type,
|
||||||
|
];
|
||||||
|
|
||||||
|
$precedingItem = $this->data[$insertStartIndex - 1];
|
||||||
|
if ($this->data[$insertStartIndex - 1]['start'] === $start) {
|
||||||
|
// The old item starts at the exact same point as the new item.
|
||||||
|
--$insertStartIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we know where to insert the item, we need to know where it
|
||||||
|
// starts overlapping with items on the tail end. We need to start
|
||||||
|
// looking one item before the insertStartIndex, because it's possible
|
||||||
|
// that the new item 'sits inside' the previous old item.
|
||||||
|
if ($insertStartIndex > 0) {
|
||||||
|
$currentIndex = $insertStartIndex - 1;
|
||||||
|
} else {
|
||||||
|
$currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($end > $this->data[$currentIndex]['end']) {
|
||||||
|
++$currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// What we are about to insert into the array
|
||||||
|
$newItems = [
|
||||||
|
$newItem,
|
||||||
|
];
|
||||||
|
|
||||||
|
// This is the amount of items that are completely overwritten by the
|
||||||
|
// new item.
|
||||||
|
$itemsToDelete = $currentIndex - $insertStartIndex;
|
||||||
|
if ($this->data[$currentIndex]['end'] <= $end) {
|
||||||
|
++$itemsToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If itemsToDelete was -1, it means that the newly inserted item is
|
||||||
|
// 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
|
||||||
|
// between.
|
||||||
|
if (-1 === $itemsToDelete) {
|
||||||
|
$itemsToDelete = 0;
|
||||||
|
if ($newItem['end'] < $precedingItem['end']) {
|
||||||
|
$newItems[] = [
|
||||||
|
'start' => $newItem['end'] + 1,
|
||||||
|
'end' => $precedingItem['end'],
|
||||||
|
'type' => $precedingItem['type'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_splice(
|
||||||
|
$this->data,
|
||||||
|
$insertStartIndex,
|
||||||
|
$itemsToDelete,
|
||||||
|
$newItems
|
||||||
|
);
|
||||||
|
|
||||||
|
$doMerge = false;
|
||||||
|
$mergeOffset = $insertStartIndex;
|
||||||
|
$mergeItem = $newItem;
|
||||||
|
$mergeDelete = 1;
|
||||||
|
|
||||||
|
if (isset($this->data[$insertStartIndex - 1])) {
|
||||||
|
// Updating the start time of the previous item.
|
||||||
|
$this->data[$insertStartIndex - 1]['end'] = $start;
|
||||||
|
|
||||||
|
// If the previous and the current are of the same type, we can
|
||||||
|
// merge them into one item.
|
||||||
|
if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) {
|
||||||
|
$doMerge = true;
|
||||||
|
--$mergeOffset;
|
||||||
|
++$mergeDelete;
|
||||||
|
$mergeItem['start'] = $this->data[$insertStartIndex - 1]['start'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($this->data[$insertStartIndex + 1])) {
|
||||||
|
// Updating the start time of the next item.
|
||||||
|
$this->data[$insertStartIndex + 1]['start'] = $end;
|
||||||
|
|
||||||
|
// If the next and the current are of the same type, we can
|
||||||
|
// merge them into one item.
|
||||||
|
if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) {
|
||||||
|
$doMerge = true;
|
||||||
|
++$mergeDelete;
|
||||||
|
$mergeItem['end'] = $this->data[$insertStartIndex + 1]['end'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($doMerge) {
|
||||||
|
array_splice(
|
||||||
|
$this->data,
|
||||||
|
$mergeOffset,
|
||||||
|
$mergeDelete,
|
||||||
|
[$mergeItem]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData()
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
use
|
use DateTimeImmutable;
|
||||||
Sabre\VObject\Component\VCalendar;
|
use DateTimeInterface;
|
||||||
|
use DateTimeZone;
|
||||||
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
use Sabre\VObject\Recur\EventIterator;
|
||||||
|
use Sabre\VObject\Recur\NoInstancesException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class helps with generating FREEBUSY reports based on existing sets of
|
* This class helps with generating FREEBUSY reports based on existing sets of
|
||||||
|
|
@ -15,61 +19,87 @@ use
|
||||||
* VFREEBUSY components are described in RFC5545, The rules for what should
|
* VFREEBUSY components are described in RFC5545, The rules for what should
|
||||||
* go in a single freebusy report is taken from RFC4791, section 7.10.
|
* go in a single freebusy report is taken from RFC4791, section 7.10.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class FreeBusyGenerator {
|
class FreeBusyGenerator
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Input objects
|
* Input objects.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $objects;
|
protected $objects = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start of range
|
* Start of range.
|
||||||
*
|
*
|
||||||
* @var DateTime|null
|
* @var DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
protected $start;
|
protected $start;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End of range
|
* End of range.
|
||||||
*
|
*
|
||||||
* @var DateTime|null
|
* @var DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
protected $end;
|
protected $end;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VCALENDAR object
|
* VCALENDAR object.
|
||||||
*
|
*
|
||||||
* @var Component
|
* @var Document
|
||||||
*/
|
*/
|
||||||
protected $baseObject;
|
protected $baseObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference timezone.
|
||||||
|
*
|
||||||
|
* When we are calculating busy times, and we come across so-called
|
||||||
|
* floating times (times without a timezone), we use the reference timezone
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* This is also used for all-day events.
|
||||||
|
*
|
||||||
|
* This defaults to UTC.
|
||||||
|
*
|
||||||
|
* @var DateTimeZone
|
||||||
|
*/
|
||||||
|
protected $timeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A VAVAILABILITY document.
|
||||||
|
*
|
||||||
|
* If this is set, its information will be included when calculating
|
||||||
|
* freebusy time.
|
||||||
|
*
|
||||||
|
* @var Document
|
||||||
|
*/
|
||||||
|
protected $vavailability;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the generator.
|
* Creates the generator.
|
||||||
*
|
*
|
||||||
* Check the setTimeRange and setObjects methods for details about the
|
* Check the setTimeRange and setObjects methods for details about the
|
||||||
* arguments.
|
* arguments.
|
||||||
*
|
*
|
||||||
* @param DateTime $start
|
* @param DateTimeInterface $start
|
||||||
* @param DateTime $end
|
* @param DateTimeInterface $end
|
||||||
* @param mixed $objects
|
* @param mixed $objects
|
||||||
* @return void
|
* @param DateTimeZone $timeZone
|
||||||
*/
|
*/
|
||||||
public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
|
public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null)
|
||||||
|
{
|
||||||
if ($start && $end) {
|
|
||||||
$this->setTimeRange($start, $end);
|
$this->setTimeRange($start, $end);
|
||||||
}
|
|
||||||
|
|
||||||
if ($objects) {
|
if ($objects) {
|
||||||
$this->setObjects($objects);
|
$this->setObjects($objects);
|
||||||
}
|
}
|
||||||
|
if (is_null($timeZone)) {
|
||||||
|
$timeZone = new DateTimeZone('UTC');
|
||||||
|
}
|
||||||
|
$this->setTimeZone($timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,61 +109,73 @@ class FreeBusyGenerator {
|
||||||
* for setting things like the METHOD, CALSCALE, VERSION, etc..
|
* for setting things like the METHOD, CALSCALE, VERSION, etc..
|
||||||
*
|
*
|
||||||
* The VFREEBUSY object will be automatically added though.
|
* The VFREEBUSY object will be automatically added though.
|
||||||
*
|
|
||||||
* @param Component $vcalendar
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setBaseObject(Component $vcalendar) {
|
public function setBaseObject(Document $vcalendar)
|
||||||
|
{
|
||||||
$this->baseObject = $vcalendar;
|
$this->baseObject = $vcalendar;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the input objects
|
* Sets a VAVAILABILITY document.
|
||||||
|
*/
|
||||||
|
public function setVAvailability(Document $vcalendar)
|
||||||
|
{
|
||||||
|
$this->vavailability = $vcalendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input objects.
|
||||||
*
|
*
|
||||||
* You must either specify a valendar object as a strong, or as the parse
|
* You must either specify a vcalendar object as a string, or as the parse
|
||||||
* Component.
|
* Component.
|
||||||
* 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) {
|
public function setObjects($objects)
|
||||||
|
{
|
||||||
if (!is_array($objects)) {
|
if (!is_array($objects)) {
|
||||||
$objects = array($objects);
|
$objects = [$objects];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->objects = array();
|
$this->objects = [];
|
||||||
foreach ($objects as $object) {
|
foreach ($objects as $object) {
|
||||||
|
if (is_string($object) || is_resource($object)) {
|
||||||
if (is_string($object)) {
|
|
||||||
$this->objects[] = Reader::read($object);
|
$this->objects[] = Reader::read($object);
|
||||||
} elseif ($object instanceof Component) {
|
} elseif ($object instanceof Component) {
|
||||||
$this->objects[] = $object;
|
$this->objects[] = $object;
|
||||||
} 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the time range
|
* Sets the time range.
|
||||||
*
|
*
|
||||||
* Any freebusy object falling outside of this time range will be ignored.
|
* Any freebusy object falling outside of this time range will be ignored.
|
||||||
*
|
*
|
||||||
* @param DateTime $start
|
* @param DateTimeInterface $start
|
||||||
* @param DateTime $end
|
* @param DateTimeInterface $end
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
|
public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null)
|
||||||
|
{
|
||||||
|
if (!$start) {
|
||||||
|
$start = new DateTimeImmutable(Settings::$minDate);
|
||||||
|
}
|
||||||
|
if (!$end) {
|
||||||
|
$end = new DateTimeImmutable(Settings::$maxDate);
|
||||||
|
}
|
||||||
$this->start = $start;
|
$this->start = $start;
|
||||||
$this->end = $end;
|
$this->end = $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reference timezone for floating times.
|
||||||
|
*/
|
||||||
|
public function setTimeZone(DateTimeZone $timeZone)
|
||||||
|
{
|
||||||
|
$this->timeZone = $timeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -142,141 +184,317 @@ class FreeBusyGenerator {
|
||||||
*
|
*
|
||||||
* @return Component
|
* @return Component
|
||||||
*/
|
*/
|
||||||
public function getResult() {
|
public function getResult()
|
||||||
|
{
|
||||||
|
$fbData = new FreeBusyData(
|
||||||
|
$this->start->getTimeStamp(),
|
||||||
|
$this->end->getTimeStamp()
|
||||||
|
);
|
||||||
|
if ($this->vavailability) {
|
||||||
|
$this->calculateAvailability($fbData, $this->vavailability);
|
||||||
|
}
|
||||||
|
|
||||||
$busyTimes = array();
|
$this->calculateBusy($fbData, $this->objects);
|
||||||
|
|
||||||
foreach($this->objects as $object) {
|
return $this->generateFreeBusyCalendar($fbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes a VAVAILABILITY component and figures out all the
|
||||||
|
* available times.
|
||||||
|
*/
|
||||||
|
protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability)
|
||||||
|
{
|
||||||
|
$vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
|
||||||
|
usort(
|
||||||
|
$vavailComps,
|
||||||
|
function ($a, $b) {
|
||||||
|
// We need to order the components by priority. Priority 1
|
||||||
|
// comes first, up until priority 9. Priority 0 comes after
|
||||||
|
// priority 9. No priority implies priority 0.
|
||||||
|
//
|
||||||
|
// Yes, I'm serious.
|
||||||
|
$priorityA = isset($a->PRIORITY) ? (int) $a->PRIORITY->getValue() : 0;
|
||||||
|
$priorityB = isset($b->PRIORITY) ? (int) $b->PRIORITY->getValue() : 0;
|
||||||
|
|
||||||
|
if (0 === $priorityA) {
|
||||||
|
$priorityA = 10;
|
||||||
|
}
|
||||||
|
if (0 === $priorityB) {
|
||||||
|
$priorityB = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $priorityA - $priorityB;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now we go over all the VAVAILABILITY components and figure if
|
||||||
|
// there's any we don't need to consider.
|
||||||
|
//
|
||||||
|
// This is can be because of one of two reasons: either the
|
||||||
|
// VAVAILABILITY component falls outside the time we are interested in,
|
||||||
|
// or a different VAVAILABILITY component with a higher priority has
|
||||||
|
// already completely covered the time-range.
|
||||||
|
$old = $vavailComps;
|
||||||
|
$new = [];
|
||||||
|
|
||||||
|
foreach ($old as $vavail) {
|
||||||
|
list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
|
||||||
|
|
||||||
|
// We don't care about datetimes that are earlier or later than the
|
||||||
|
// start and end of the freebusy report, so this gets normalized
|
||||||
|
// first.
|
||||||
|
if (is_null($compStart) || $compStart < $this->start) {
|
||||||
|
$compStart = $this->start;
|
||||||
|
}
|
||||||
|
if (is_null($compEnd) || $compEnd > $this->end) {
|
||||||
|
$compEnd = $this->end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the item fell out of the timerange, we can just skip it.
|
||||||
|
if ($compStart > $this->end || $compEnd < $this->start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Going through our existing list of components to see if there's
|
||||||
|
// a higher priority component that already fully covers this one.
|
||||||
|
foreach ($new as $higherVavail) {
|
||||||
|
list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
|
||||||
|
if (
|
||||||
|
(is_null($higherStart) || $higherStart < $compStart) &&
|
||||||
|
(is_null($higherEnd) || $higherEnd > $compEnd)
|
||||||
|
) {
|
||||||
|
// Component is fully covered by a higher priority
|
||||||
|
// component. We can skip this component.
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're keeping it!
|
||||||
|
$new[] = $vavail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly, we need to traverse the remaining components and fill in the
|
||||||
|
// freebusydata slots.
|
||||||
|
//
|
||||||
|
// We traverse the components in reverse, because we want the higher
|
||||||
|
// priority components to override the lower ones.
|
||||||
|
foreach (array_reverse($new) as $vavail) {
|
||||||
|
$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
|
||||||
|
list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
|
||||||
|
|
||||||
|
// Making the component size no larger than the requested free-busy
|
||||||
|
// report range.
|
||||||
|
if (!$vavailStart || $vavailStart < $this->start) {
|
||||||
|
$vavailStart = $this->start;
|
||||||
|
}
|
||||||
|
if (!$vavailEnd || $vavailEnd > $this->end) {
|
||||||
|
$vavailEnd = $this->end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marking the entire time range of the VAVAILABILITY component as
|
||||||
|
// busy.
|
||||||
|
$fbData->add(
|
||||||
|
$vavailStart->getTimeStamp(),
|
||||||
|
$vavailEnd->getTimeStamp(),
|
||||||
|
$busyType
|
||||||
|
);
|
||||||
|
|
||||||
|
// Looping over the AVAILABLE components.
|
||||||
|
if (isset($vavail->AVAILABLE)) {
|
||||||
|
foreach ($vavail->AVAILABLE as $available) {
|
||||||
|
list($availStart, $availEnd) = $available->getEffectiveStartEnd();
|
||||||
|
$fbData->add(
|
||||||
|
$availStart->getTimeStamp(),
|
||||||
|
$availEnd->getTimeStamp(),
|
||||||
|
'FREE'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($available->RRULE) {
|
||||||
|
// Our favourite thing: recurrence!!
|
||||||
|
|
||||||
|
$rruleIterator = new Recur\RRuleIterator(
|
||||||
|
$available->RRULE->getValue(),
|
||||||
|
$availStart
|
||||||
|
);
|
||||||
|
$rruleIterator->fastForward($vavailStart);
|
||||||
|
|
||||||
|
$startEndDiff = $availStart->diff($availEnd);
|
||||||
|
|
||||||
|
while ($rruleIterator->valid()) {
|
||||||
|
$recurStart = $rruleIterator->current();
|
||||||
|
$recurEnd = $recurStart->add($startEndDiff);
|
||||||
|
|
||||||
|
if ($recurStart > $vavailEnd) {
|
||||||
|
// We're beyond the legal timerange.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($recurEnd > $vavailEnd) {
|
||||||
|
// Truncating the end if it exceeds the
|
||||||
|
// VAVAILABILITY end.
|
||||||
|
$recurEnd = $vavailEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fbData->add(
|
||||||
|
$recurStart->getTimeStamp(),
|
||||||
|
$recurEnd->getTimeStamp(),
|
||||||
|
'FREE'
|
||||||
|
);
|
||||||
|
|
||||||
|
$rruleIterator->next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes an array of iCalendar objects and applies its busy
|
||||||
|
* times on fbData.
|
||||||
|
*
|
||||||
|
* @param VCalendar[] $objects
|
||||||
|
*/
|
||||||
|
protected function calculateBusy(FreeBusyData $fbData, array $objects)
|
||||||
|
{
|
||||||
|
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) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
|
if (isset($component->TRANSP) && ('TRANSPARENT' === strtoupper($component->TRANSP))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (isset($component->STATUS)) {
|
if (isset($component->STATUS)) {
|
||||||
$status = strtoupper($component->STATUS);
|
$status = strtoupper($component->STATUS);
|
||||||
if ($status==='CANCELLED') {
|
if ('CANCELLED' === $status) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($status==='TENTATIVE') {
|
if ('TENTATIVE' === $status) {
|
||||||
$FBTYPE = 'BUSY-TENTATIVE';
|
$FBTYPE = 'BUSY-TENTATIVE';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$times = array();
|
$times = [];
|
||||||
|
|
||||||
if ($component->RRULE) {
|
if ($component->RRULE) {
|
||||||
|
try {
|
||||||
|
$iterator = new EventIterator($object, (string) $component->UID, $this->timeZone);
|
||||||
|
} catch (NoInstancesException $e) {
|
||||||
|
// This event is recurring, but it doesn't have a single
|
||||||
|
// instance. We are skipping this event from the output
|
||||||
|
// entirely.
|
||||||
|
unset($this->objects[$key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$iterator = new RecurrenceIterator($object, (string)$component->uid);
|
|
||||||
if ($this->start) {
|
if ($this->start) {
|
||||||
$iterator->fastForward($this->start);
|
$iterator->fastForward($this->start);
|
||||||
}
|
}
|
||||||
|
|
||||||
$maxRecurrences = 200;
|
$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;
|
||||||
}
|
}
|
||||||
$times[] = array(
|
$times[] = [
|
||||||
$iterator->getDTStart(),
|
$iterator->getDTStart(),
|
||||||
$iterator->getDTEnd(),
|
$iterator->getDTEnd(),
|
||||||
);
|
];
|
||||||
|
|
||||||
$iterator->next();
|
$iterator->next();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
$startTime = $component->DTSTART->getDateTime($this->timeZone);
|
||||||
$startTime = $component->DTSTART->getDateTime();
|
|
||||||
if ($this->end && $startTime > $this->end) {
|
if ($this->end && $startTime > $this->end) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$endTime = null;
|
$endTime = null;
|
||||||
if (isset($component->DTEND)) {
|
if (isset($component->DTEND)) {
|
||||||
$endTime = $component->DTEND->getDateTime();
|
$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->add($duration);
|
$endTime = $endTime->add($duration);
|
||||||
} elseif (!$component->DTSTART->hasTime()) {
|
} elseif (!$component->DTSTART->hasTime()) {
|
||||||
$endTime = clone $startTime;
|
$endTime = clone $startTime;
|
||||||
$endTime->modify('+1 day');
|
$endTime = $endTime->modify('+1 day');
|
||||||
} else {
|
} else {
|
||||||
// The event had no duration (0 seconds)
|
// The event had no duration (0 seconds)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$times[] = array($startTime, $endTime);
|
$times[] = [$startTime, $endTime];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($times as $time) {
|
foreach ($times as $time) {
|
||||||
|
if ($this->end && $time[0] > $this->end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($this->start && $time[1] < $this->start) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->end && $time[0] > $this->end) break;
|
$fbData->add(
|
||||||
if ($this->start && $time[1] < $this->start) break;
|
$time[0]->getTimeStamp(),
|
||||||
|
$time[1]->getTimeStamp(),
|
||||||
$busyTimes[] = array(
|
$FBTYPE
|
||||||
$time[0],
|
|
||||||
$time[1],
|
|
||||||
$FBTYPE,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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 ($fbType==='FREE')
|
if ('FREE' === $fbType) {
|
||||||
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 (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
|
if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) {
|
||||||
$duration = DateTimeParser::parseDuration($endTime);
|
$duration = DateTimeParser::parseDuration($endTime);
|
||||||
$endTime = clone $startTime;
|
$endTime = clone $startTime;
|
||||||
$endTime->add($duration);
|
$endTime = $endTime->add($duration);
|
||||||
} else {
|
} else {
|
||||||
$endTime = DateTimeParser::parseDateTime($endTime);
|
$endTime = DateTimeParser::parseDateTime($endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->start && $this->start > $endTime) continue;
|
if ($this->start && $this->start > $endTime) {
|
||||||
if($this->end && $this->end < $startTime) continue;
|
continue;
|
||||||
$busyTimes[] = array(
|
}
|
||||||
$startTime,
|
if ($this->end && $this->end < $startTime) {
|
||||||
$endTime,
|
continue;
|
||||||
|
}
|
||||||
|
$fbData->add(
|
||||||
|
$startTime->getTimeStamp(),
|
||||||
|
$endTime->getTimeStamp(),
|
||||||
$fbType
|
$fbType
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes a FreeBusyData object and generates the VCALENDAR
|
||||||
|
* object associated with it.
|
||||||
|
*
|
||||||
|
* @return VCalendar
|
||||||
|
*/
|
||||||
|
protected function generateFreeBusyCalendar(FreeBusyData $fbData)
|
||||||
|
{
|
||||||
if ($this->baseObject) {
|
if ($this->baseObject) {
|
||||||
$calendar = $this->baseObject;
|
$calendar = $this->baseObject;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -296,27 +514,36 @@ class FreeBusyGenerator {
|
||||||
$dtend->setDateTime($this->end);
|
$dtend->setDateTime($this->end);
|
||||||
$vfreebusy->add($dtend);
|
$vfreebusy->add($dtend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tz = new \DateTimeZone('UTC');
|
||||||
$dtstamp = $calendar->createProperty('DTSTAMP');
|
$dtstamp = $calendar->createProperty('DTSTAMP');
|
||||||
$dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC')));
|
$dtstamp->setDateTime(new DateTimeImmutable('now', $tz));
|
||||||
$vfreebusy->add($dtstamp);
|
$vfreebusy->add($dtstamp);
|
||||||
|
|
||||||
foreach($busyTimes as $busyTime) {
|
foreach ($fbData->getData() as $busyTime) {
|
||||||
|
$busyType = strtoupper($busyTime['type']);
|
||||||
|
|
||||||
$busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
|
// Ignoring all the FREE parts, because those are already assumed.
|
||||||
$busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
|
if ('FREE' === $busyType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$busyTime[0] = new \DateTimeImmutable('@'.$busyTime['start'], $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')
|
||||||
);
|
);
|
||||||
$prop['FBTYPE'] = $busyTime[2];
|
|
||||||
$vfreebusy->add($prop);
|
|
||||||
|
|
||||||
|
// Only setting FBTYPE if it's not BUSY, because BUSY is the
|
||||||
|
// default anyway.
|
||||||
|
if ('BUSY' !== $busyType) {
|
||||||
|
$prop['FBTYPE'] = $busyType;
|
||||||
|
}
|
||||||
|
$vfreebusy->add($prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $calendar;
|
return $calendar;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
978
snappymail/v/0.0.0/app/libraries/Sabre/VObject/ITip/Broker.php
Normal file
978
snappymail/v/0.0.0/app/libraries/Sabre/VObject/ITip/Broker.php
Normal file
|
|
@ -0,0 +1,978 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\ITip;
|
||||||
|
|
||||||
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
use Sabre\VObject\Reader;
|
||||||
|
use Sabre\VObject\Recur\EventIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ITip\Broker class is a utility class that helps with processing
|
||||||
|
* so-called iTip messages.
|
||||||
|
*
|
||||||
|
* iTip is defined in rfc5546, stands for iCalendar Transport-Independent
|
||||||
|
* Interoperability Protocol, and describes the underlying mechanism for
|
||||||
|
* using iCalendar for scheduling for for example through email (also known as
|
||||||
|
* IMip) and CalDAV Scheduling.
|
||||||
|
*
|
||||||
|
* This class helps by:
|
||||||
|
*
|
||||||
|
* 1. Creating individual invites based on an iCalendar event for each
|
||||||
|
* attendee.
|
||||||
|
* 2. Generating invite updates based on an iCalendar update. This may result
|
||||||
|
* in new invites, updates and cancellations for attendees, if that list
|
||||||
|
* changed.
|
||||||
|
* 3. On the receiving end, it can create a local iCalendar event based on
|
||||||
|
* a received invite.
|
||||||
|
* 4. It can also process an invite update on a local event, ensuring that any
|
||||||
|
* overridden properties from attendees are retained.
|
||||||
|
* 5. It can create a accepted or declined iTip reply based on an invite.
|
||||||
|
* 6. It can process a reply from an invite and update an events attendee
|
||||||
|
* status based on a reply.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Broker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This setting determines whether the rules for the SCHEDULE-AGENT
|
||||||
|
* parameter should be followed.
|
||||||
|
*
|
||||||
|
* This is a parameter defined on ATTENDEE properties, introduced by RFC
|
||||||
|
* 6638. This parameter allows a caldav client to tell the server 'Don't do
|
||||||
|
* any scheduling operations'.
|
||||||
|
*
|
||||||
|
* If this setting is turned on, any attendees with SCHEDULE-AGENT set to
|
||||||
|
* CLIENT will be ignored. This is the desired behavior for a CalDAV
|
||||||
|
* server, but if you're writing an iTip application that doesn't deal with
|
||||||
|
* CalDAV, you may want to ignore this parameter.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $scheduleAgentServerRules = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The broker will try during 'parseEvent' figure out whether the change
|
||||||
|
* was significant.
|
||||||
|
*
|
||||||
|
* It uses a few different ways to do this. One of these ways is seeing if
|
||||||
|
* certain properties changed values. This list of specified here.
|
||||||
|
*
|
||||||
|
* This list is taken from:
|
||||||
|
* * http://tools.ietf.org/html/rfc5546#section-2.1.4
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $significantChangeProperties = [
|
||||||
|
'DTSTART',
|
||||||
|
'DTEND',
|
||||||
|
'DURATION',
|
||||||
|
'DUE',
|
||||||
|
'RRULE',
|
||||||
|
'RDATE',
|
||||||
|
'EXDATE',
|
||||||
|
'STATUS',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to process an incoming itip message.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* 1. A user is an attendee to an event. The organizer sends an updated
|
||||||
|
* meeting using a new iTip message with METHOD:REQUEST. This function
|
||||||
|
* will process the message and update the attendee's event accordingly.
|
||||||
|
*
|
||||||
|
* 2. The organizer cancelled the event using METHOD:CANCEL. We will update
|
||||||
|
* the users event to state STATUS:CANCELLED.
|
||||||
|
*
|
||||||
|
* 3. An attendee sent a reply to an invite using METHOD:REPLY. We can
|
||||||
|
* update the organizers event to update the ATTENDEE with its correct
|
||||||
|
* PARTSTAT.
|
||||||
|
*
|
||||||
|
* The $existingObject is updated in-place. If there is no existing object
|
||||||
|
* (because it's a new invite for example) a new object will be created.
|
||||||
|
*
|
||||||
|
* If an existing object does not exist, and the method was CANCEL or
|
||||||
|
* REPLY, the message effectively gets ignored, and no 'existingObject'
|
||||||
|
* will be created.
|
||||||
|
*
|
||||||
|
* The updated $existingObject is also returned from this function.
|
||||||
|
*
|
||||||
|
* If the iTip message was not supported, we will always return false.
|
||||||
|
*
|
||||||
|
* @param VCalendar $existingObject
|
||||||
|
*
|
||||||
|
* @return VCalendar|null
|
||||||
|
*/
|
||||||
|
public function processMessage(Message $itipMessage, VCalendar $existingObject = null)
|
||||||
|
{
|
||||||
|
// We only support events at the moment.
|
||||||
|
if ('VEVENT' !== $itipMessage->component) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($itipMessage->method) {
|
||||||
|
case 'REQUEST':
|
||||||
|
return $this->processMessageRequest($itipMessage, $existingObject);
|
||||||
|
|
||||||
|
case 'CANCEL':
|
||||||
|
return $this->processMessageCancel($itipMessage, $existingObject);
|
||||||
|
|
||||||
|
case 'REPLY':
|
||||||
|
return $this->processMessageReply($itipMessage, $existingObject);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unsupported iTip message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $existingObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function parses a VCALENDAR object and figure out if any messages
|
||||||
|
* need to be sent.
|
||||||
|
*
|
||||||
|
* A VCALENDAR object will be created from the perspective of either an
|
||||||
|
* attendee, or an organizer. You must pass a string identifying the
|
||||||
|
* current user, so we can figure out who in the list of attendees or the
|
||||||
|
* organizer we are sending this message on behalf of.
|
||||||
|
*
|
||||||
|
* It's possible to specify the current user as an array, in case the user
|
||||||
|
* has more than one identifying href (such as multiple emails).
|
||||||
|
*
|
||||||
|
* It $oldCalendar is specified, it is assumed that the operation is
|
||||||
|
* updating an existing event, which means that we need to look at the
|
||||||
|
* differences between events, and potentially send old attendees
|
||||||
|
* cancellations, and current attendees updates.
|
||||||
|
*
|
||||||
|
* If $calendar is null, but $oldCalendar is specified, we treat the
|
||||||
|
* operation as if the user has deleted an event. If the user was an
|
||||||
|
* organizer, this means that we need to send cancellation notices to
|
||||||
|
* people. If the user was an attendee, we need to make sure that the
|
||||||
|
* organizer gets the 'declined' message.
|
||||||
|
*
|
||||||
|
* @param VCalendar|string $calendar
|
||||||
|
* @param string|array $userHref
|
||||||
|
* @param VCalendar|string $oldCalendar
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function parseEvent($calendar, $userHref, $oldCalendar = null)
|
||||||
|
{
|
||||||
|
if ($oldCalendar) {
|
||||||
|
if (is_string($oldCalendar)) {
|
||||||
|
$oldCalendar = Reader::read($oldCalendar);
|
||||||
|
}
|
||||||
|
if (!isset($oldCalendar->VEVENT)) {
|
||||||
|
// We only support events at the moment
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldEventInfo = $this->parseEventInfo($oldCalendar);
|
||||||
|
} else {
|
||||||
|
$oldEventInfo = [
|
||||||
|
'organizer' => null,
|
||||||
|
'significantChangeHash' => '',
|
||||||
|
'attendees' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userHref = (array) $userHref;
|
||||||
|
|
||||||
|
if (!is_null($calendar)) {
|
||||||
|
if (is_string($calendar)) {
|
||||||
|
$calendar = Reader::read($calendar);
|
||||||
|
}
|
||||||
|
if (!isset($calendar->VEVENT)) {
|
||||||
|
// We only support events at the moment
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$eventInfo = $this->parseEventInfo($calendar);
|
||||||
|
if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
|
||||||
|
// If there were no attendees on either side of the equation,
|
||||||
|
// we don't need to do anything.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
|
||||||
|
// There was no organizer before or after the change.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseCalendar = $calendar;
|
||||||
|
|
||||||
|
// If the new object didn't have an organizer, the organizer
|
||||||
|
// changed the object from a scheduling object to a non-scheduling
|
||||||
|
// object. We just copy the info from the old object.
|
||||||
|
if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
|
||||||
|
$eventInfo['organizer'] = $oldEventInfo['organizer'];
|
||||||
|
$eventInfo['organizerName'] = $oldEventInfo['organizerName'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The calendar object got deleted, we need to process this as a
|
||||||
|
// cancellation / decline.
|
||||||
|
if (!$oldCalendar) {
|
||||||
|
// No old and no new calendar, there's no thing to do.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventInfo = $oldEventInfo;
|
||||||
|
|
||||||
|
if (in_array($eventInfo['organizer'], $userHref)) {
|
||||||
|
// This is an organizer deleting the event.
|
||||||
|
$eventInfo['attendees'] = [];
|
||||||
|
// Increasing the sequence, but only if the organizer deleted
|
||||||
|
// the event.
|
||||||
|
++$eventInfo['sequence'];
|
||||||
|
} else {
|
||||||
|
// This is an attendee deleting the event.
|
||||||
|
foreach ($eventInfo['attendees'] as $key => $attendee) {
|
||||||
|
if (in_array($attendee['href'], $userHref)) {
|
||||||
|
$eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$baseCalendar = $oldCalendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($eventInfo['organizer'], $userHref)) {
|
||||||
|
return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
|
||||||
|
} elseif ($oldCalendar) {
|
||||||
|
// We need to figure out if the user is an attendee, but we're only
|
||||||
|
// doing so if there's an oldCalendar, because we only want to
|
||||||
|
// process updates, not creation of new events.
|
||||||
|
foreach ($eventInfo['attendees'] as $attendee) {
|
||||||
|
if (in_array($attendee['href'], $userHref)) {
|
||||||
|
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes incoming REQUEST messages.
|
||||||
|
*
|
||||||
|
* This is message from an organizer, and is either a new event
|
||||||
|
* invite, or an update to an existing one.
|
||||||
|
*
|
||||||
|
* @param VCalendar $existingObject
|
||||||
|
*
|
||||||
|
* @return VCalendar|null
|
||||||
|
*/
|
||||||
|
protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null)
|
||||||
|
{
|
||||||
|
if (!$existingObject) {
|
||||||
|
// This is a new invite, and we're just going to copy over
|
||||||
|
// all the components from the invite.
|
||||||
|
$existingObject = new VCalendar();
|
||||||
|
foreach ($itipMessage->message->getComponents() as $component) {
|
||||||
|
$existingObject->add(clone $component);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We need to update an existing object with all the new
|
||||||
|
// information. We can just remove all existing components
|
||||||
|
// and create new ones.
|
||||||
|
foreach ($existingObject->getComponents() as $component) {
|
||||||
|
$existingObject->remove($component);
|
||||||
|
}
|
||||||
|
foreach ($itipMessage->message->getComponents() as $component) {
|
||||||
|
$existingObject->add(clone $component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $existingObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes incoming CANCEL messages.
|
||||||
|
*
|
||||||
|
* This is a message from an organizer, and means that either an
|
||||||
|
* attendee got removed from an event, or an event got cancelled
|
||||||
|
* altogether.
|
||||||
|
*
|
||||||
|
* @param VCalendar $existingObject
|
||||||
|
*
|
||||||
|
* @return VCalendar|null
|
||||||
|
*/
|
||||||
|
protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null)
|
||||||
|
{
|
||||||
|
if (!$existingObject) {
|
||||||
|
// The event didn't exist in the first place, so we're just
|
||||||
|
// ignoring this message.
|
||||||
|
} else {
|
||||||
|
foreach ($existingObject->VEVENT as $vevent) {
|
||||||
|
$vevent->STATUS = 'CANCELLED';
|
||||||
|
$vevent->SEQUENCE = $itipMessage->sequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $existingObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes incoming REPLY messages.
|
||||||
|
*
|
||||||
|
* The message is a reply. This is for example an attendee telling
|
||||||
|
* an organizer he accepted the invite, or declined it.
|
||||||
|
*
|
||||||
|
* @param VCalendar $existingObject
|
||||||
|
*
|
||||||
|
* @return VCalendar|null
|
||||||
|
*/
|
||||||
|
protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null)
|
||||||
|
{
|
||||||
|
// A reply can only be processed based on an existing object.
|
||||||
|
// If the object is not available, the reply is ignored.
|
||||||
|
if (!$existingObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$instances = [];
|
||||||
|
$requestStatus = '2.0';
|
||||||
|
|
||||||
|
// Finding all the instances the attendee replied to.
|
||||||
|
foreach ($itipMessage->message->VEVENT as $vevent) {
|
||||||
|
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
||||||
|
$attendee = $vevent->ATTENDEE;
|
||||||
|
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
|
||||||
|
if (isset($vevent->{'REQUEST-STATUS'})) {
|
||||||
|
$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
|
||||||
|
list($requestStatus) = explode(';', $requestStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to loop through the original organizer event, to find
|
||||||
|
// all the instances where we have a reply for.
|
||||||
|
$masterObject = null;
|
||||||
|
foreach ($existingObject->VEVENT as $vevent) {
|
||||||
|
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
||||||
|
if ('master' === $recurId) {
|
||||||
|
$masterObject = $vevent;
|
||||||
|
}
|
||||||
|
if (isset($instances[$recurId])) {
|
||||||
|
$attendeeFound = false;
|
||||||
|
if (isset($vevent->ATTENDEE)) {
|
||||||
|
foreach ($vevent->ATTENDEE as $attendee) {
|
||||||
|
if ($attendee->getValue() === $itipMessage->sender) {
|
||||||
|
$attendeeFound = true;
|
||||||
|
$attendee['PARTSTAT'] = $instances[$recurId];
|
||||||
|
$attendee['SCHEDULE-STATUS'] = $requestStatus;
|
||||||
|
// Un-setting the RSVP status, because we now know
|
||||||
|
// that the attendee already replied.
|
||||||
|
unset($attendee['RSVP']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$attendeeFound) {
|
||||||
|
// Adding a new attendee. The iTip documentation calls this
|
||||||
|
// a party crasher.
|
||||||
|
$attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
|
||||||
|
'PARTSTAT' => $instances[$recurId],
|
||||||
|
]);
|
||||||
|
if ($itipMessage->senderName) {
|
||||||
|
$attendee['CN'] = $itipMessage->senderName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($instances[$recurId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$masterObject) {
|
||||||
|
// No master object, we can't add new instances.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If we got replies to instances that did not exist in the
|
||||||
|
// original list, it means that new exceptions must be created.
|
||||||
|
foreach ($instances as $recurId => $partstat) {
|
||||||
|
$recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
|
||||||
|
$found = false;
|
||||||
|
$iterations = 1000;
|
||||||
|
do {
|
||||||
|
$newObject = $recurrenceIterator->getEventObject();
|
||||||
|
$recurrenceIterator->next();
|
||||||
|
|
||||||
|
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
--$iterations;
|
||||||
|
} while ($recurrenceIterator->valid() && !$found && $iterations);
|
||||||
|
|
||||||
|
// Invalid recurrence id. Skipping this object.
|
||||||
|
if (!$found) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset(
|
||||||
|
$newObject->RRULE,
|
||||||
|
$newObject->EXDATE,
|
||||||
|
$newObject->RDATE
|
||||||
|
);
|
||||||
|
$attendeeFound = false;
|
||||||
|
if (isset($newObject->ATTENDEE)) {
|
||||||
|
foreach ($newObject->ATTENDEE as $attendee) {
|
||||||
|
if ($attendee->getValue() === $itipMessage->sender) {
|
||||||
|
$attendeeFound = true;
|
||||||
|
$attendee['PARTSTAT'] = $partstat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$attendeeFound) {
|
||||||
|
// Adding a new attendee
|
||||||
|
$attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
|
||||||
|
'PARTSTAT' => $partstat,
|
||||||
|
]);
|
||||||
|
if ($itipMessage->senderName) {
|
||||||
|
$attendee['CN'] = $itipMessage->senderName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$existingObject->add($newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $existingObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used in cases where an event got updated, and we
|
||||||
|
* potentially need to send emails to attendees to let them know of updates
|
||||||
|
* in the events.
|
||||||
|
*
|
||||||
|
* We will detect which attendees got added, which got removed and create
|
||||||
|
* specific messages for these situations.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo)
|
||||||
|
{
|
||||||
|
// Merging attendee lists.
|
||||||
|
$attendees = [];
|
||||||
|
foreach ($oldEventInfo['attendees'] as $attendee) {
|
||||||
|
$attendees[$attendee['href']] = [
|
||||||
|
'href' => $attendee['href'],
|
||||||
|
'oldInstances' => $attendee['instances'],
|
||||||
|
'newInstances' => [],
|
||||||
|
'name' => $attendee['name'],
|
||||||
|
'forceSend' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
foreach ($eventInfo['attendees'] as $attendee) {
|
||||||
|
if (isset($attendees[$attendee['href']])) {
|
||||||
|
$attendees[$attendee['href']]['name'] = $attendee['name'];
|
||||||
|
$attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
|
||||||
|
$attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
|
||||||
|
} else {
|
||||||
|
$attendees[$attendee['href']] = [
|
||||||
|
'href' => $attendee['href'],
|
||||||
|
'oldInstances' => [],
|
||||||
|
'newInstances' => $attendee['instances'],
|
||||||
|
'name' => $attendee['name'],
|
||||||
|
'forceSend' => $attendee['forceSend'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages = [];
|
||||||
|
|
||||||
|
foreach ($attendees as $attendee) {
|
||||||
|
// An organizer can also be an attendee. We should not generate any
|
||||||
|
// messages for those.
|
||||||
|
if ($attendee['href'] === $eventInfo['organizer']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = new Message();
|
||||||
|
$message->uid = $eventInfo['uid'];
|
||||||
|
$message->component = 'VEVENT';
|
||||||
|
$message->sequence = $eventInfo['sequence'];
|
||||||
|
$message->sender = $eventInfo['organizer'];
|
||||||
|
$message->senderName = $eventInfo['organizerName'];
|
||||||
|
$message->recipient = $attendee['href'];
|
||||||
|
$message->recipientName = $attendee['name'];
|
||||||
|
|
||||||
|
// Creating the new iCalendar body.
|
||||||
|
$icalMsg = new VCalendar();
|
||||||
|
|
||||||
|
foreach ($calendar->select('VTIMEZONE') as $timezone) {
|
||||||
|
$icalMsg->add(clone $timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$attendee['newInstances']) {
|
||||||
|
// If there are no instances the attendee is a part of, it
|
||||||
|
// means the attendee was removed and we need to send him a
|
||||||
|
// CANCEL.
|
||||||
|
$message->method = 'CANCEL';
|
||||||
|
|
||||||
|
$icalMsg->METHOD = $message->method;
|
||||||
|
|
||||||
|
$event = $icalMsg->add('VEVENT', [
|
||||||
|
'UID' => $message->uid,
|
||||||
|
'SEQUENCE' => $message->sequence,
|
||||||
|
'DTSTAMP' => gmdate('Ymd\\THis\\Z'),
|
||||||
|
]);
|
||||||
|
if (isset($calendar->VEVENT->SUMMARY)) {
|
||||||
|
$event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
|
||||||
|
}
|
||||||
|
$event->add(clone $calendar->VEVENT->DTSTART);
|
||||||
|
if (isset($calendar->VEVENT->DTEND)) {
|
||||||
|
$event->add(clone $calendar->VEVENT->DTEND);
|
||||||
|
} elseif (isset($calendar->VEVENT->DURATION)) {
|
||||||
|
$event->add(clone $calendar->VEVENT->DURATION);
|
||||||
|
}
|
||||||
|
$org = $event->add('ORGANIZER', $eventInfo['organizer']);
|
||||||
|
if ($eventInfo['organizerName']) {
|
||||||
|
$org['CN'] = $eventInfo['organizerName'];
|
||||||
|
}
|
||||||
|
$event->add('ATTENDEE', $attendee['href'], [
|
||||||
|
'CN' => $attendee['name'],
|
||||||
|
]);
|
||||||
|
$message->significantChange = true;
|
||||||
|
} else {
|
||||||
|
// The attendee gets the updated event body
|
||||||
|
$message->method = 'REQUEST';
|
||||||
|
|
||||||
|
$icalMsg->METHOD = $message->method;
|
||||||
|
|
||||||
|
// We need to find out that this change is significant. If it's
|
||||||
|
// not, systems may opt to not send messages.
|
||||||
|
//
|
||||||
|
// We do this based on the 'significantChangeHash' which is
|
||||||
|
// some value that changes if there's a certain set of
|
||||||
|
// properties changed in the event, or simply if there's a
|
||||||
|
// difference in instances that the attendee is invited to.
|
||||||
|
|
||||||
|
$oldAttendeeInstances = array_keys($attendee['oldInstances']);
|
||||||
|
$newAttendeeInstances = array_keys($attendee['newInstances']);
|
||||||
|
|
||||||
|
$message->significantChange =
|
||||||
|
'REQUEST' === $attendee['forceSend'] ||
|
||||||
|
count($oldAttendeeInstances) != count($newAttendeeInstances) ||
|
||||||
|
count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 ||
|
||||||
|
$oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
|
||||||
|
|
||||||
|
foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
|
||||||
|
$currentEvent = clone $eventInfo['instances'][$instanceId];
|
||||||
|
if ('master' === $instanceId) {
|
||||||
|
// We need to find a list of events that the attendee
|
||||||
|
// is not a part of to add to the list of exceptions.
|
||||||
|
$exceptions = [];
|
||||||
|
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
|
||||||
|
if (!isset($attendee['newInstances'][$instanceId])) {
|
||||||
|
$exceptions[] = $instanceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there were exceptions, we need to add it to an
|
||||||
|
// existing EXDATE property, if it exists.
|
||||||
|
if ($exceptions) {
|
||||||
|
if (isset($currentEvent->EXDATE)) {
|
||||||
|
$currentEvent->EXDATE->setParts(array_merge(
|
||||||
|
$currentEvent->EXDATE->getParts(),
|
||||||
|
$exceptions
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$currentEvent->EXDATE = $exceptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleaning up any scheduling information that
|
||||||
|
// shouldn't be sent along.
|
||||||
|
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
||||||
|
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
|
||||||
|
|
||||||
|
foreach ($currentEvent->ATTENDEE as $attendee) {
|
||||||
|
unset($attendee['SCHEDULE-FORCE-SEND']);
|
||||||
|
unset($attendee['SCHEDULE-STATUS']);
|
||||||
|
|
||||||
|
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
|
||||||
|
// iOS shows an "Inbox Item"
|
||||||
|
if (!isset($attendee['PARTSTAT'])) {
|
||||||
|
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentEvent->DTSTAMP = gmdate('Ymd\\THis\\Z');
|
||||||
|
$icalMsg->add($currentEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$message->message = $icalMsg;
|
||||||
|
$messages[] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an event update for an attendee.
|
||||||
|
*
|
||||||
|
* This function figures out if we need to send a reply to an organizer.
|
||||||
|
*
|
||||||
|
* @param string $attendee
|
||||||
|
*
|
||||||
|
* @return Message[]
|
||||||
|
*/
|
||||||
|
protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee)
|
||||||
|
{
|
||||||
|
if ($this->scheduleAgentServerRules && 'CLIENT' === $eventInfo['organizerScheduleAgent']) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother generating messages for events that have already been
|
||||||
|
// cancelled.
|
||||||
|
if ('CANCELLED' === $eventInfo['status']) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
|
||||||
|
$oldEventInfo['attendees'][$attendee]['instances'] :
|
||||||
|
[];
|
||||||
|
|
||||||
|
$instances = [];
|
||||||
|
foreach ($oldInstances as $instance) {
|
||||||
|
$instances[$instance['id']] = [
|
||||||
|
'id' => $instance['id'],
|
||||||
|
'oldstatus' => $instance['partstat'],
|
||||||
|
'newstatus' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {
|
||||||
|
if (isset($instances[$instance['id']])) {
|
||||||
|
$instances[$instance['id']]['newstatus'] = $instance['partstat'];
|
||||||
|
} else {
|
||||||
|
$instances[$instance['id']] = [
|
||||||
|
'id' => $instance['id'],
|
||||||
|
'oldstatus' => null,
|
||||||
|
'newstatus' => $instance['partstat'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to also look for differences in EXDATE. If there are new
|
||||||
|
// items in EXDATE, it means that an attendee deleted instances of an
|
||||||
|
// event, which means we need to send DECLINED specifically for those
|
||||||
|
// instances.
|
||||||
|
// We only need to do that though, if the master event is not declined.
|
||||||
|
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) {
|
||||||
|
foreach ($eventInfo['exdate'] as $exDate) {
|
||||||
|
if (!in_array($exDate, $oldEventInfo['exdate'])) {
|
||||||
|
if (isset($instances[$exDate])) {
|
||||||
|
$instances[$exDate]['newstatus'] = 'DECLINED';
|
||||||
|
} else {
|
||||||
|
$instances[$exDate] = [
|
||||||
|
'id' => $exDate,
|
||||||
|
'oldstatus' => null,
|
||||||
|
'newstatus' => 'DECLINED',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gathering a few extra properties for each instance.
|
||||||
|
foreach ($instances as $recurId => $instanceInfo) {
|
||||||
|
if (isset($eventInfo['instances'][$recurId])) {
|
||||||
|
$instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
|
||||||
|
} else {
|
||||||
|
$instances[$recurId]['dtstart'] = $recurId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = new Message();
|
||||||
|
$message->uid = $eventInfo['uid'];
|
||||||
|
$message->method = 'REPLY';
|
||||||
|
$message->component = 'VEVENT';
|
||||||
|
$message->sequence = $eventInfo['sequence'];
|
||||||
|
$message->sender = $attendee;
|
||||||
|
$message->senderName = $eventInfo['attendees'][$attendee]['name'];
|
||||||
|
$message->recipient = $eventInfo['organizer'];
|
||||||
|
$message->recipientName = $eventInfo['organizerName'];
|
||||||
|
|
||||||
|
$icalMsg = new VCalendar();
|
||||||
|
$icalMsg->METHOD = 'REPLY';
|
||||||
|
|
||||||
|
foreach ($calendar->select('VTIMEZONE') as $timezone) {
|
||||||
|
$icalMsg->add(clone $timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasReply = false;
|
||||||
|
|
||||||
|
foreach ($instances as $instance) {
|
||||||
|
if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
|
||||||
|
// Skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = $icalMsg->add('VEVENT', [
|
||||||
|
'UID' => $message->uid,
|
||||||
|
'SEQUENCE' => $message->sequence,
|
||||||
|
]);
|
||||||
|
$summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
|
||||||
|
// Adding properties from the correct source instance
|
||||||
|
if (isset($eventInfo['instances'][$instance['id']])) {
|
||||||
|
$instanceObj = $eventInfo['instances'][$instance['id']];
|
||||||
|
$event->add(clone $instanceObj->DTSTART);
|
||||||
|
if (isset($instanceObj->DTEND)) {
|
||||||
|
$event->add(clone $instanceObj->DTEND);
|
||||||
|
} elseif (isset($instanceObj->DURATION)) {
|
||||||
|
$event->add(clone $instanceObj->DURATION);
|
||||||
|
}
|
||||||
|
if (isset($instanceObj->SUMMARY)) {
|
||||||
|
$event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
|
||||||
|
} elseif ($summary) {
|
||||||
|
$event->add('SUMMARY', $summary);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This branch of the code is reached, when a reply is
|
||||||
|
// generated for an instance of a recurring event, through the
|
||||||
|
// fact that the instance has disappeared by showing up in
|
||||||
|
// EXDATE
|
||||||
|
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||||
|
// Treat is as a DATE field
|
||||||
|
if (strlen($instance['id']) <= 8) {
|
||||||
|
$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
|
||||||
|
} else {
|
||||||
|
$event->add('DTSTART', $dt);
|
||||||
|
}
|
||||||
|
if ($summary) {
|
||||||
|
$event->add('SUMMARY', $summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('master' !== $instance['id']) {
|
||||||
|
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||||
|
// Treat is as a DATE field
|
||||||
|
if (strlen($instance['id']) <= 8) {
|
||||||
|
$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
|
||||||
|
} else {
|
||||||
|
$event->add('RECURRENCE-ID', $dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$organizer = $event->add('ORGANIZER', $message->recipient);
|
||||||
|
if ($message->recipientName) {
|
||||||
|
$organizer['CN'] = $message->recipientName;
|
||||||
|
}
|
||||||
|
$attendee = $event->add('ATTENDEE', $message->sender, [
|
||||||
|
'PARTSTAT' => $instance['newstatus'],
|
||||||
|
]);
|
||||||
|
if ($message->senderName) {
|
||||||
|
$attendee['CN'] = $message->senderName;
|
||||||
|
}
|
||||||
|
$hasReply = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasReply) {
|
||||||
|
$message->message = $icalMsg;
|
||||||
|
|
||||||
|
return [$message];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns attendee information and information about instances of an
|
||||||
|
* event.
|
||||||
|
*
|
||||||
|
* Returns an array with the following keys:
|
||||||
|
*
|
||||||
|
* 1. uid
|
||||||
|
* 2. organizer
|
||||||
|
* 3. organizerName
|
||||||
|
* 4. organizerScheduleAgent
|
||||||
|
* 5. organizerForceSend
|
||||||
|
* 6. instances
|
||||||
|
* 7. attendees
|
||||||
|
* 8. sequence
|
||||||
|
* 9. exdate
|
||||||
|
* 10. timezone - strictly the timezone on which the recurrence rule is
|
||||||
|
* based on.
|
||||||
|
* 11. significantChangeHash
|
||||||
|
* 12. status
|
||||||
|
*
|
||||||
|
* @param VCalendar $calendar
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function parseEventInfo(VCalendar $calendar = null)
|
||||||
|
{
|
||||||
|
$uid = null;
|
||||||
|
$organizer = null;
|
||||||
|
$organizerName = null;
|
||||||
|
$organizerForceSend = null;
|
||||||
|
$sequence = null;
|
||||||
|
$timezone = null;
|
||||||
|
$status = null;
|
||||||
|
$organizerScheduleAgent = 'SERVER';
|
||||||
|
|
||||||
|
$significantChangeHash = '';
|
||||||
|
|
||||||
|
// Now we need to collect a list of attendees, and which instances they
|
||||||
|
// are a part of.
|
||||||
|
$attendees = [];
|
||||||
|
|
||||||
|
$instances = [];
|
||||||
|
$exdate = [];
|
||||||
|
|
||||||
|
$significantChangeEventProperties = [];
|
||||||
|
|
||||||
|
foreach ($calendar->VEVENT as $vevent) {
|
||||||
|
$eventSignificantChangeHash = '';
|
||||||
|
$rrule = [];
|
||||||
|
|
||||||
|
if (is_null($uid)) {
|
||||||
|
$uid = $vevent->UID->getValue();
|
||||||
|
} else {
|
||||||
|
if ($uid !== $vevent->UID->getValue()) {
|
||||||
|
throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($vevent->DTSTART)) {
|
||||||
|
throw new ITipException('An event MUST have a DTSTART property.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($vevent->ORGANIZER)) {
|
||||||
|
if (is_null($organizer)) {
|
||||||
|
$organizer = $vevent->ORGANIZER->getNormalizedValue();
|
||||||
|
$organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null;
|
||||||
|
} else {
|
||||||
|
if (strtoupper($organizer) !== strtoupper($vevent->ORGANIZER->getNormalizedValue())) {
|
||||||
|
throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$organizerForceSend =
|
||||||
|
isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
|
||||||
|
strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
|
||||||
|
null;
|
||||||
|
$organizerScheduleAgent =
|
||||||
|
isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
|
||||||
|
strtoupper((string) $vevent->ORGANIZER['SCHEDULE-AGENT']) :
|
||||||
|
'SERVER';
|
||||||
|
}
|
||||||
|
if (is_null($sequence) && isset($vevent->SEQUENCE)) {
|
||||||
|
$sequence = $vevent->SEQUENCE->getValue();
|
||||||
|
}
|
||||||
|
if (isset($vevent->EXDATE)) {
|
||||||
|
foreach ($vevent->select('EXDATE') as $val) {
|
||||||
|
$exdate = array_merge($exdate, $val->getParts());
|
||||||
|
}
|
||||||
|
sort($exdate);
|
||||||
|
}
|
||||||
|
if (isset($vevent->RRULE)) {
|
||||||
|
foreach ($vevent->select('RRULE') as $rr) {
|
||||||
|
foreach ($rr->getParts() as $key => $val) {
|
||||||
|
// ignore default values (https://github.com/sabre-io/vobject/issues/126)
|
||||||
|
if ('INTERVAL' === $key && 1 == $val) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_array($val)) {
|
||||||
|
$val = implode(',', $val);
|
||||||
|
}
|
||||||
|
$rrule[] = "$key=$val";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($rrule);
|
||||||
|
}
|
||||||
|
if (isset($vevent->STATUS)) {
|
||||||
|
$status = strtoupper($vevent->STATUS->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
||||||
|
if (is_null($timezone)) {
|
||||||
|
if ('master' === $recurId) {
|
||||||
|
$timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
|
||||||
|
} else {
|
||||||
|
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($vevent->ATTENDEE)) {
|
||||||
|
foreach ($vevent->ATTENDEE as $attendee) {
|
||||||
|
if ($this->scheduleAgentServerRules &&
|
||||||
|
isset($attendee['SCHEDULE-AGENT']) &&
|
||||||
|
'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue())
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$partStat =
|
||||||
|
isset($attendee['PARTSTAT']) ?
|
||||||
|
strtoupper($attendee['PARTSTAT']) :
|
||||||
|
'NEEDS-ACTION';
|
||||||
|
|
||||||
|
$forceSend =
|
||||||
|
isset($attendee['SCHEDULE-FORCE-SEND']) ?
|
||||||
|
strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (isset($attendees[$attendee->getNormalizedValue()])) {
|
||||||
|
$attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
|
||||||
|
'id' => $recurId,
|
||||||
|
'partstat' => $partStat,
|
||||||
|
'forceSend' => $forceSend,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$attendees[$attendee->getNormalizedValue()] = [
|
||||||
|
'href' => $attendee->getNormalizedValue(),
|
||||||
|
'instances' => [
|
||||||
|
$recurId => [
|
||||||
|
'id' => $recurId,
|
||||||
|
'partstat' => $partStat,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'name' => isset($attendee['CN']) ? (string) $attendee['CN'] : null,
|
||||||
|
'forceSend' => $forceSend,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$instances[$recurId] = $vevent;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->significantChangeProperties as $prop) {
|
||||||
|
if (isset($vevent->$prop)) {
|
||||||
|
$propertyValues = $vevent->select($prop);
|
||||||
|
|
||||||
|
$eventSignificantChangeHash .= $prop.':';
|
||||||
|
|
||||||
|
if ('EXDATE' === $prop) {
|
||||||
|
$eventSignificantChangeHash .= implode(',', $exdate).';';
|
||||||
|
} elseif ('RRULE' === $prop) {
|
||||||
|
$eventSignificantChangeHash .= implode(',', $rrule).';';
|
||||||
|
} else {
|
||||||
|
foreach ($propertyValues as $val) {
|
||||||
|
$eventSignificantChangeHash .= $val->getValue().';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$significantChangeEventProperties[] = $eventSignificantChangeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
asort($significantChangeEventProperties);
|
||||||
|
|
||||||
|
foreach ($significantChangeEventProperties as $eventSignificantChangeHash) {
|
||||||
|
$significantChangeHash .= $eventSignificantChangeHash;
|
||||||
|
}
|
||||||
|
$significantChangeHash = md5($significantChangeHash);
|
||||||
|
|
||||||
|
return compact(
|
||||||
|
'uid',
|
||||||
|
'organizer',
|
||||||
|
'organizerName',
|
||||||
|
'organizerScheduleAgent',
|
||||||
|
'organizerForceSend',
|
||||||
|
'instances',
|
||||||
|
'attendees',
|
||||||
|
'sequence',
|
||||||
|
'exdate',
|
||||||
|
'timezone',
|
||||||
|
'significantChangeHash',
|
||||||
|
'status'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\ITip;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message is emitted in case of serious problems with iTip messages.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class ITipException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
136
snappymail/v/0.0.0/app/libraries/Sabre/VObject/ITip/Message.php
Normal file
136
snappymail/v/0.0.0/app/libraries/Sabre/VObject/ITip/Message.php
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\ITip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an iTip message.
|
||||||
|
*
|
||||||
|
* A message holds all the information relevant to the message, including the
|
||||||
|
* object itself.
|
||||||
|
*
|
||||||
|
* It should for the most part be treated as immutable.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Message
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The object's UID.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component type, such as VEVENT.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the ITip method, which is something like REQUEST, REPLY or
|
||||||
|
* CANCEL.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current sequence number for the event.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $sequence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The senders' email address.
|
||||||
|
*
|
||||||
|
* Note that this does not imply that this has to be used in a From: field
|
||||||
|
* if the message is sent by email. It may also be populated in Reply-To:
|
||||||
|
* or not at all.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $sender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the sender. This is often populated from a CN parameter from
|
||||||
|
* either the ORGANIZER or ATTENDEE, depending on the message.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $senderName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The recipient's email address.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $recipient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the recipient. This is usually populated with the CN
|
||||||
|
* parameter from the ATTENDEE or ORGANIZER property, if it's available.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $recipientName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the message has been delivered, this should contain a string such
|
||||||
|
* as : 1.1;Sent or 1.2;Delivered.
|
||||||
|
*
|
||||||
|
* In case of a failure, this will hold the error status code.
|
||||||
|
*
|
||||||
|
* See:
|
||||||
|
* http://tools.ietf.org/html/rfc6638#section-7.3
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $scheduleStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The iCalendar / iTip body.
|
||||||
|
*
|
||||||
|
* @var \Sabre\VObject\Component\VCalendar
|
||||||
|
*/
|
||||||
|
public $message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will be set to true, if the iTip broker considers the change
|
||||||
|
* 'significant'.
|
||||||
|
*
|
||||||
|
* In practice, this means that we'll only mark it true, if for instance
|
||||||
|
* DTSTART changed. This allows systems to only send iTip messages when
|
||||||
|
* significant changes happened. This is especially useful for iMip, as
|
||||||
|
* normally a ton of messages may be generated for normal calendar use.
|
||||||
|
*
|
||||||
|
* To see the list of properties that are considered 'significant', check
|
||||||
|
* out Sabre\VObject\ITip\Broker::$significantChangeProperties.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $significantChange = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schedule status as a string.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1.2
|
||||||
|
*
|
||||||
|
* @return mixed bool|string
|
||||||
|
*/
|
||||||
|
public function getScheduleStatus()
|
||||||
|
{
|
||||||
|
if (!$this->scheduleStatus) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
list($scheduleStatus) = explode(';', $this->scheduleStatus);
|
||||||
|
|
||||||
|
return $scheduleStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\ITip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SameOrganizerForAllComponentsException.
|
||||||
|
*
|
||||||
|
* This exception is emitted when an event is encountered with more than one
|
||||||
|
* component (e.g.: exceptions), but the organizer is not identical in every
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class SameOrganizerForAllComponentsException extends ITipException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown whenever an invalid value is found anywhere in a
|
||||||
|
* iCalendar or vCard object.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class InvalidDataException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -2,20 +2,43 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node is the root class for every element in an iCalendar of vCard object.
|
* A node is the root class for every element in an iCalendar of vCard object.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
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.
|
||||||
|
*
|
||||||
|
* If REPAIR is set, the validator will attempt to repair any broken data
|
||||||
|
* (if possible).
|
||||||
*/
|
*/
|
||||||
const REPAIR = 1;
|
const REPAIR = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this option is set, the validator will operate on the vcards on the
|
||||||
|
* assumption that the vcards need to be valid for CardDAV.
|
||||||
|
*
|
||||||
|
* This means for example that the UID is required, whereas it is not for
|
||||||
|
* regular vcards.
|
||||||
|
*/
|
||||||
|
const PROFILE_CARDDAV = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this option is set, the validator will operate on iCalendar objects
|
||||||
|
* on the assumption that the vcards need to be valid for CalDAV.
|
||||||
|
*
|
||||||
|
* This means for example that calendars can only contain objects with
|
||||||
|
* identical component types and UIDs.
|
||||||
|
*/
|
||||||
|
const PROFILE_CALDAV = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the parent object, if this is not the top object.
|
* Reference to the parent object, if this is not the top object.
|
||||||
*
|
*
|
||||||
|
|
@ -24,85 +47,107 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
public $parent;
|
public $parent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterator override
|
* Iterator override.
|
||||||
*
|
*
|
||||||
* @var ElementList
|
* @var ElementList
|
||||||
*/
|
*/
|
||||||
protected $iterator = null;
|
protected $iterator = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The root document
|
* The root document.
|
||||||
*
|
*
|
||||||
* @var Component
|
* @var Component
|
||||||
*/
|
*/
|
||||||
protected $root;
|
protected $root;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the node into a mimedir format
|
* Serializes the node into a mimedir format.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
abstract function serialize();
|
abstract public 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
|
||||||
* encoded in json. This is used to create jCard or jCal documents.
|
* encoded in JSON. This is used to create jCard or jCal documents.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
abstract function jsonSerialize();
|
#[\ReturnTypeWillChange]
|
||||||
|
abstract public function jsonSerialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes the data into XML. This is used to create xCard or
|
||||||
|
* xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
abstract public function xmlSerialize(Xml\Writer $writer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 up.
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
$this->parent = null;
|
||||||
|
$this->root = null;
|
||||||
|
}
|
||||||
|
|
||||||
/* {{{ IteratorAggregator interface */
|
/* {{{ IteratorAggregator interface */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the iterator for this object
|
* Returns the iterator for this object.
|
||||||
*
|
*
|
||||||
* @return ElementList
|
* @return ElementList
|
||||||
*/
|
*/
|
||||||
public function getIterator() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function getIterator()
|
||||||
if (!is_null($this->iterator))
|
{
|
||||||
|
if (!is_null($this->iterator)) {
|
||||||
return $this->iterator;
|
return $this->iterator;
|
||||||
|
}
|
||||||
|
|
||||||
return new ElementList(array($this));
|
return new ElementList([$this]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the overridden iterator
|
* Sets the overridden iterator.
|
||||||
*
|
*
|
||||||
* 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
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setIterator(ElementList $iterator) {
|
public function setIterator(ElementList $iterator)
|
||||||
|
{
|
||||||
$this->iterator = $iterator;
|
$this->iterator = $iterator;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the node for correctness.
|
* Validates the node for correctness.
|
||||||
*
|
*
|
||||||
* The following options are supported:
|
* The following options are supported:
|
||||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
* be attempted.
|
|
||||||
*
|
*
|
||||||
* An array is returned with warnings.
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
*
|
*
|
||||||
* Every item in the array has the following properties:
|
* * level - problem level.
|
||||||
* * level - (number between 1 and 3 with severity information)
|
* * message - A human-readable string describing the issue.
|
||||||
* * message - (human readable message)
|
* * node - A reference to the problematic node.
|
||||||
* * node - (reference to the offending node)
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
*
|
*
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
return array();
|
return [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
@ -110,35 +155,37 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
/* {{{ Countable interface */
|
/* {{{ Countable interface */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of elements
|
* Returns the number of elements.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function count() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public 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.
|
||||||
*
|
*
|
||||||
* 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 bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function offsetExists($offset) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
$iterator = $this->getIterator();
|
$iterator = $this->getIterator();
|
||||||
return $iterator->offsetExists($offset);
|
|
||||||
|
|
||||||
|
return $iterator->offsetExists($offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,13 +194,15 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
* 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 mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function offsetGet($offset) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
$iterator = $this->getIterator();
|
$iterator = $this->getIterator();
|
||||||
return $iterator->offsetGet($offset);
|
|
||||||
|
|
||||||
|
return $iterator->offsetGet($offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,10 +212,10 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
*
|
*
|
||||||
* @param int $offset
|
* @param int $offset
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function offsetSet($offset,$value) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
$iterator = $this->getIterator();
|
$iterator = $this->getIterator();
|
||||||
$iterator->offsetSet($offset, $value);
|
$iterator->offsetSet($offset, $value);
|
||||||
|
|
||||||
|
|
@ -175,6 +224,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
// 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,10 +233,10 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
* 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) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
$iterator = $this->getIterator();
|
$iterator = $this->getIterator();
|
||||||
$iterator->offsetUnset($offset);
|
$iterator->offsetUnset($offset);
|
||||||
|
|
||||||
|
|
@ -195,6 +245,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||||
// 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
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHPUnit Assertions.
|
||||||
|
*
|
||||||
|
* This trait can be added to your unittest to make it easier to test iCalendar
|
||||||
|
* and/or vCards.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
trait PHPUnitAssertions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This method tests whether two vcards or icalendar objects are
|
||||||
|
* semantically identical.
|
||||||
|
*
|
||||||
|
* It supports objects being supplied as strings, streams or
|
||||||
|
* Sabre\VObject\Component instances.
|
||||||
|
*
|
||||||
|
* PRODID is removed from both objects as this is often changes and would
|
||||||
|
* just get in the way.
|
||||||
|
*
|
||||||
|
* CALSCALE will automatically get removed if it's set to GREGORIAN.
|
||||||
|
*
|
||||||
|
* Any property that has the value **ANY** will be treated as a wildcard.
|
||||||
|
*
|
||||||
|
* @param resource|string|Component $expected
|
||||||
|
* @param resource|string|Component $actual
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function assertVObjectEqualsVObject($expected, $actual, $message = '')
|
||||||
|
{
|
||||||
|
$getObj = function ($input) {
|
||||||
|
if (is_resource($input)) {
|
||||||
|
$input = stream_get_contents($input);
|
||||||
|
}
|
||||||
|
if (is_string($input)) {
|
||||||
|
$input = Reader::read($input);
|
||||||
|
}
|
||||||
|
if (!$input instanceof Component) {
|
||||||
|
$this->fail('Input must be a string, stream or VObject component');
|
||||||
|
}
|
||||||
|
unset($input->PRODID);
|
||||||
|
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) {
|
||||||
|
unset($input->CALSCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
};
|
||||||
|
|
||||||
|
$expected = $getObj($expected)->serialize();
|
||||||
|
$actual = $getObj($actual)->serialize();
|
||||||
|
|
||||||
|
// Finding wildcards in expected.
|
||||||
|
preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$actual = preg_replace(
|
||||||
|
'|^'.preg_quote($match[1], '|').':(.*)\r$|m',
|
||||||
|
$match[1].':**ANY**'."\r",
|
||||||
|
$actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
$expected,
|
||||||
|
$actual,
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,25 +2,25 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
use
|
use ArrayIterator;
|
||||||
ArrayObject;
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VObject Parameter
|
* VObject Parameter.
|
||||||
*
|
*
|
||||||
* This class represents a parameter. A parameter is always tied to a property.
|
* This class represents a parameter. A parameter is always tied to a property.
|
||||||
* In the case of:
|
* In the case of:
|
||||||
* DTSTART;VALUE=DATE:20101108
|
* DTSTART;VALUE=DATE:20101108
|
||||||
* VALUE=DATE would be the parameter name and value.
|
* VALUE=DATE would be the parameter name and value.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Parameter extends Node {
|
class Parameter extends Node
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Parameter name
|
* Parameter name.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -29,14 +29,14 @@ 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 it's value.
|
* We can deduce the parameter name based on its value.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $noName = false;
|
public $noName = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter value
|
* Parameter value.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -50,30 +50,40 @@ class Parameter extends Node {
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $value
|
* @param string $value
|
||||||
*/
|
*/
|
||||||
public function __construct(Document $root, $name, $value = null) {
|
public 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)) {
|
||||||
$this->noName = true;
|
$this->noName = true;
|
||||||
$this->name = static::guessParameterNameByValue($value);
|
$this->name = static::guessParameterNameByValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If guessParameterNameByValue() returns an empty string
|
||||||
|
// above, we're actually dealing with a parameter that has no value.
|
||||||
|
// In that case we have to move the value to the name.
|
||||||
|
if ('' === $this->name) {
|
||||||
|
$this->noName = false;
|
||||||
|
$this->name = strtoupper($value);
|
||||||
|
} else {
|
||||||
$this->setValue($value);
|
$this->setValue($value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
|
* Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
|
||||||
*
|
*
|
||||||
* Figuring out what the name should have been. Note that a ton of
|
* Figuring out what the name should have been. Note that a ton of
|
||||||
* these are rather silly in 2013 and would probably rarely be
|
* these are rather silly in 2014 and would probably rarely be
|
||||||
* used, but we like to be complete.
|
* used, but we like to be complete.
|
||||||
*
|
*
|
||||||
* @param string $value
|
* @param string $value
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function guessParameterNameByValue($value) {
|
public static function guessParameterNameByValue($value)
|
||||||
|
{
|
||||||
switch (strtoupper($value)) {
|
switch (strtoupper($value)) {
|
||||||
|
|
||||||
// Encodings
|
// Encodings
|
||||||
case '7-BIT':
|
case '7-BIT':
|
||||||
case 'QUOTED-PRINTABLE':
|
case 'QUOTED-PRINTABLE':
|
||||||
|
|
@ -85,13 +95,11 @@ class Parameter extends Node {
|
||||||
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':
|
||||||
|
|
@ -103,7 +111,6 @@ class Parameter extends Node {
|
||||||
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':
|
||||||
|
|
@ -117,7 +124,6 @@ class Parameter extends Node {
|
||||||
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':
|
||||||
|
|
@ -133,12 +139,10 @@ class Parameter extends Node {
|
||||||
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':
|
||||||
|
|
@ -166,42 +170,35 @@ 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) {
|
public function setValue($value)
|
||||||
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current value
|
* Returns the current value.
|
||||||
*
|
*
|
||||||
* This method will always return a string, or null. If there were multiple
|
* This method will always return a string, or null. If there were multiple
|
||||||
* values, it will automatically concatinate them (separated by comma).
|
* values, it will automatically concatenate them (separated by comma).
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getValue() {
|
public 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
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setParts(array $value) {
|
public function setParts(array $value)
|
||||||
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -211,35 +208,32 @@ class Parameter extends Node {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getParts() {
|
public 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)) {
|
||||||
return array();
|
return [];
|
||||||
} else {
|
} else {
|
||||||
return array($this->value);
|
return [$this->value];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a value to this parameter
|
* Adds a value to this parameter.
|
||||||
*
|
*
|
||||||
* If the argument is specified as an array, all items will be added to the
|
* If the argument is specified as an array, all items will be added to the
|
||||||
* parameter value list.
|
* parameter value list.
|
||||||
*
|
*
|
||||||
* @param string|array $part
|
* @param string|array $part
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function addValue($part) {
|
public 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -250,15 +244,15 @@ class Parameter extends Node {
|
||||||
* 'WORK' or 'FAX'.
|
* 'WORK' or 'FAX'.
|
||||||
*
|
*
|
||||||
* @param string $value
|
* @param string $value
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function has($value) {
|
public 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)
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -266,78 +260,108 @@ class Parameter extends Node {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function serialize() {
|
public function serialize()
|
||||||
|
{
|
||||||
$value = $this->getParts();
|
$value = $this->getParts();
|
||||||
|
|
||||||
if (count($value)===0) {
|
if (0 === count($value)) {
|
||||||
return $this->name;
|
return $this->name.'=';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
|
if (Document::VCARD21 === $this->root->getDocumentType() && $this->noName) {
|
||||||
|
|
||||||
return implode(';', $value);
|
return implode(';', $value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->name . '=' . array_reduce($value, function($out, $item) {
|
return $this->name.'='.array_reduce(
|
||||||
|
$value,
|
||||||
if (!is_null($out)) $out.=',';
|
function ($out, $item) {
|
||||||
|
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.
|
||||||
if (!preg_match('#(?: [\n":;\^,] )#x', $item)) {
|
//
|
||||||
|
// The list of special characters is defined as:
|
||||||
|
//
|
||||||
|
// Any character except CONTROL, DQUOTE, ";", ":", ","
|
||||||
|
//
|
||||||
|
// by the iCalendar spec:
|
||||||
|
// https://tools.ietf.org/html/rfc5545#section-3.1
|
||||||
|
//
|
||||||
|
// And we add ^ to that because of:
|
||||||
|
// https://tools.ietf.org/html/rfc6868
|
||||||
|
//
|
||||||
|
// But we've found that iCal (7.0, shipped with OSX 10.9)
|
||||||
|
// severely trips on + characters not being quoted, so we
|
||||||
|
// added + as well.
|
||||||
|
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($item, array(
|
$out .= '"'.strtr(
|
||||||
|
$item,
|
||||||
|
[
|
||||||
'^' => '^^',
|
'^' => '^^',
|
||||||
"\n" => '^n',
|
"\n" => '^n',
|
||||||
'"' => '^\'',
|
'"' => '^\'',
|
||||||
)) . '"';
|
]
|
||||||
|
).'"';
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns an array, with the representation as it should be
|
* This method returns an array, with the representation as it should be
|
||||||
* encoded in json. This is used to create jCard or jCal documents.
|
* encoded in JSON. This is used to create jCard or jCal documents.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this object is being cast to a string
|
* This method serializes the data into XML. This is used to create xCard or
|
||||||
|
* xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
foreach (explode(',', $this->value) as $value) {
|
||||||
|
$writer->writeElement('text', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when this object is being cast to a string.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function __toString() {
|
public function __toString()
|
||||||
|
{
|
||||||
return $this->getValue();
|
return (string) $this->getValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the iterator for this object
|
* Returns the iterator for this object.
|
||||||
*
|
*
|
||||||
* @return ElementList
|
* @return ElementList
|
||||||
*/
|
*/
|
||||||
public function getIterator() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function getIterator()
|
||||||
if (!is_null($this->iterator))
|
{
|
||||||
|
if (!is_null($this->iterator)) {
|
||||||
return $this->iterator;
|
return $this->iterator;
|
||||||
|
|
||||||
return $this->iterator = new ArrayObject((array)$this->value);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->iterator = new ArrayIterator((array) $this->value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ namespace Sabre\VObject;
|
||||||
/**
|
/**
|
||||||
* Exception thrown by Reader if an invalid object was attempted to be parsed.
|
* Exception thrown by Reader if an invalid object was attempted to be parsed.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class ParseException extends \Exception { }
|
class ParseException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,34 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Parser;
|
namespace Sabre\VObject\Parser;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Component\VCalendar;
|
||||||
Sabre\VObject\Component\VCalendar,
|
use Sabre\VObject\Component\VCard;
|
||||||
Sabre\VObject\Component\VCard,
|
use Sabre\VObject\Document;
|
||||||
Sabre\VObject\ParseException,
|
use Sabre\VObject\EofException;
|
||||||
Sabre\VObject\EofException;
|
use Sabre\VObject\ParseException;
|
||||||
|
use Sabre\VObject\Property\FlatText;
|
||||||
|
use Sabre\VObject\Property\Text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Json Parser.
|
* Json Parser.
|
||||||
*
|
*
|
||||||
* This parser parses both the jCal and jCard formats.
|
* This parser parses both the jCal and jCard formats.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Json extends Parser {
|
class Json extends Parser
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* The input data
|
* The input data.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $input;
|
protected $input;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root component
|
* Root component.
|
||||||
*
|
*
|
||||||
* @var Document
|
* @var Document
|
||||||
*/
|
*/
|
||||||
|
|
@ -42,11 +44,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|null $options
|
* @param int $options
|
||||||
* @return array
|
*
|
||||||
|
* @return \Sabre\VObject\Document
|
||||||
*/
|
*/
|
||||||
public function parse($input = null, $options = null) {
|
public function parse($input = null, $options = 0)
|
||||||
|
{
|
||||||
if (!is_null($input)) {
|
if (!is_null($input)) {
|
||||||
$this->setInput($input);
|
$this->setInput($input);
|
||||||
}
|
}
|
||||||
|
|
@ -54,74 +57,77 @@ class Json extends Parser {
|
||||||
throw new EofException('End of input stream, or no input supplied');
|
throw new EofException('End of input stream, or no input supplied');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($options)) {
|
if (0 !== $options) {
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->input[0]) {
|
switch ($this->input[0]) {
|
||||||
case 'vcalendar':
|
case 'vcalendar':
|
||||||
$this->root = new VCalendar(array(), false);
|
$this->root = new VCalendar([], false);
|
||||||
break;
|
break;
|
||||||
case 'vcard':
|
case 'vcard':
|
||||||
$this->root = new VCard(array(), 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])) foreach($this->input[2] as $comp) {
|
if (isset($this->input[2])) {
|
||||||
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a component
|
* Parses a component.
|
||||||
*
|
*
|
||||||
* @param array $jComp
|
|
||||||
* @return \Sabre\VObject\Component
|
* @return \Sabre\VObject\Component
|
||||||
*/
|
*/
|
||||||
public function parseComponent(array $jComp) {
|
public 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(function($jProp) use ($self) {
|
$properties = array_map(
|
||||||
|
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 = array();
|
);
|
||||||
|
} 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
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses properties.
|
* Parses properties.
|
||||||
*
|
*
|
||||||
* @param array $jProp
|
|
||||||
* @return \Sabre\VObject\Property
|
* @return \Sabre\VObject\Property
|
||||||
*/
|
*/
|
||||||
public function parseProperty(array $jProp) {
|
public function parseProperty(array $jProp)
|
||||||
|
{
|
||||||
list(
|
list(
|
||||||
$propertyName,
|
$propertyName,
|
||||||
$parameters,
|
$parameters,
|
||||||
|
|
@ -152,29 +158,27 @@ 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 ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
|
if (FlatText::class === $defaultPropertyClass) {
|
||||||
$defaultPropertyClass = 'Sabre\VObject\Property\Text';
|
$defaultPropertyClass = Text::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the value type we received (e.g.: TEXT) was not the default value
|
// If the value type we received (e.g.: TEXT) was not the default value
|
||||||
// type for the given property (e.g.: BDAY), we need to add a VALUE=
|
// 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) {
|
public function setInput($input)
|
||||||
|
{
|
||||||
if (is_resource($input)) {
|
if (is_resource($input)) {
|
||||||
$input = stream_get_contents($input);
|
$input = stream_get_contents($input);
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +186,5 @@ class Json extends Parser {
|
||||||
$input = json_decode($input);
|
$input = json_decode($input);
|
||||||
}
|
}
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,29 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Parser;
|
namespace Sabre\VObject\Parser;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Component;
|
||||||
Sabre\VObject\ParseException,
|
use Sabre\VObject\Component\VCalendar;
|
||||||
Sabre\VObject\EofException,
|
use Sabre\VObject\Component\VCard;
|
||||||
Sabre\VObject\Component,
|
use Sabre\VObject\Document;
|
||||||
Sabre\VObject\Property,
|
use Sabre\VObject\EofException;
|
||||||
Sabre\VObject\Component\VCalendar,
|
use Sabre\VObject\Node;
|
||||||
Sabre\VObject\Component\VCard;
|
use Sabre\VObject\ParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MimeDir parser.
|
* MimeDir parser.
|
||||||
*
|
*
|
||||||
* This class parses iCalendar/vCard files and returns an array.
|
* This class parses iCalendar 2.0 and vCard 2.1, 3.0 and 4.0 files. This
|
||||||
|
* parser will return one of the following two objects from the parse method:
|
||||||
*
|
*
|
||||||
* The array is identical to the format jCard/jCal use.
|
* Sabre\VObject\Component\VCalendar
|
||||||
|
* Sabre\VObject\Component\VCard
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class MimeDir extends Parser {
|
class MimeDir extends Parser
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* The input stream.
|
* The input stream.
|
||||||
*
|
*
|
||||||
|
|
@ -31,135 +33,183 @@ class MimeDir extends Parser {
|
||||||
protected $input;
|
protected $input;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root component
|
* Root component.
|
||||||
*
|
*
|
||||||
* @var Component
|
* @var Component
|
||||||
*/
|
*/
|
||||||
protected $root;
|
protected $root;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an iCalendar or vCard file
|
* By default all input will be assumed to be UTF-8.
|
||||||
|
*
|
||||||
|
* However, both iCalendar and vCard might be encoded using different
|
||||||
|
* character sets. The character set is usually set in the mime-type.
|
||||||
|
*
|
||||||
|
* If this is the case, use setEncoding to specify that a different
|
||||||
|
* encoding will be used. If this is set, the parser will automatically
|
||||||
|
* convert all incoming data to UTF-8.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $charset = 'UTF-8';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of character sets we support when decoding.
|
||||||
|
*
|
||||||
|
* This would be a const expression but for now we need to support PHP 5.5
|
||||||
|
*/
|
||||||
|
protected static $SUPPORTED_CHARSETS = [
|
||||||
|
'UTF-8',
|
||||||
|
'ISO-8859-1',
|
||||||
|
'Windows-1252',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an iCalendar or vCard file.
|
||||||
*
|
*
|
||||||
* Pass a stream or a string. If null is parsed, the existing buffer is
|
* Pass a stream or a string. If null is parsed, the existing buffer is
|
||||||
* used.
|
* used.
|
||||||
*
|
*
|
||||||
* @param string|resource|null $input
|
* @param string|resource|null $input
|
||||||
* @param int|null $options
|
* @param int $options
|
||||||
* @return array
|
*
|
||||||
|
* @return \Sabre\VObject\Document
|
||||||
*/
|
*/
|
||||||
public function parse($input = null, $options = null) {
|
public function parse($input = null, $options = 0)
|
||||||
|
{
|
||||||
$this->root = null;
|
$this->root = null;
|
||||||
|
|
||||||
if (!is_null($input)) {
|
if (!is_null($input)) {
|
||||||
|
|
||||||
$this->setInput($input);
|
$this->setInput($input);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($options)) $this->options = $options;
|
if (0 !== $options) {
|
||||||
|
$this->options = $options;
|
||||||
|
}
|
||||||
|
|
||||||
$this->parseDocument();
|
$this->parseDocument();
|
||||||
|
|
||||||
return $this->root;
|
return $this->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default all input will be assumed to be UTF-8.
|
||||||
|
*
|
||||||
|
* However, both iCalendar and vCard might be encoded using different
|
||||||
|
* character sets. The character set is usually set in the mime-type.
|
||||||
|
*
|
||||||
|
* If this is the case, use setEncoding to specify that a different
|
||||||
|
* encoding will be used. If this is set, the parser will automatically
|
||||||
|
* convert all incoming data to UTF-8.
|
||||||
|
*
|
||||||
|
* @param string $charset
|
||||||
|
*/
|
||||||
|
public function setCharset($charset)
|
||||||
|
{
|
||||||
|
if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
|
||||||
|
throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')');
|
||||||
|
}
|
||||||
|
$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) {
|
public function setInput($input)
|
||||||
|
{
|
||||||
// Resetting the parser
|
// Resetting the parser
|
||||||
$this->lineIndex = 0;
|
$this->lineIndex = 0;
|
||||||
$this->startLine = 0;
|
$this->startLine = 0;
|
||||||
|
|
||||||
if (is_string($input)) {
|
if (is_string($input)) {
|
||||||
// Convering to a stream.
|
// Converting to a stream.
|
||||||
$stream = fopen('php://temp', 'r+');
|
$stream = fopen('php://temp', 'r+');
|
||||||
fwrite($stream, $input);
|
fwrite($stream, $input);
|
||||||
rewind($stream);
|
rewind($stream);
|
||||||
$this->input = $stream;
|
$this->input = $stream;
|
||||||
} else {
|
} elseif (is_resource($input)) {
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
|
} else {
|
||||||
|
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).
|
||||||
|
// It's 0xEF 0xBB 0xBF in UTF-8 hex.
|
||||||
|
if (3 <= strlen($line)
|
||||||
|
&& 0xef === ord($line[0])
|
||||||
|
&& 0xbb === ord($line[1])
|
||||||
|
&& 0xbf === ord($line[2])) {
|
||||||
|
$line = substr($line, 3);
|
||||||
|
}
|
||||||
|
|
||||||
switch (strtoupper($line)) {
|
switch (strtoupper($line)) {
|
||||||
case 'BEGIN:VCALENDAR':
|
case 'BEGIN:VCALENDAR':
|
||||||
$class = isset(VCalendar::$componentMap['VCALENDAR'])
|
$class = VCalendar::$componentMap['VCALENDAR'];
|
||||||
? VCalendar::$componentMap[$name]
|
|
||||||
: 'Sabre\\VObject\\Component\\VCalendar';
|
|
||||||
break;
|
break;
|
||||||
case 'BEGIN:VCARD':
|
case 'BEGIN:VCARD':
|
||||||
$class = isset(VCard::$componentMap['VCARD'])
|
$class = VCard::$componentMap['VCARD'];
|
||||||
? VCard::$componentMap['VCARD']
|
|
||||||
: 'Sabre\\VObject\\Component\\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(array(), 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 (strtoupper(substr($line,0,4)) === 'END:') {
|
if ('END:' === strtoupper(substr($line, 0, 4))) {
|
||||||
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.'"');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a line, and if it hits a component, it will also attempt to parse
|
* Parses a line, and if it hits a component, it will also attempt to parse
|
||||||
* the entire component
|
* the entire component.
|
||||||
*
|
*
|
||||||
* @param string $line Unfolded line
|
* @param string $line Unfolded line
|
||||||
|
*
|
||||||
* @return Node
|
* @return Node
|
||||||
*/
|
*/
|
||||||
protected function parseLine($line) {
|
protected function parseLine($line)
|
||||||
|
{
|
||||||
// Start of a new component
|
// Start of a new component
|
||||||
if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
|
if ('BEGIN:' === strtoupper(substr($line, 0, 6))) {
|
||||||
|
if (substr($line, 6) === $this->root->name) {
|
||||||
$component = $this->root->createComponent(substr($line,6), array(), false);
|
throw new ParseException('Invalid MimeDir file. Unexpected component: "'.$line.'" in document type '.$this->root->name);
|
||||||
|
}
|
||||||
|
$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 (strtoupper(substr($line,0,4)) === 'END:') {
|
if ('END:' === strtoupper(substr($line, 0, 4))) {
|
||||||
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));
|
||||||
|
|
@ -168,19 +218,16 @@ class MimeDir extends Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -189,7 +236,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 null|string
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
protected $lineBuffer;
|
protected $lineBuffer;
|
||||||
|
|
||||||
|
|
@ -219,22 +266,29 @@ class MimeDir extends Parser {
|
||||||
* This method strips any newlines and also takes care of unfolding.
|
* This method strips any newlines and also takes care of unfolding.
|
||||||
*
|
*
|
||||||
* @throws \Sabre\VObject\EofException
|
* @throws \Sabre\VObject\EofException
|
||||||
|
*
|
||||||
* @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;
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
$rawLine = fgets($this->input);
|
$eof = \feof($this->input);
|
||||||
if ($rawLine === false && feof($this->input)) {
|
|
||||||
|
$rawLine = \fgets($this->input);
|
||||||
|
|
||||||
|
if ($eof || (\feof($this->input) && false === $rawLine)) {
|
||||||
throw new EofException('End of document reached prematurely');
|
throw new EofException('End of document reached prematurely');
|
||||||
}
|
}
|
||||||
$rawLine = rtrim($rawLine, "\r\n");
|
if (false === $rawLine) {
|
||||||
} while ($rawLine === ''); // Skipping empty lines
|
throw new ParseException('Error reading from input stream');
|
||||||
$this->lineIndex++;
|
}
|
||||||
|
$rawLine = \rtrim($rawLine, "\r\n");
|
||||||
|
} while ('' === $rawLine); // Skipping empty lines
|
||||||
|
++$this->lineIndex;
|
||||||
}
|
}
|
||||||
$line = $rawLine;
|
$line = $rawLine;
|
||||||
|
|
||||||
|
|
@ -242,33 +296,30 @@ 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 ($nextLine[0] === "\t" || $nextLine[0] === " ") {
|
if ("\t" === $nextLine[0] || ' ' === $nextLine[0]) {
|
||||||
$line .= substr($nextLine, 1);
|
$curLine = \substr($nextLine, 1);
|
||||||
$rawLine .= "\n " . substr($nextLine, 1);
|
$line .= $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 {
|
||||||
|
|
@ -282,7 +333,7 @@ class MimeDir extends Parser {
|
||||||
$regex = "/
|
$regex = "/
|
||||||
^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name
|
^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name
|
||||||
|
|
|
|
||||||
(?<=:)(?P<propValue> .*)$ # property value
|
(?<=:)(?P<propValue> .+)$ # property value
|
||||||
|
|
|
|
||||||
;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name
|
;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name
|
||||||
|
|
|
|
||||||
|
|
@ -292,18 +343,18 @@ class MimeDir extends Parser {
|
||||||
) (?=[;:,])
|
) (?=[;:,])
|
||||||
/xi";
|
/xi";
|
||||||
|
|
||||||
//echo $regex, "\n"; die();
|
//echo $regex, "\n"; exit();
|
||||||
preg_match_all($regex, $line, $matches, PREG_SET_ORDER);
|
preg_match_all($regex, $line, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
$property = array(
|
$property = [
|
||||||
'name' => null,
|
'name' => null,
|
||||||
'parameters' => array(),
|
'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
|
||||||
|
|
@ -311,9 +362,8 @@ 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'];
|
||||||
|
|
@ -321,15 +371,18 @@ class MimeDir extends Parser {
|
||||||
|
|
||||||
$value = $this->unescapeParam($value);
|
$value = $this->unescapeParam($value);
|
||||||
|
|
||||||
|
if (is_null($lastParam)) {
|
||||||
|
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;
|
||||||
} elseif (is_array($property['parameters'][$lastParam])) {
|
} elseif (is_array($property['parameters'][$lastParam])) {
|
||||||
$property['parameters'][$lastParam][] = $value;
|
$property['parameters'][$lastParam][] = $value;
|
||||||
} else {
|
} else {
|
||||||
$property['parameters'][$lastParam] = array(
|
$property['parameters'][$lastParam] = [
|
||||||
$property['parameters'][$lastParam],
|
$property['parameters'][$lastParam],
|
||||||
$value
|
$value,
|
||||||
);
|
];
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -352,10 +405,12 @@ 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']) || !$property['name']) {
|
if (is_null($property['value'])) {
|
||||||
|
$property['value'] = '';
|
||||||
|
}
|
||||||
|
if (!$property['name']) {
|
||||||
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -363,12 +418,12 @@ class MimeDir extends Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 it's name.
|
// a value. We can deduce the value based on its 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.
|
||||||
$namedParameters = array();
|
$namedParameters = [];
|
||||||
$namelessParameters = array();
|
$namelessParameters = [];
|
||||||
|
|
||||||
foreach ($property['parameters'] as $name => $value) {
|
foreach ($property['parameters'] as $name => $value) {
|
||||||
if (!is_null($value)) {
|
if (!is_null($value)) {
|
||||||
|
|
@ -384,14 +439,30 @@ class MimeDir extends Parser {
|
||||||
$propObj->add(null, $namelessParameter);
|
$propObj->add(null, $namelessParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
|
if (isset($propObj['ENCODING']) && 'QUOTED-PRINTABLE' === strtoupper($propObj['ENCODING'])) {
|
||||||
$propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
|
$propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
|
||||||
} else {
|
} else {
|
||||||
|
$charset = $this->charset;
|
||||||
|
if (Document::VCARD21 === $this->root->getDocumentType() && isset($propObj['CHARSET'])) {
|
||||||
|
// vCard 2.1 allows the character set to be specified per property.
|
||||||
|
$charset = (string) $propObj['CHARSET'];
|
||||||
|
}
|
||||||
|
switch (strtolower($charset)) {
|
||||||
|
case 'utf-8':
|
||||||
|
break;
|
||||||
|
case 'iso-8859-1':
|
||||||
|
$property['value'] = utf8_encode($property['value']);
|
||||||
|
break;
|
||||||
|
case 'windows-1252':
|
||||||
|
$property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ParseException('Unsupported CHARSET: '.$propObj['CHARSET']);
|
||||||
|
}
|
||||||
$propObj->setRawMimeDirValue($property['value']);
|
$propObj->setRawMimeDirValue($property['value']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $propObj;
|
return $propObj;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -409,7 +480,7 @@ class MimeDir extends Parser {
|
||||||
* vCard 3.0 says:
|
* vCard 3.0 says:
|
||||||
* * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
|
* * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
|
||||||
* escaped, all time time.
|
* escaped, all time time.
|
||||||
* * Comma's are used for delimeters in multiple values
|
* * Comma's are used for delimiters in multiple values
|
||||||
* * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
|
* * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
|
||||||
* as in some properties semi-colon is used for separators.
|
* as in some properties semi-colon is used for separators.
|
||||||
* * Properties using semi-colons: N, ADR, GEO, ORG
|
* * Properties using semi-colons: N, ADR, GEO, ORG
|
||||||
|
|
@ -447,16 +518,17 @@ class MimeDir extends Parser {
|
||||||
*
|
*
|
||||||
* Now for the parameters
|
* Now for the parameters
|
||||||
*
|
*
|
||||||
* If delimiter is not set (null) this method will just return a string.
|
* If delimiter is not set (empty string) this method will just return a string.
|
||||||
* If it's a comma or a semi-colon the string will be split on those
|
* If it's a comma or a semi-colon the string will be split on those
|
||||||
* characters, and always return an array.
|
* characters, and always return an array.
|
||||||
*
|
*
|
||||||
* @param string $input
|
* @param string $input
|
||||||
* @param string $delimiter
|
* @param string $delimiter
|
||||||
|
*
|
||||||
* @return string|string[]
|
* @return string|string[]
|
||||||
*/
|
*/
|
||||||
static public function unescapeValue($input, $delimiter = ';') {
|
public static function unescapeValue($input, $delimiter = ';')
|
||||||
|
{
|
||||||
$regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
|
$regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
|
||||||
if ($delimiter) {
|
if ($delimiter) {
|
||||||
$regex .= ' | ('.$delimiter.')';
|
$regex .= ' | ('.$delimiter.')';
|
||||||
|
|
@ -465,11 +537,10 @@ class MimeDir extends Parser {
|
||||||
|
|
||||||
$matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
$matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
$resultArray = array();
|
$resultArray = [];
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
|
|
||||||
switch ($match) {
|
switch ($match) {
|
||||||
case '\\\\':
|
case '\\\\':
|
||||||
$result .= '\\';
|
$result .= '\\';
|
||||||
|
|
@ -491,14 +562,12 @@ class MimeDir extends Parser {
|
||||||
default:
|
default:
|
||||||
$result .= $match;
|
$result .= $match;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$resultArray[] = $result;
|
$resultArray[] = $result;
|
||||||
return $delimiter ? $resultArray : $result;
|
|
||||||
|
|
||||||
|
return $delimiter ? $resultArray : $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -532,12 +601,13 @@ 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('#(\^(\^|n|\'))#',function($matches) {
|
preg_replace_callback(
|
||||||
|
'#(\^(\^|n|\'))#',
|
||||||
|
function ($matches) {
|
||||||
switch ($matches[2]) {
|
switch ($matches[2]) {
|
||||||
case 'n':
|
case 'n':
|
||||||
return "\n";
|
return "\n";
|
||||||
|
|
@ -549,8 +619,9 @@ class MimeDir extends Parser {
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
}
|
}
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}, $input);
|
},
|
||||||
|
$input
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -563,8 +634,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
|
||||||
|
|
@ -587,7 +658,7 @@ 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) && $this->lineBuffer) {
|
||||||
// Reading the line
|
// Reading the line
|
||||||
$this->readLine();
|
$this->readLine();
|
||||||
// Grabbing the raw form
|
// Grabbing the raw form
|
||||||
|
|
@ -596,7 +667,5 @@ class MimeDir extends Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ namespace Sabre\VObject\Parser;
|
||||||
*
|
*
|
||||||
* This class serves as a base-class for the different parsers.
|
* This class serves as a base-class for the different parsers.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
|
|
@ -29,7 +29,7 @@ abstract class Parser {
|
||||||
const OPTION_IGNORE_INVALID_LINES = 2;
|
const OPTION_IGNORE_INVALID_LINES = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bitmask of parser options
|
* Bitmask of parser options.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
|
|
@ -41,11 +41,10 @@ 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) {
|
public function __construct($input = null, $options = 0)
|
||||||
|
{
|
||||||
if (!is_null($input)) {
|
if (!is_null($input)) {
|
||||||
$this->setInput($input);
|
$this->setInput($input);
|
||||||
}
|
}
|
||||||
|
|
@ -61,17 +60,16 @@ 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|null $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
abstract public function parse($input = null, $options = null);
|
abstract public 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 public function setInput($input);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
377
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Parser/XML.php
Normal file
377
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Parser/XML.php
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Parser;
|
||||||
|
|
||||||
|
use Sabre\VObject\Component;
|
||||||
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
use Sabre\VObject\Component\VCard;
|
||||||
|
use Sabre\VObject\EofException;
|
||||||
|
use Sabre\VObject\ParseException;
|
||||||
|
use Sabre\Xml as SabreXml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML Parser.
|
||||||
|
*
|
||||||
|
* This parser parses both the xCal and xCard formats.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Ivan Enderlin
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class XML extends Parser
|
||||||
|
{
|
||||||
|
const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0';
|
||||||
|
const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input data.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pointer/reference to the input.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $pointer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document, root component.
|
||||||
|
*
|
||||||
|
* @var \Sabre\VObject\Document
|
||||||
|
*/
|
||||||
|
protected $root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the parser.
|
||||||
|
*
|
||||||
|
* Optionally, it's possible to parse the input stream here.
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @param int $options any parser options (OPTION constants)
|
||||||
|
*/
|
||||||
|
public function __construct($input = null, $options = 0)
|
||||||
|
{
|
||||||
|
if (0 === $options) {
|
||||||
|
$options = parent::OPTION_FORGIVING;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($input, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse xCal or xCard.
|
||||||
|
*
|
||||||
|
* @param resource|string $input
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*
|
||||||
|
* @return \Sabre\VObject\Document
|
||||||
|
*/
|
||||||
|
public function parse($input = null, $options = 0)
|
||||||
|
{
|
||||||
|
if (!is_null($input)) {
|
||||||
|
$this->setInput($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 !== $options) {
|
||||||
|
$this->options = $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->input)) {
|
||||||
|
throw new EofException('End of input stream, or no input supplied');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->input['name']) {
|
||||||
|
case '{'.self::XCAL_NAMESPACE.'}icalendar':
|
||||||
|
$this->root = new VCalendar([], false);
|
||||||
|
$this->pointer = &$this->input['value'][0];
|
||||||
|
$this->parseVCalendarComponents($this->root);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '{'.self::XCARD_NAMESPACE.'}vcards':
|
||||||
|
foreach ($this->input['value'] as &$vCard) {
|
||||||
|
$this->root = new VCard(['version' => '4.0'], false);
|
||||||
|
$this->pointer = &$vCard;
|
||||||
|
$this->parseVCardComponents($this->root);
|
||||||
|
|
||||||
|
// We just parse the first <vcard /> element.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ParseException('Unsupported XML standard');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a xCalendar component.
|
||||||
|
*/
|
||||||
|
protected function parseVCalendarComponents(Component $parentComponent)
|
||||||
|
{
|
||||||
|
foreach ($this->pointer['value'] ?: [] as $children) {
|
||||||
|
switch (static::getTagName($children['name'])) {
|
||||||
|
case 'properties':
|
||||||
|
$this->pointer = &$children['value'];
|
||||||
|
$this->parseProperties($parentComponent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'components':
|
||||||
|
$this->pointer = &$children;
|
||||||
|
$this->parseComponent($parentComponent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a xCard component.
|
||||||
|
*/
|
||||||
|
protected function parseVCardComponents(Component $parentComponent)
|
||||||
|
{
|
||||||
|
$this->pointer = &$this->pointer['value'];
|
||||||
|
$this->parseProperties($parentComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse xCalendar and xCard properties.
|
||||||
|
*
|
||||||
|
* @param string $propertyNamePrefix
|
||||||
|
*/
|
||||||
|
protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '')
|
||||||
|
{
|
||||||
|
foreach ($this->pointer ?: [] as $xmlProperty) {
|
||||||
|
list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
|
||||||
|
|
||||||
|
$propertyName = $tagName;
|
||||||
|
$propertyValue = [];
|
||||||
|
$propertyParameters = [];
|
||||||
|
$propertyType = 'text';
|
||||||
|
|
||||||
|
// A property which is not part of the standard.
|
||||||
|
if (self::XCAL_NAMESPACE !== $namespace
|
||||||
|
&& self::XCARD_NAMESPACE !== $namespace) {
|
||||||
|
$propertyName = 'xml';
|
||||||
|
$value = '<'.$tagName.' xmlns="'.$namespace.'"';
|
||||||
|
|
||||||
|
foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
|
||||||
|
$value .= ' '.$attributeName.'="'.str_replace('"', '\"', $attributeValue).'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$value .= '>'.$xmlProperty['value'].'</'.$tagName.'>';
|
||||||
|
|
||||||
|
$propertyValue = [$value];
|
||||||
|
|
||||||
|
$this->createProperty(
|
||||||
|
$parentComponent,
|
||||||
|
$propertyName,
|
||||||
|
$propertyParameters,
|
||||||
|
$propertyType,
|
||||||
|
$propertyValue
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// xCard group.
|
||||||
|
if ('group' === $propertyName) {
|
||||||
|
if (!isset($xmlProperty['attributes']['name'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pointer = &$xmlProperty['value'];
|
||||||
|
$this->parseProperties(
|
||||||
|
$parentComponent,
|
||||||
|
strtoupper($xmlProperty['attributes']['name']).'.'
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect parameters.
|
||||||
|
foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {
|
||||||
|
if (!is_array($xmlPropertyChild)
|
||||||
|
|| 'parameters' !== static::getTagName($xmlPropertyChild['name'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$xmlParameters = $xmlPropertyChild['value'];
|
||||||
|
|
||||||
|
foreach ($xmlParameters as $xmlParameter) {
|
||||||
|
$propertyParameterValues = [];
|
||||||
|
|
||||||
|
foreach ($xmlParameter['value'] as $xmlParameterValues) {
|
||||||
|
$propertyParameterValues[] = $xmlParameterValues['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$propertyParameters[static::getTagName($xmlParameter['name'])]
|
||||||
|
= implode(',', $propertyParameterValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_splice($xmlProperty['value'], $i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$propertyNameExtended = ($this->root instanceof VCalendar
|
||||||
|
? 'xcal'
|
||||||
|
: 'xcard').':'.$propertyName;
|
||||||
|
|
||||||
|
switch ($propertyNameExtended) {
|
||||||
|
case 'xcal:geo':
|
||||||
|
$propertyType = 'float';
|
||||||
|
$propertyValue['latitude'] = 0;
|
||||||
|
$propertyValue['longitude'] = 0;
|
||||||
|
|
||||||
|
foreach ($xmlProperty['value'] as $xmlRequestChild) {
|
||||||
|
$propertyValue[static::getTagName($xmlRequestChild['name'])]
|
||||||
|
= $xmlRequestChild['value'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xcal:request-status':
|
||||||
|
$propertyType = 'text';
|
||||||
|
|
||||||
|
foreach ($xmlProperty['value'] as $xmlRequestChild) {
|
||||||
|
$propertyValue[static::getTagName($xmlRequestChild['name'])]
|
||||||
|
= $xmlRequestChild['value'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xcal:freebusy':
|
||||||
|
$propertyType = 'freebusy';
|
||||||
|
// We don't break because we only want to set
|
||||||
|
// another property type.
|
||||||
|
|
||||||
|
// no break
|
||||||
|
case 'xcal:categories':
|
||||||
|
case 'xcal:resources':
|
||||||
|
case 'xcal:exdate':
|
||||||
|
foreach ($xmlProperty['value'] as $specialChild) {
|
||||||
|
$propertyValue[static::getTagName($specialChild['name'])]
|
||||||
|
= $specialChild['value'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xcal:rdate':
|
||||||
|
$propertyType = 'date-time';
|
||||||
|
|
||||||
|
foreach ($xmlProperty['value'] as $specialChild) {
|
||||||
|
$tagName = static::getTagName($specialChild['name']);
|
||||||
|
|
||||||
|
if ('period' === $tagName) {
|
||||||
|
$propertyParameters['value'] = 'PERIOD';
|
||||||
|
$propertyValue[] = implode('/', $specialChild['value']);
|
||||||
|
} else {
|
||||||
|
$propertyValue[] = $specialChild['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$propertyType = static::getTagName($xmlProperty['value'][0]['name']);
|
||||||
|
|
||||||
|
foreach ($xmlProperty['value'] as $value) {
|
||||||
|
$propertyValue[] = $value['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('date' === $propertyType) {
|
||||||
|
$propertyParameters['value'] = 'DATE';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->createProperty(
|
||||||
|
$parentComponent,
|
||||||
|
$propertyNamePrefix.$propertyName,
|
||||||
|
$propertyParameters,
|
||||||
|
$propertyType,
|
||||||
|
$propertyValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a component.
|
||||||
|
*/
|
||||||
|
protected function parseComponent(Component $parentComponent)
|
||||||
|
{
|
||||||
|
$components = $this->pointer['value'] ?: [];
|
||||||
|
|
||||||
|
foreach ($components as $component) {
|
||||||
|
$componentName = static::getTagName($component['name']);
|
||||||
|
$currentComponent = $this->root->createComponent(
|
||||||
|
$componentName,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->pointer = &$component;
|
||||||
|
$this->parseVCalendarComponents($currentComponent);
|
||||||
|
|
||||||
|
$parentComponent->add($currentComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a property.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param array $parameters
|
||||||
|
* @param string $type
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value)
|
||||||
|
{
|
||||||
|
$property = $this->root->createProperty(
|
||||||
|
$name,
|
||||||
|
null,
|
||||||
|
$parameters,
|
||||||
|
$type
|
||||||
|
);
|
||||||
|
$parentComponent->add($property);
|
||||||
|
$property->setXmlValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input data.
|
||||||
|
*
|
||||||
|
* @param resource|string $input
|
||||||
|
*/
|
||||||
|
public function setInput($input)
|
||||||
|
{
|
||||||
|
if (is_resource($input)) {
|
||||||
|
$input = stream_get_contents($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($input)) {
|
||||||
|
$reader = new SabreXml\Reader();
|
||||||
|
$reader->elementMap['{'.self::XCAL_NAMESPACE.'}period']
|
||||||
|
= XML\Element\KeyValue::class;
|
||||||
|
$reader->elementMap['{'.self::XCAL_NAMESPACE.'}recur']
|
||||||
|
= XML\Element\KeyValue::class;
|
||||||
|
$reader->xml($input);
|
||||||
|
$input = $reader->parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tag name from a Clark notation.
|
||||||
|
*
|
||||||
|
* @param string $clarkedTagName
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function getTagName($clarkedTagName)
|
||||||
|
{
|
||||||
|
list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
|
||||||
|
|
||||||
|
return $tagName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Parser\XML\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml as SabreXml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our own sabre/xml key-value element.
|
||||||
|
*
|
||||||
|
* It just removes the clark notation.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Ivan Enderlin
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class KeyValue extends SabreXml\Element\KeyValue
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The deserialize method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* Important note 2: You are responsible for advancing the reader to the
|
||||||
|
* next element. Not doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @param XML\Reader $reader
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(SabreXml\Reader $reader)
|
||||||
|
{
|
||||||
|
// If there's no children, we don't do anything.
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = [];
|
||||||
|
$reader->read();
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (SabreXml\Reader::ELEMENT === $reader->nodeType) {
|
||||||
|
$name = $reader->localName;
|
||||||
|
$values[$name] = $reader->parseCurrentElement()['value'];
|
||||||
|
} else {
|
||||||
|
$reader->read();
|
||||||
|
}
|
||||||
|
} while (SabreXml\Reader::END_ELEMENT !== $reader->nodeType);
|
||||||
|
|
||||||
|
$reader->read();
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property
|
* Property.
|
||||||
*
|
*
|
||||||
* A property is always in a KEY:VALUE structure, and may optionally contain
|
* A property is always in a KEY:VALUE structure, and may optionally contain
|
||||||
* parameters.
|
* parameters.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
abstract class Property extends Node {
|
abstract class Property extends Node
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Property name.
|
* Property name.
|
||||||
*
|
*
|
||||||
|
|
@ -33,14 +35,14 @@ abstract class Property extends Node {
|
||||||
public $group;
|
public $group;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of parameters
|
* List of parameters.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $parameters = array();
|
public $parameters = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current value
|
* Current value.
|
||||||
*
|
*
|
||||||
* @var mixed
|
* @var mixed
|
||||||
*/
|
*/
|
||||||
|
|
@ -50,7 +52,7 @@ abstract class Property extends Node {
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = ';';
|
public $delimiter = ';';
|
||||||
|
|
||||||
|
|
@ -64,23 +66,21 @@ abstract class Property extends Node {
|
||||||
* @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 = array(), $group = null) {
|
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null)
|
||||||
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->group = $group;
|
$this->group = $group;
|
||||||
|
|
||||||
$this->root = $root;
|
$this->root = $root;
|
||||||
|
|
||||||
if (!is_null($value)) {
|
|
||||||
$this->setValue($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($parameters as $k => $v) {
|
foreach ($parameters as $k => $v) {
|
||||||
$this->add($k, $v);
|
$this->add($k, $v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_null($value)) {
|
||||||
|
$this->setValue($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -89,12 +89,10 @@ 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) {
|
public function setValue($value)
|
||||||
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,32 +106,27 @@ abstract class Property extends Node {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValue() {
|
public function getValue()
|
||||||
|
{
|
||||||
if (is_array($this->value)) {
|
if (is_array($this->value)) {
|
||||||
if (count($this->value)==0) {
|
if (0 == count($this->value)) {
|
||||||
return null;
|
return;
|
||||||
} elseif (count($this->value)===1) {
|
} elseif (1 === count($this->value)) {
|
||||||
return $this->value[0];
|
return $this->value[0];
|
||||||
} else {
|
} else {
|
||||||
return $this->getRawMimeDirValue($this->value);
|
return $this->getRawMimeDirValue();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a multi-valued property.
|
* Sets a multi-valued property.
|
||||||
*
|
|
||||||
* @param array $parts
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setParts(array $parts) {
|
public function setParts(array $parts)
|
||||||
|
{
|
||||||
$this->value = $parts;
|
$this->value = $parts;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,40 +137,38 @@ abstract class Property extends Node {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getParts() {
|
public function getParts()
|
||||||
|
{
|
||||||
if (is_null($this->value)) {
|
if (is_null($this->value)) {
|
||||||
return array();
|
return [];
|
||||||
} elseif (is_array($this->value)) {
|
} elseif (is_array($this->value)) {
|
||||||
return $this->value;
|
return $this->value;
|
||||||
} else {
|
} else {
|
||||||
return array($this->value);
|
return [$this->value];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new parameter, and returns the new item.
|
* Adds a new parameter.
|
||||||
*
|
*
|
||||||
* 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 it's name.
|
* If nameless parameter is added, we try to guess its name.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string|null|array $value
|
* @param string|array|null $value
|
||||||
* @return Node
|
|
||||||
*/
|
*/
|
||||||
public function add($name, $value = null) {
|
public function add($name, $value = null)
|
||||||
|
{
|
||||||
$noName = false;
|
$noName = false;
|
||||||
if ($name === null) {
|
if (null === $name) {
|
||||||
$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;
|
||||||
|
|
@ -185,14 +176,13 @@ abstract class Property extends Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterable list of children
|
* Returns an iterable list of children.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function parameters() {
|
public function parameters()
|
||||||
|
{
|
||||||
return $this->parameters;
|
return $this->parameters;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,7 +202,6 @@ 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 public function setRawMimeDirValue($val);
|
||||||
|
|
||||||
|
|
@ -228,78 +217,72 @@ abstract class Property extends Node {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function serialize() {
|
public function serialize()
|
||||||
|
{
|
||||||
$str = $this->name;
|
$str = $this->name;
|
||||||
if ($this->group) $str = $this->group . '.' . $this->name;
|
if ($this->group) {
|
||||||
|
$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();
|
||||||
|
|
||||||
$out = '';
|
$str = \preg_replace(
|
||||||
while(strlen($str)>0) {
|
'/(
|
||||||
if (strlen($str)>75) {
|
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
|
||||||
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
|
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
|
||||||
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
|
(?![\x80-\xbf]) # prevent splitting multibyte characters
|
||||||
} else {
|
)/x',
|
||||||
$out.=$str . "\r\n";
|
"$1\r\n ",
|
||||||
$str='';
|
$str
|
||||||
break;
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $out;
|
|
||||||
|
|
||||||
|
// remove single space after last CRLF
|
||||||
|
return \substr($str, 0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value, in the format it should be encoded for json.
|
* Returns the value, in the format it should be encoded for JSON.
|
||||||
*
|
*
|
||||||
* This method must always return an array.
|
* This method must always return an array.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
return $this->getParts();
|
return $this->getParts();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
* Sets the JSON value, as it would appear in a jCard or jCal object.
|
||||||
*
|
*
|
||||||
* The value must always be an array.
|
* The value must always be an array.
|
||||||
*
|
|
||||||
* @param array $value
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setJsonValue(array $value) {
|
public function setJsonValue(array $value)
|
||||||
|
{
|
||||||
if (count($value)===1) {
|
if (1 === count($value)) {
|
||||||
$this->setValue(reset($value));
|
$this->setValue(reset($value));
|
||||||
} else {
|
} else {
|
||||||
$this->setValue($value);
|
$this->setValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns an array, with the representation as it should be
|
* This method returns an array, with the representation as it should be
|
||||||
* encoded in json. This is used to create jCard or jCal documents.
|
* encoded in JSON. This is used to create jCard or jCal documents.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize() {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function jsonSerialize()
|
||||||
$parameters = array();
|
{
|
||||||
|
$parameters = [];
|
||||||
|
|
||||||
foreach ($this->parameters as $parameter) {
|
foreach ($this->parameters as $parameter) {
|
||||||
if ($parameter->name === 'VALUE') {
|
if ('VALUE' === $parameter->name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
|
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
|
||||||
|
|
@ -311,15 +294,76 @@ abstract class Property extends Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge(
|
return array_merge(
|
||||||
array(
|
[
|
||||||
strtolower($this->name),
|
strtolower($this->name),
|
||||||
(object) $parameters,
|
(object) $parameters,
|
||||||
strtolower($this->getValueType()),
|
strtolower($this->getValueType()),
|
||||||
),
|
],
|
||||||
$this->getJsonValue()
|
$this->getJsonValue()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate data from a XML subtree, as it would appear in a xCard or xCal
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public function setXmlValue(array $value)
|
||||||
|
{
|
||||||
|
$this->setJsonValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes the data into XML. This is used to create xCard or
|
||||||
|
* xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$parameters = [];
|
||||||
|
|
||||||
|
foreach ($this->parameters as $parameter) {
|
||||||
|
if ('VALUE' === $parameter->name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters[] = $parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->startElement(strtolower($this->name));
|
||||||
|
|
||||||
|
if (!empty($parameters)) {
|
||||||
|
$writer->startElement('parameters');
|
||||||
|
|
||||||
|
foreach ($parameters as $parameter) {
|
||||||
|
$writer->startElement(strtolower($parameter->name));
|
||||||
|
$writer->write($parameter);
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->xmlSerializeValue($writer);
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$valueType = strtolower($this->getValueType());
|
||||||
|
|
||||||
|
foreach ($this->getJsonValue() as $values) {
|
||||||
|
foreach ((array) $values as $value) {
|
||||||
|
$writer->writeElement($valueType, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this object is being cast to a string.
|
* Called when this object is being cast to a string.
|
||||||
|
|
@ -330,31 +374,36 @@ abstract class Property extends Node {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function __toString() {
|
public function __toString()
|
||||||
|
{
|
||||||
return (string) $this->getValue();
|
return (string) $this->getValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ArrayAccess interface {{{ */
|
/* ArrayAccess interface {{{ */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an array element exists
|
* Checks if an array element exists.
|
||||||
*
|
*
|
||||||
* @param mixed $name
|
* @param mixed $name
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function offsetExists($name) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetExists($name)
|
||||||
if (is_int($name)) return parent::offsetExists($name);
|
{
|
||||||
|
if (is_int($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) return true;
|
if ($parameter->name == $name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -363,30 +412,33 @@ abstract class Property extends Node {
|
||||||
* If the parameter does not exist, null is returned.
|
* If the parameter does not exist, null is returned.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
|
*
|
||||||
* @return Node
|
* @return Node
|
||||||
*/
|
*/
|
||||||
public function offsetGet($name) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetGet($name)
|
||||||
if (is_int($name)) return parent::offsetGet($name);
|
{
|
||||||
|
if (is_int($name)) {
|
||||||
|
return parent::offsetGet($name);
|
||||||
|
}
|
||||||
$name = strtoupper($name);
|
$name = strtoupper($name);
|
||||||
|
|
||||||
if (!isset($this->parameters[$name])) {
|
if (!isset($this->parameters[$name])) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetSet($name, $value)
|
||||||
|
{
|
||||||
if (is_int($name)) {
|
if (is_int($name)) {
|
||||||
parent::offsetSet($name, $value);
|
parent::offsetSet($name, $value);
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
|
|
@ -398,17 +450,16 @@ 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) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetUnset($name)
|
||||||
|
{
|
||||||
if (is_int($name)) {
|
if (is_int($name)) {
|
||||||
parent::offsetUnset($name);
|
parent::offsetUnset($name);
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
|
|
@ -419,23 +470,20 @@ 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() {
|
public 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -453,31 +501,45 @@ abstract class Property extends Node {
|
||||||
* * node - (reference to the offending node)
|
* * node - (reference to the offending node)
|
||||||
*
|
*
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
$warnings = array();
|
$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())) {
|
||||||
$warnings[] = array(
|
$oldValue = $this->getRawMimeDirValue();
|
||||||
'level' => 1,
|
$level = 3;
|
||||||
'message' => 'Property is not valid UTF-8!',
|
|
||||||
'node' => $this,
|
|
||||||
);
|
|
||||||
if ($options & self::REPAIR) {
|
if ($options & self::REPAIR) {
|
||||||
$this->setRawMimeDirValue(StringUtil::convertToUTF8($this->getRawMimeDirValue()));
|
$newValue = StringUtil::convertToUTF8($oldValue);
|
||||||
|
if (true || StringUtil::isUTF8($newValue)) {
|
||||||
|
$this->setRawMimeDirValue($newValue);
|
||||||
|
$level = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) {
|
||||||
|
$message = 'Property contained a control character (0x'.bin2hex($matches[1]).')';
|
||||||
|
} else {
|
||||||
|
$message = 'Property is not valid UTF-8! '.$oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $level,
|
||||||
|
'message' => $message,
|
||||||
|
'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[] = array(
|
$warnings[] = [
|
||||||
'level' => 1,
|
'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.
|
||||||
$this->name = strtoupper(
|
$this->name = strtoupper(
|
||||||
|
|
@ -485,9 +547,52 @@ 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 (Document::VCARD40 === $this->root->getDocumentType()) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'ENCODING parameter is not valid in vCard 4.',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$encoding = (string) $encoding;
|
||||||
|
|
||||||
|
$allowedEncoding = [];
|
||||||
|
|
||||||
|
switch ($this->root->getDocumentType()) {
|
||||||
|
case Document::ICALENDAR20:
|
||||||
|
$allowedEncoding = ['8BIT', 'BASE64'];
|
||||||
|
break;
|
||||||
|
case Document::VCARD21:
|
||||||
|
$allowedEncoding = ['QUOTED-PRINTABLE', 'BASE64', '8BIT'];
|
||||||
|
break;
|
||||||
|
case Document::VCARD30:
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validating inner parameters
|
// Validating inner parameters
|
||||||
|
|
@ -496,7 +601,20 @@ abstract class Property extends Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $warnings;
|
return $warnings;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 up.
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
parent::destroy();
|
||||||
|
foreach ($this->parameters as $param) {
|
||||||
|
$param->destroy();
|
||||||
|
}
|
||||||
|
$this->parameters = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
LogicException,
|
|
||||||
Sabre\VObject\Property;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BINARY property
|
* BINARY property.
|
||||||
*
|
*
|
||||||
* This object represents BINARY values.
|
* This object represents BINARY values.
|
||||||
*
|
*
|
||||||
|
|
@ -16,19 +14,19 @@ use
|
||||||
*
|
*
|
||||||
* This property will transparently encode and decode to base64.
|
* This property will transparently encode and decode to base64.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the current value.
|
* Updates the current value.
|
||||||
|
|
@ -36,24 +34,18 @@ 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) {
|
public 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,12 +55,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->value = base64_decode($val);
|
$this->value = base64_decode($val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,10 +66,9 @@ class Binary extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return base64_encode($this->value);
|
return base64_encode($this->value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,10 +79,9 @@ class Binary extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return 'BINARY';
|
return 'BINARY';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,25 +91,19 @@ class Binary extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
return array(base64_encode($this->getValue()));
|
return [base64_encode($this->getValue())];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||||
*
|
*
|
||||||
* The value must always be an array.
|
* The value must always be an array.
|
||||||
*
|
|
||||||
* @param array $value
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setJsonValue(array $value) {
|
public function setJsonValue(array $value)
|
||||||
|
{
|
||||||
$value = array_map('base64_decode', $value);
|
$value = array_map('base64_decode', $value);
|
||||||
parent::setJsonValue($value);
|
parent::setJsonValue($value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,22 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Property;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean property
|
* Boolean property.
|
||||||
*
|
*
|
||||||
* This object represents BOOLEAN values. These are always the case-insenstive
|
* This object represents BOOLEAN values. These are always the case-insensitive
|
||||||
* string TRUE or FALSE.
|
* string TRUE or FALSE.
|
||||||
*
|
*
|
||||||
* Automatic conversion to PHP's true and false are done.
|
* Automatic conversion to PHP's true and false are done.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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,13 +25,11 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$val = strtoupper($val)==='TRUE'?true:false;
|
$val = 'TRUE' === strtoupper($val) ? true : false;
|
||||||
$this->setValue($val);
|
$this->setValue($val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,10 +37,9 @@ class Boolean extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return $this->value ? 'TRUE' : 'FALSE';
|
return $this->value ? 'TRUE' : 'FALSE';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,10 +50,23 @@ class Boolean extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return 'BOOLEAN';
|
return 'BOOLEAN';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate data from a XML subtree, as it would appear in a xCard or xCal
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public function setXmlValue(array $value)
|
||||||
|
{
|
||||||
|
$value = array_map(
|
||||||
|
function ($value) {
|
||||||
|
return 'true' === $value;
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
parent::setXmlValue($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlatText property
|
* FlatText property.
|
||||||
*
|
*
|
||||||
* This object represents certain TEXT values.
|
* This object represents certain TEXT values.
|
||||||
*
|
*
|
||||||
|
|
@ -18,14 +18,14 @@ namespace Sabre\VObject\Property;
|
||||||
* vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
|
* vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
|
||||||
* delimiter, depending on the property.
|
* delimiter, depending on the property.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class FlatText extends Text {
|
class FlatText extends Text
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Field separator
|
* Field separator.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -37,13 +37,10 @@ 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) {
|
public function setQuotedPrintableValue($val)
|
||||||
|
{
|
||||||
$val = quoted_printable_decode($val);
|
$val = quoted_printable_decode($val);
|
||||||
$this->setValue($val);
|
$this->setValue($val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,26 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Property;
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Float property
|
* Float property.
|
||||||
*
|
*
|
||||||
* This object represents FLOAT values. These can be 1 or more floating-point
|
* This object represents FLOAT values. These can be 1 or more floating-point
|
||||||
* numbers.
|
* numbers.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = ';';
|
public $delimiter = ';';
|
||||||
|
|
||||||
|
|
@ -32,16 +32,14 @@ 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) {
|
public 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,13 +47,12 @@ class FloatValue extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return implode(
|
return implode(
|
||||||
$this->delimiter,
|
$this->delimiter,
|
||||||
$this->getParts()
|
$this->getParts()
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,36 +63,62 @@ class FloatValue extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "FLOAT";
|
return 'FLOAT';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value, in the format it should be encoded for json.
|
* Returns the value, in the format it should be encoded for JSON.
|
||||||
*
|
*
|
||||||
* This method must always return an array.
|
* This method must always return an array.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$val = array_map(function($item) {
|
$val = array_map('floatval', $this->getParts());
|
||||||
|
|
||||||
return (float)$item;
|
|
||||||
|
|
||||||
}, $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 ($this->name==='GEO') {
|
if ('GEO' === $this->name) {
|
||||||
return array($val);
|
return [$val];
|
||||||
} else {
|
}
|
||||||
|
|
||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate data from a XML subtree, as it would appear in a xCard or xCal
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public function setXmlValue(array $value)
|
||||||
|
{
|
||||||
|
$value = array_map('floatval', $value);
|
||||||
|
parent::setXmlValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
// Special-casing the GEO property.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2
|
||||||
|
if ('GEO' === $this->name) {
|
||||||
|
$value = array_map('floatval', $this->getParts());
|
||||||
|
|
||||||
|
$writer->writeElement('latitude', $value[0]);
|
||||||
|
$writer->writeElement('longitude', $value[1]);
|
||||||
|
} else {
|
||||||
|
parent::xmlSerializeValue($writer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,26 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property\Text;
|
||||||
Sabre\VObject\Property\Text;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CalAddress property
|
* CalAddress property.
|
||||||
*
|
*
|
||||||
* This object encodes CAL-ADDRESS values, as defined in rfc5545
|
* This object encodes CAL-ADDRESS values, as defined in rfc5545
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -32,10 +31,29 @@ class CalAddress extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return 'CAL-ADDRESS';
|
return 'CAL-ADDRESS';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns a normalized form of the value.
|
||||||
|
*
|
||||||
|
* This is primarily used right now to turn mixed-cased schemes in user
|
||||||
|
* uris to lower-case.
|
||||||
|
*
|
||||||
|
* Evolution in particular tends to encode mailto: as MAILTO:.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNormalizedValue()
|
||||||
|
{
|
||||||
|
$input = $this->getValue();
|
||||||
|
if (!strpos($input, ':')) {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
list($schema, $everythingElse) = explode(':', $input, 2);
|
||||||
|
|
||||||
|
return strtolower($schema).':'.$everythingElse;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,17 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
|
||||||
Sabre\VObject\Property,
|
|
||||||
Sabre\VObject\Parser\MimeDir,
|
|
||||||
Sabre\VObject\DateTimeParser,
|
|
||||||
Sabre\VObject\TimeZoneUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DateTime property
|
* DateTime property.
|
||||||
*
|
*
|
||||||
* This object represents DATE values, as defined here:
|
* This object represents DATE values, as defined here:
|
||||||
*
|
*
|
||||||
* http://tools.ietf.org/html/rfc5545#section-3.3.5
|
* http://tools.ietf.org/html/rfc5545#section-3.3.5
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Date extends DateTime {
|
class Date extends DateTime
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
use DateTimeInterface;
|
||||||
Sabre\VObject\Property,
|
use DateTimeZone;
|
||||||
Sabre\VObject\Parser\MimeDir,
|
use Sabre\VObject\DateTimeParser;
|
||||||
Sabre\VObject\DateTimeParser,
|
use Sabre\VObject\InvalidDataException;
|
||||||
Sabre\VObject\TimeZoneUtil;
|
use Sabre\VObject\Property;
|
||||||
|
use Sabre\VObject\TimeZoneUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DateTime property
|
* DateTime property.
|
||||||
*
|
*
|
||||||
* This object represents DATE-TIME values, as defined here:
|
* This object represents DATE-TIME values, as defined here:
|
||||||
*
|
*
|
||||||
|
|
@ -19,12 +20,12 @@ use
|
||||||
* cases represent a DATE value. This is because it's a common usecase to be
|
* cases represent a DATE value. This is because it's a common usecase to be
|
||||||
* able to change a DATE-TIME into a DATE.
|
* able to change a DATE-TIME into a DATE.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -37,18 +38,14 @@ class DateTime extends Property {
|
||||||
* Sets a multi-valued property.
|
* Sets a multi-valued property.
|
||||||
*
|
*
|
||||||
* You may also specify DateTime objects here.
|
* You may also specify DateTime objects here.
|
||||||
*
|
|
||||||
* @param array $parts
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setParts(array $parts) {
|
public function setParts(array $parts)
|
||||||
|
{
|
||||||
if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
|
if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
|
||||||
$this->setDateTimes($parts);
|
$this->setDateTimes($parts);
|
||||||
} else {
|
} else {
|
||||||
parent::setParts($parts);
|
parent::setParts($parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,19 +55,17 @@ 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|\DateTime $value
|
* @param string|array|DateTimeInterface $value
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setValue($value) {
|
public function setValue($value)
|
||||||
|
{
|
||||||
if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) {
|
if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) {
|
||||||
$this->setDateTimes($value);
|
$this->setDateTimes($value);
|
||||||
} elseif ($value instanceof \DateTime) {
|
} elseif ($value instanceof DateTimeInterface) {
|
||||||
$this->setDateTimes(array($value));
|
$this->setDateTimes([$value]);
|
||||||
} else {
|
} else {
|
||||||
parent::setValue($value);
|
parent::setValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -80,12 +75,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue(explode($this->delimiter, $val));
|
$this->setValue(explode($this->delimiter, $val));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,10 +86,9 @@ class DateTime extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return implode($this->delimiter, $this->getParts());
|
return implode($this->delimiter, $this->getParts());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -104,10 +96,24 @@ class DateTime extends Property {
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function hasTime() {
|
public function hasTime()
|
||||||
|
{
|
||||||
return strtoupper((string)$this['VALUE']) !== 'DATE';
|
return 'DATE' !== strtoupper((string) $this['VALUE']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this is a floating DATE or DATE-TIME.
|
||||||
|
*
|
||||||
|
* Note that DATE is always floating.
|
||||||
|
*/
|
||||||
|
public function isFloating()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
!$this->hasTime() ||
|
||||||
|
(
|
||||||
|
!isset($this['TZID']) &&
|
||||||
|
false === strpos($this->getValue(), 'Z')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -117,85 +123,93 @@ class DateTime extends Property {
|
||||||
* first will be returned. To get an array with multiple values, call
|
* first will be returned. To get an array with multiple values, call
|
||||||
* getDateTimes.
|
* getDateTimes.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* If no timezone information is known, because it's either an all-day
|
||||||
|
* property or floating time, we will use the DateTimeZone argument to
|
||||||
|
* figure out the exact date.
|
||||||
|
*
|
||||||
|
* @param DateTimeZone $timeZone
|
||||||
|
*
|
||||||
|
* @return \DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getDateTime() {
|
public function getDateTime(DateTimeZone $timeZone = null)
|
||||||
|
{
|
||||||
$dt = $this->getDateTimes();
|
$dt = $this->getDateTimes($timeZone);
|
||||||
if (!$dt) return null;
|
if (!$dt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return $dt[0];
|
return $dt[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns multiple date-time values.
|
* Returns multiple date-time values.
|
||||||
*
|
*
|
||||||
|
* If no timezone information is known, because it's either an all-day
|
||||||
|
* property or floating time, we will use the DateTimeZone argument to
|
||||||
|
* figure out the exact date.
|
||||||
|
*
|
||||||
|
* @param DateTimeZone $timeZone
|
||||||
|
*
|
||||||
|
* @return \DateTimeImmutable[]
|
||||||
* @return \DateTime[]
|
* @return \DateTime[]
|
||||||
*/
|
*/
|
||||||
public function getDateTimes() {
|
public function getDateTimes(DateTimeZone $timeZone = null)
|
||||||
|
{
|
||||||
|
// Does the property have a TZID?
|
||||||
|
$tzid = $this['TZID'];
|
||||||
|
|
||||||
// Finding the timezone.
|
if ($tzid) {
|
||||||
$tz = $this['TZID'];
|
$timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root);
|
||||||
|
|
||||||
if ($tz) {
|
|
||||||
$tz = TimeZoneUtil::getTimeZone((string)$tz, $this->root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$dts = array();
|
$dts = [];
|
||||||
foreach ($this->getParts() as $part) {
|
foreach ($this->getParts() as $part) {
|
||||||
$dts[] = DateTimeParser::parse($part, $tz);
|
$dts[] = DateTimeParser::parse($part, $timeZone);
|
||||||
}
|
}
|
||||||
return $dts;
|
|
||||||
|
|
||||||
|
return $dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the property as a DateTime object.
|
* Sets the property as a DateTime object.
|
||||||
*
|
*
|
||||||
* @param \DateTime $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(\DateTime $dt, $isFloating = false) {
|
public function setDateTime(DateTimeInterface $dt, $isFloating = false)
|
||||||
|
{
|
||||||
$this->setDateTimes(array($dt), $isFloating);
|
$this->setDateTimes([$dt], $isFloating);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the property as multiple date-time objects.
|
* Sets the property as multiple date-time objects.
|
||||||
*
|
*
|
||||||
* The first value will be used as a reference for the timezones, and all
|
* The first value will be used as a reference for the timezones, and all
|
||||||
* the otehr values will be adjusted for that timezone
|
* the other values will be adjusted for that timezone
|
||||||
*
|
*
|
||||||
* @param \DateTime[] $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) {
|
public function setDateTimes(array $dt, $isFloating = false)
|
||||||
|
{
|
||||||
$values = array();
|
$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;
|
||||||
}
|
}
|
||||||
if (is_null($tz)) {
|
if (is_null($tz)) {
|
||||||
$tz = $d->getTimeZone();
|
$tz = $d->getTimeZone();
|
||||||
$isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
|
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']);
|
||||||
if (!$isUtc) {
|
if (!$isUtc) {
|
||||||
$this->offsetSet('TZID', $tz->getName());
|
$this->offsetSet('TZID', $tz->getName());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$d->setTimeZone($tz);
|
$d = $d->setTimeZone($tz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isUtc) {
|
if ($isUtc) {
|
||||||
|
|
@ -203,25 +217,18 @@ 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -232,77 +239,126 @@ class DateTime extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return $this->hasTime() ? 'DATE-TIME' : 'DATE';
|
return $this->hasTime() ? 'DATE-TIME' : 'DATE';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value, in the format it should be encoded for json.
|
* Returns the value, in the format it should be encoded for JSON.
|
||||||
*
|
*
|
||||||
* This method must always return an array.
|
* This method must always return an array.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$dts = $this->getDateTimes();
|
$dts = $this->getDateTimes();
|
||||||
$hasTime = $this->hasTime();
|
$hasTime = $this->hasTime();
|
||||||
|
$isFloating = $this->isFloating();
|
||||||
|
|
||||||
$tz = $dts[0]->getTimeZone();
|
$tz = $dts[0]->getTimeZone();
|
||||||
$isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
|
$isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
||||||
|
|
||||||
return array_map(function($dt) use ($hasTime, $isUtc) {
|
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||||
*
|
*
|
||||||
* The value must always be an array.
|
* The value must always be an array.
|
||||||
*
|
|
||||||
* @param array $value
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setJsonValue(array $value) {
|
public 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(array_map(function($item) {
|
$this->setValue(
|
||||||
|
array_map(
|
||||||
return strtr($item, array(':'=>'', '-'=>''));
|
function ($item) {
|
||||||
|
return strtr($item, [':' => '', '-' => '']);
|
||||||
}, $value));
|
},
|
||||||
|
$value
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need to intercept offsetSet, because it may be used to alter the
|
* We need to intercept offsetSet, because it may be used to alter the
|
||||||
* 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) {
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetSet($name, $value)
|
||||||
|
{
|
||||||
parent::offsetSet($name, $value);
|
parent::offsetSet($name, $value);
|
||||||
if (strtoupper($name)!=='VALUE') {
|
if ('VALUE' !== strtoupper($name)) {
|
||||||
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$messages = parent::validate($options);
|
||||||
|
$valueType = $this->getValueType();
|
||||||
|
$values = $this->getParts();
|
||||||
|
foreach ($values as $value) {
|
||||||
|
try {
|
||||||
|
switch ($valueType) {
|
||||||
|
case 'DATE':
|
||||||
|
DateTimeParser::parseDate($value);
|
||||||
|
break;
|
||||||
|
case 'DATE-TIME':
|
||||||
|
DateTimeParser::parseDateTime($value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InvalidDataException $e) {
|
||||||
|
$messages[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType,
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,27 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\DateTimeParser;
|
||||||
Sabre\VObject\Property,
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Parser\MimeDir,
|
|
||||||
Sabre\VObject\DateTimeParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration property
|
* Duration property.
|
||||||
*
|
*
|
||||||
* This object represents DURATION values, as defined here:
|
* This object represents DURATION values, as defined here:
|
||||||
*
|
*
|
||||||
* http://tools.ietf.org/html/rfc5545#section-3.3.6
|
* http://tools.ietf.org/html/rfc5545#section-3.3.6
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = ',';
|
public $delimiter = ',';
|
||||||
|
|
||||||
|
|
@ -35,12 +33,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue(explode($this->delimiter, $val));
|
$this->setValue(explode($this->delimiter, $val));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,10 +44,9 @@ class Duration extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return implode($this->delimiter, $this->getParts());
|
return implode($this->delimiter, $this->getParts());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,10 +57,9 @@ class Duration extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return 'DURATION';
|
return 'DURATION';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,12 +69,11 @@ class Duration extends Property {
|
||||||
*
|
*
|
||||||
* @return \DateInterval
|
* @return \DateInterval
|
||||||
*/
|
*/
|
||||||
public function getDateInterval() {
|
public function getDateInterval()
|
||||||
|
{
|
||||||
$parts = $this->getParts();
|
$parts = $this->getParts();
|
||||||
$value = $parts[0];
|
$value = $parts[0];
|
||||||
|
|
||||||
return DateTimeParser::parseDuration($value);
|
return DateTimeParser::parseDuration($value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,28 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\DateTimeParser;
|
||||||
Sabre\VObject\Property,
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Parser\MimeDir,
|
use Sabre\Xml;
|
||||||
Sabre\VObject\DateTimeParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Period property
|
* Period property.
|
||||||
*
|
*
|
||||||
* This object represents PERIOD values, as defined here:
|
* This object represents PERIOD values, as defined here:
|
||||||
*
|
*
|
||||||
* http://tools.ietf.org/html/rfc5545#section-3.8.2.6
|
* http://tools.ietf.org/html/rfc5545#section-3.8.2.6
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = ',';
|
public $delimiter = ',';
|
||||||
|
|
||||||
|
|
@ -35,12 +34,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue(explode($this->delimiter, $val));
|
$this->setValue(explode($this->delimiter, $val));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,10 +45,9 @@ class Period extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return implode($this->delimiter, $this->getParts());
|
return implode($this->delimiter, $this->getParts());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,29 +58,25 @@ class Period extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "PERIOD";
|
return 'PERIOD';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||||
*
|
*
|
||||||
* The value must always be an array.
|
* The value must always be an array.
|
||||||
*
|
|
||||||
* @param array $value
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setJsonValue(array $value) {
|
public function setJsonValue(array $value)
|
||||||
|
{
|
||||||
$value = array_map(function($item) {
|
$value = array_map(
|
||||||
|
function ($item) {
|
||||||
return strtr(implode('/', $item), array(':' => '', '-' => ''));
|
return strtr(implode('/', $item), [':' => '', '-' => '']);
|
||||||
|
},
|
||||||
}, $value);
|
$value
|
||||||
|
);
|
||||||
parent::setJsonValue($value);
|
parent::setJsonValue($value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -94,33 +86,50 @@ class Period extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$return = array();
|
$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 ($end[0]==='P') {
|
if ('P' === $end[0]) {
|
||||||
$return[] = array(
|
$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);
|
||||||
$return[] = array(
|
$return[] = [
|
||||||
$start->format('Y-m-d\\TH:i:s'),
|
$start->format('Y-m-d\\TH:i:s'),
|
||||||
$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
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$writer->startElement(strtolower($this->getValueType()));
|
||||||
|
$value = $this->getJsonValue();
|
||||||
|
$writer->writeElement('start', $value[0][0]);
|
||||||
|
|
||||||
|
if ('P' === $value[0][1][0]) {
|
||||||
|
$writer->writeElement('duration', $value[0][1]);
|
||||||
|
} else {
|
||||||
|
$writer->writeElement('end', $value[0][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\ICalendar;
|
namespace Sabre\VObject\Property\ICalendar;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Property,
|
use Sabre\Xml;
|
||||||
Sabre\VObject\Parser\MimeDir;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recur property
|
* Recur property.
|
||||||
*
|
*
|
||||||
* This object represents RECUR properties.
|
* This object represents RECUR properties.
|
||||||
* These values are just used for RRULE and the now deprecated EXRULE.
|
* These values are just used for RRULE and the now deprecated EXRULE.
|
||||||
|
|
@ -19,39 +18,40 @@ use
|
||||||
* This property exposes this as a key=>value array that is accessible using
|
* This property exposes this as a key=>value array that is accessible using
|
||||||
* getParts, and may be set using setParts.
|
* getParts, and may be set using setParts.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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) {
|
public 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 = array();
|
$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 (strpos($v,',')!==false) {
|
if (false !== strpos($v, ',')) {
|
||||||
$v = explode(',', $v);
|
$v = explode(',', $v);
|
||||||
}
|
}
|
||||||
} else {
|
if (0 === strcmp($k, 'until')) {
|
||||||
|
$v = strtr($v, [':' => '', '-' => '']);
|
||||||
|
}
|
||||||
|
} elseif (is_array($v)) {
|
||||||
$v = array_map('strtoupper', $v);
|
$v = array_map('strtoupper', $v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,28 +59,10 @@ class Recur extends Property {
|
||||||
}
|
}
|
||||||
$this->value = $newVal;
|
$this->value = $newVal;
|
||||||
} elseif (is_string($value)) {
|
} elseif (is_string($value)) {
|
||||||
$value = strtoupper($value);
|
$this->value = self::stringToArray($value);
|
||||||
$newValue = array();
|
|
||||||
foreach(explode(';', $value) as $part) {
|
|
||||||
|
|
||||||
// Skipping empty parts.
|
|
||||||
if (empty($part)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
list($partName, $partValue) = explode('=', $part);
|
|
||||||
|
|
||||||
// The value itself had multiple values..
|
|
||||||
if (strpos($partValue,',')!==false) {
|
|
||||||
$partValue=explode(',', $partValue);
|
|
||||||
}
|
|
||||||
$newValue[$partName] = $partValue;
|
|
||||||
|
|
||||||
}
|
|
||||||
$this->value = $newValue;
|
|
||||||
} 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -94,26 +76,22 @@ class Recur extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValue() {
|
public function getValue()
|
||||||
|
{
|
||||||
$out = array();
|
$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
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function setParts(array $parts) {
|
public function setParts(array $parts)
|
||||||
|
{
|
||||||
$this->setValue($parts);
|
$this->setValue($parts);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,10 +102,9 @@ class Recur extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getParts() {
|
public function getParts()
|
||||||
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -137,12 +114,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue($val);
|
$this->setValue($val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -150,10 +125,9 @@ class Recur extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return $this->getValue();
|
return $this->getValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,10 +138,9 @@ class Recur extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "RECUR";
|
return 'RECUR';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -177,13 +150,187 @@ class Recur extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$values = array();
|
$values = [];
|
||||||
foreach ($this->getParts() as $k => $v) {
|
foreach ($this->getParts() as $k => $v) {
|
||||||
|
if (0 === strcmp($k, 'UNTIL')) {
|
||||||
|
$date = new DateTime($this->root, null, $v);
|
||||||
|
$values[strtolower($k)] = $date->getJsonValue()[0];
|
||||||
|
} elseif (0 === strcmp($k, 'COUNT')) {
|
||||||
|
$values[strtolower($k)] = intval($v);
|
||||||
|
} else {
|
||||||
$values[strtolower($k)] = $v;
|
$values[strtolower($k)] = $v;
|
||||||
}
|
}
|
||||||
return array($values);
|
}
|
||||||
|
|
||||||
|
return [$values];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$valueType = strtolower($this->getValueType());
|
||||||
|
|
||||||
|
foreach ($this->getJsonValue() as $value) {
|
||||||
|
$writer->writeElement($valueType, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an RRULE value string, and turns it into a struct-ish array.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function stringToArray($value)
|
||||||
|
{
|
||||||
|
$value = strtoupper($value);
|
||||||
|
$newValue = [];
|
||||||
|
foreach (explode(';', $value) as $part) {
|
||||||
|
// Skipping empty parts.
|
||||||
|
if (empty($part)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list($partName, $partValue) = explode('=', $part);
|
||||||
|
|
||||||
|
// The value itself had multiple values..
|
||||||
|
if (false !== strpos($partValue, ',')) {
|
||||||
|
$partValue = explode(',', $partValue);
|
||||||
|
}
|
||||||
|
$newValue[$partName] = $partValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$repair = ($options & self::REPAIR);
|
||||||
|
|
||||||
|
$warnings = parent::validate($options);
|
||||||
|
$values = $this->getParts();
|
||||||
|
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
if ('' === $value) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repair ? 1 : 3,
|
||||||
|
'message' => 'Invalid value for '.$key.' in '.$this->name,
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
if ($repair) {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
} elseif ('BYMONTH' == $key) {
|
||||||
|
$byMonth = (array) $value;
|
||||||
|
foreach ($byMonth as $i => $v) {
|
||||||
|
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repair ? 1 : 3,
|
||||||
|
'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
if ($repair) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
unset($values[$key][$i]);
|
||||||
|
} else {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there is no valid entry left, remove the whole value
|
||||||
|
if (is_array($value) && empty($values[$key])) {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
} elseif ('BYWEEKNO' == $key) {
|
||||||
|
$byWeekNo = (array) $value;
|
||||||
|
foreach ($byWeekNo as $i => $v) {
|
||||||
|
if (!is_numeric($v) || (int) $v < -53 || 0 == (int) $v || (int) $v > 53) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repair ? 1 : 3,
|
||||||
|
'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
if ($repair) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
unset($values[$key][$i]);
|
||||||
|
} else {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there is no valid entry left, remove the whole value
|
||||||
|
if (is_array($value) && empty($values[$key])) {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
} elseif ('BYYEARDAY' == $key) {
|
||||||
|
$byYearDay = (array) $value;
|
||||||
|
foreach ($byYearDay as $i => $v) {
|
||||||
|
if (!is_numeric($v) || (int) $v < -366 || 0 == (int) $v || (int) $v > 366) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repair ? 1 : 3,
|
||||||
|
'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
if ($repair) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
unset($values[$key][$i]);
|
||||||
|
} else {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there is no valid entry left, remove the whole value
|
||||||
|
if (is_array($value) && empty($values[$key])) {
|
||||||
|
unset($values[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isset($values['FREQ'])) {
|
||||||
|
$warnings[] = [
|
||||||
|
'level' => $repair ? 1 : 3,
|
||||||
|
'message' => 'FREQ is required in '.$this->name,
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
if ($repair) {
|
||||||
|
$this->parent->remove($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($repair) {
|
||||||
|
$this->setValue($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $warnings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Property;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integer property
|
* Integer property.
|
||||||
*
|
*
|
||||||
* This object represents INTEGER values. These are always a single integer.
|
* This object represents INTEGER values. These are always a single integer.
|
||||||
* They may be preceeded by either + or -.
|
* They may be preceded by either + or -.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Integer 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,12 +23,10 @@ class Integer 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue((int) $val);
|
$this->setValue((int) $val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,10 +34,9 @@ class Integer extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,10 +47,9 @@ class Integer extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "INTEGER";
|
return 'INTEGER';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,9 +59,18 @@ class Integer extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
return array((int)$this->getValue());
|
return [(int) $this->getValue()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate data from a XML subtree, as it would appear in a xCard or xCal
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public function setXmlValue(array $value)
|
||||||
|
{
|
||||||
|
$value = array_map('intval', $value);
|
||||||
|
parent::setXmlValue($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,23 +2,23 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Component;
|
||||||
Sabre\VObject\Property,
|
use Sabre\VObject\Document;
|
||||||
Sabre\VObject\Component,
|
use Sabre\VObject\Parser\MimeDir;
|
||||||
Sabre\VObject\Parser\MimeDir,
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Document;
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text property
|
* Text property.
|
||||||
*
|
*
|
||||||
* This object represents TEXT values.
|
* This object represents TEXT values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -32,16 +32,17 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $structuredValues = array(
|
protected $structuredValues = [
|
||||||
// vCard
|
// vCard
|
||||||
'N',
|
'N',
|
||||||
'ADR',
|
'ADR',
|
||||||
'ORG',
|
'ORG',
|
||||||
'GENDER',
|
'GENDER',
|
||||||
|
'CLIENTPIDMAP',
|
||||||
|
|
||||||
// iCalendar
|
// iCalendar
|
||||||
'REQUEST-STATUS',
|
'REQUEST-STATUS',
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some text components have a minimum number of components.
|
* Some text components have a minimum number of components.
|
||||||
|
|
@ -51,10 +52,10 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $minimumPropertyValues = array(
|
protected $minimumPropertyValues = [
|
||||||
'N' => 5,
|
'N' => 5,
|
||||||
'ADR' => 7,
|
'ADR' => 7,
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the property.
|
* Creates the property.
|
||||||
|
|
@ -68,10 +69,9 @@ class Text extends Property {
|
||||||
* @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 = array(), $group = null) {
|
public 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,10 +82,8 @@ class Text extends Property {
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct($root, $name, $value, $parameters, $group);
|
parent::__construct($root, $name, $value, $parameters, $group);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||||
*
|
*
|
||||||
|
|
@ -93,22 +91,19 @@ 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) {
|
public 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) {
|
public 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
|
||||||
|
|
@ -116,11 +111,10 @@ class Text extends Property {
|
||||||
// that.
|
// that.
|
||||||
//
|
//
|
||||||
// We also don't have to unescape \\, so all we need to look for is a ;
|
// We also don't have to unescape \\, so all we need to look for is a ;
|
||||||
// that's not preceeded with a \.
|
// that's not preceded with a \.
|
||||||
$regex = '# (?<!\\\\) ; #x';
|
$regex = '# (?<!\\\\) ; #x';
|
||||||
$matches = preg_split($regex, $val);
|
$matches = preg_split($regex, $val);
|
||||||
$this->setValue($matches);
|
$this->setValue($matches);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -128,8 +122,8 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
$val = $this->getParts();
|
$val = $this->getParts();
|
||||||
|
|
||||||
if (isset($this->minimumPropertyValues[$this->name])) {
|
if (isset($this->minimumPropertyValues[$this->name])) {
|
||||||
|
|
@ -137,26 +131,26 @@ class Text extends Property {
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($val as &$item) {
|
foreach ($val as &$item) {
|
||||||
|
|
||||||
if (!is_array($item)) {
|
if (!is_array($item)) {
|
||||||
$item = array($item);
|
$item = [$item];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($item as &$subItem) {
|
foreach ($item as &$subItem) {
|
||||||
$subItem = strtr($subItem, array(
|
$subItem = strtr(
|
||||||
|
$subItem,
|
||||||
|
[
|
||||||
'\\' => '\\\\',
|
'\\' => '\\\\',
|
||||||
';' => '\;',
|
';' => '\;',
|
||||||
',' => '\,',
|
',' => '\,',
|
||||||
"\n" => '\n',
|
"\n" => '\n',
|
||||||
"\r" => "",
|
"\r" => '',
|
||||||
));
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$item = implode(',', $item);
|
$item = implode(',', $item);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode($this->delimiter, $val);
|
return implode($this->delimiter, $val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -166,17 +160,16 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public 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 array($this->getParts());
|
return [$this->getParts()];
|
||||||
} else {
|
|
||||||
return $this->getParts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->getParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -187,10 +180,9 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "TEXT";
|
return 'TEXT';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -198,47 +190,44 @@ class Text extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function serialize() {
|
public 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 ($this->root->getDocumentType() !== Document::VCARD21) {
|
if (Document::VCARD21 !== $this->root->getDocumentType()) {
|
||||||
return parent::serialize();
|
return parent::serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
$val = $this->getParts();
|
$val = $this->getParts();
|
||||||
|
|
||||||
if (isset($this->minimumPropertyValues[$this->name])) {
|
if (isset($this->minimumPropertyValues[$this->name])) {
|
||||||
$val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
|
$val = \array_pad($val, $this->minimumPropertyValues[$this->name], '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imploding multiple parts into a single value, and splitting the
|
// Imploding multiple parts into a single value, and splitting the
|
||||||
// values with ;.
|
// values with ;.
|
||||||
if (count($val)>1) {
|
if (\count($val) > 1) {
|
||||||
foreach ($val as $k => $v) {
|
foreach ($val as $k => $v) {
|
||||||
$val[$k] = str_replace(';','\;', $v);
|
$val[$k] = \str_replace(';', '\;', $v);
|
||||||
}
|
}
|
||||||
$val = implode(';', $val);
|
$val = \implode(';', $val);
|
||||||
} else {
|
} else {
|
||||||
$val = $val[0];
|
$val = $val[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
$str = $this->name;
|
$str = $this->name;
|
||||||
if ($this->group) $str = $this->group . '.' . $this->name;
|
if ($this->group) {
|
||||||
|
$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 (strpos($val,"\n") !== false) {
|
if (false !== \strpos($val, "\n")) {
|
||||||
|
|
||||||
$str .= ';ENCODING=QUOTED-PRINTABLE:';
|
$str .= ';ENCODING=QUOTED-PRINTABLE:';
|
||||||
$lastLine = $str;
|
$lastLine = $str;
|
||||||
$out = null;
|
$out = null;
|
||||||
|
|
@ -247,45 +236,117 @@ 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)) {
|
||||||
return $out;
|
$out .= $lastLine."\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
} else {
|
} else {
|
||||||
$str .= ':'.$val;
|
$str .= ':'.$val;
|
||||||
$out = '';
|
|
||||||
while(strlen($str)>0) {
|
$str = \preg_replace(
|
||||||
if (strlen($str)>75) {
|
'/(
|
||||||
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
|
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
|
||||||
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
|
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
|
||||||
} else {
|
(?![\x80-\xbf]) # prevent splitting multibyte characters
|
||||||
$out.=$str . "\r\n";
|
)/x',
|
||||||
$str='';
|
"$1\r\n ",
|
||||||
|
$str
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove single space after last CRLF
|
||||||
|
return \substr($str, 0, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$values = $this->getParts();
|
||||||
|
|
||||||
|
$map = function ($items) use ($values, $writer) {
|
||||||
|
foreach ($items as $i => $item) {
|
||||||
|
$writer->writeElement(
|
||||||
|
$item,
|
||||||
|
!empty($values[$i]) ? $values[$i] : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch ($this->name) {
|
||||||
|
// Special-casing the REQUEST-STATUS property.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// http://tools.ietf.org/html/rfc6321#section-3.4.1.3
|
||||||
|
case 'REQUEST-STATUS':
|
||||||
|
$writer->writeElement('code', $values[0]);
|
||||||
|
$writer->writeElement('description', $values[1]);
|
||||||
|
|
||||||
|
if (isset($values[2])) {
|
||||||
|
$writer->writeElement('data', $values[2]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
$map([
|
||||||
|
'surname',
|
||||||
|
'given',
|
||||||
|
'additional',
|
||||||
|
'prefix',
|
||||||
|
'suffix',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'GENDER':
|
||||||
|
$map([
|
||||||
|
'sex',
|
||||||
|
'text',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ADR':
|
||||||
|
$map([
|
||||||
|
'pobox',
|
||||||
|
'ext',
|
||||||
|
'street',
|
||||||
|
'locality',
|
||||||
|
'region',
|
||||||
|
'code',
|
||||||
|
'country',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CLIENTPIDMAP':
|
||||||
|
$map([
|
||||||
|
'sourceid',
|
||||||
|
'uri',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
parent::xmlSerializeValue($writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $out;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the node for correctness.
|
* Validates the node for correctness.
|
||||||
*
|
*
|
||||||
|
|
@ -301,30 +362,29 @@ class Text extends Property {
|
||||||
* * node - (reference to the offending node)
|
* * node - (reference to the offending node)
|
||||||
*
|
*
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function validate($options = 0) {
|
public 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[] = array(
|
$warnings[] = [
|
||||||
'level' => 1,
|
'level' => $options & self::REPAIR ? 1 : 3,
|
||||||
'message' => 'This property must have at least ' . $minimum . ' components. 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,23 @@ namespace Sabre\VObject\Property;
|
||||||
use Sabre\VObject\DateTimeParser;
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time property
|
* Time property.
|
||||||
*
|
*
|
||||||
* This object encodes TIME values.
|
* This object encodes TIME values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -31,10 +31,30 @@ class Time extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
|
return 'TIME';
|
||||||
|
}
|
||||||
|
|
||||||
return "TIME";
|
/**
|
||||||
|
* Sets the JSON value, as it would appear in a jCard or jCal object.
|
||||||
|
*
|
||||||
|
* The value must always be an array.
|
||||||
|
*/
|
||||||
|
public function setJsonValue(array $value)
|
||||||
|
{
|
||||||
|
// Removing colons from value.
|
||||||
|
$value = str_replace(
|
||||||
|
':',
|
||||||
|
'',
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (1 === count($value)) {
|
||||||
|
$this->setValue(reset($value));
|
||||||
|
} else {
|
||||||
|
$this->setValue($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,10 +64,9 @@ class Time extends Text {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$parts = DateTimeParser::parseVCardTime($this->getValue());
|
$parts = DateTimeParser::parseVCardTime($this->getValue());
|
||||||
|
|
||||||
$timeStr = '';
|
$timeStr = '';
|
||||||
|
|
||||||
// Hour
|
// Hour
|
||||||
|
|
@ -84,11 +103,29 @@ class Time extends Text {
|
||||||
|
|
||||||
// Timezone
|
// Timezone
|
||||||
if (!is_null($parts['timezone'])) {
|
if (!is_null($parts['timezone'])) {
|
||||||
$timeStr.=$parts['timezone'];
|
if ('Z' === $parts['timezone']) {
|
||||||
|
$timeStr .= 'Z';
|
||||||
|
} else {
|
||||||
|
$timeStr .=
|
||||||
|
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($timeStr);
|
return [$timeStr];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate data from a XML subtree, as it would appear in a xCard or xCal
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public function setXmlValue(array $value)
|
||||||
|
{
|
||||||
|
$value = array_map(
|
||||||
|
function ($value) {
|
||||||
|
return str_replace(':', '', $value);
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
parent::setXmlValue($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,18 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
use
|
|
||||||
Sabre\VObject\Property,
|
|
||||||
Sabre\VObject\Component,
|
|
||||||
Sabre\VObject\Parser\MimeDir,
|
|
||||||
Sabre\VObject\Document;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unknown property
|
* Unknown property.
|
||||||
*
|
*
|
||||||
* This object represents any properties not recognized by the parser.
|
* This object represents any properties not recognized by the parser.
|
||||||
* This type of value has been introduced by the jCal, jCard specs.
|
* This type of value has been introduced by the jCal, jCard specs.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
|
|
@ -27,10 +21,9 @@ class Unknown extends Text {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
return array($this->getRawMimeDirValue());
|
return [$this->getRawMimeDirValue()];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,10 +34,8 @@ class Unknown extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "UNKNOWN";
|
return 'UNKNOWN';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,27 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
|
use Sabre\VObject\Parameter;
|
||||||
use Sabre\VObject\Property;
|
use Sabre\VObject\Property;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URI property
|
* URI property.
|
||||||
*
|
*
|
||||||
* This object encodes URI values. vCard 2.1 calls these URL.
|
* This object encodes URI values. vCard 2.1 calls these URL.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Uri extends Property {
|
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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -31,10 +32,32 @@ class Uri extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
|
return 'URI';
|
||||||
|
}
|
||||||
|
|
||||||
return "URI";
|
/**
|
||||||
|
* Returns an iterable list of children.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function parameters()
|
||||||
|
{
|
||||||
|
$parameters = parent::parameters();
|
||||||
|
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
|
||||||
|
// If we are encoding a URI value, and this URI value has no
|
||||||
|
// VALUE=URI parameter, we add it anyway.
|
||||||
|
//
|
||||||
|
// This is not required by any spec, but both Apple iCal and Apple
|
||||||
|
// AddressBook (at least in version 10.8) will trip over this if
|
||||||
|
// this is not set, and so it improves compatibility.
|
||||||
|
//
|
||||||
|
// See Issue #227 and #235
|
||||||
|
$parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,12 +67,35 @@ class Uri 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->value = $val;
|
// Normally we don't need to do any type of unescaping for these
|
||||||
|
// properties, however.. we've noticed that Google Contacts
|
||||||
|
// specifically escapes the colon (:) with a backslash. While I have
|
||||||
|
// no clue why they thought that was a good idea, I'm unescaping it
|
||||||
|
// anyway.
|
||||||
|
//
|
||||||
|
// Good thing backslashes are not allowed in urls. Makes it easy to
|
||||||
|
// assume that a backslash is always intended as an escape character.
|
||||||
|
if ('URL' === $this->name) {
|
||||||
|
$regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x';
|
||||||
|
$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
$newVal = '';
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
switch ($match) {
|
||||||
|
case '\:':
|
||||||
|
$newVal .= ':';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$newVal .= $match;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->value = $newVal;
|
||||||
|
} else {
|
||||||
|
$this->value = strtr($val, ['\,' => ',']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,14 +103,14 @@ class Uri extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
if (is_array($this->value)) {
|
if (is_array($this->value)) {
|
||||||
return $this->value[0];
|
$value = $this->value[0];
|
||||||
} else {
|
} else {
|
||||||
return $this->value;
|
$value = $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return strtr($value, [',' => '\,']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,23 @@
|
||||||
namespace Sabre\VObject\Property;
|
namespace Sabre\VObject\Property;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UtcOffset property
|
* UtcOffset property.
|
||||||
*
|
*
|
||||||
* This object encodes UTC-OFFSET values.
|
* This object encodes UTC-OFFSET values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -29,9 +29,42 @@ class UtcOffset extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
|
return 'UTC-OFFSET';
|
||||||
|
}
|
||||||
|
|
||||||
return "UTC-OFFSET";
|
/**
|
||||||
|
* Sets the JSON value, as it would appear in a jCard or jCal object.
|
||||||
|
*
|
||||||
|
* The value must always be an array.
|
||||||
|
*/
|
||||||
|
public function setJsonValue(array $value)
|
||||||
|
{
|
||||||
|
$value = array_map(
|
||||||
|
function ($value) {
|
||||||
|
return str_replace(':', '', $value);
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
parent::setJsonValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value, in the format it should be encoded for JSON.
|
||||||
|
*
|
||||||
|
* This method must always return an array.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getJsonValue()
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
function ($value) {
|
||||||
|
return substr($value, 0, -2).':'.
|
||||||
|
substr($value, -2);
|
||||||
|
},
|
||||||
|
parent::getJsonValue()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,17 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\VCard;
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
use
|
|
||||||
Sabre\VObject\DateTimeParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date property
|
* Date property.
|
||||||
*
|
*
|
||||||
* This object encodes vCard DATE values.
|
* This object encodes vCard DATE values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Date extends DateAndOrTime {
|
class Date extends DateAndOrTime
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
*
|
*
|
||||||
|
|
@ -24,10 +21,16 @@ class Date extends DateAndOrTime {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "DATE";
|
return 'DATE';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the property as a DateTime object.
|
||||||
|
*/
|
||||||
|
public function setDateTime(\DateTimeInterface $dt)
|
||||||
|
{
|
||||||
|
$this->value = $dt->format('Ymd');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,31 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\VCard;
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
use
|
use DateTime;
|
||||||
Sabre\VObject\DateTimeParser,
|
use DateTimeImmutable;
|
||||||
Sabre\VObject\Property\Text;
|
use DateTimeInterface;
|
||||||
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
use Sabre\VObject\InvalidDataException;
|
||||||
|
use Sabre\VObject\Property;
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DateAndOrTime property
|
* DateAndOrTime property.
|
||||||
*
|
*
|
||||||
* This object encodes DATE-AND-OR-TIME values.
|
* This object encodes DATE-AND-OR-TIME values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class DateAndOrTime extends Text {
|
class DateAndOrTime extends Property
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Field separator
|
* Field separator.
|
||||||
*
|
*
|
||||||
* @var null|string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -32,10 +36,99 @@ class DateAndOrTime extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
|
return 'DATE-AND-OR-TIME';
|
||||||
|
}
|
||||||
|
|
||||||
return "DATE-AND-OR-TIME";
|
/**
|
||||||
|
* Sets a multi-valued property.
|
||||||
|
*
|
||||||
|
* You may also specify DateTimeInterface objects here.
|
||||||
|
*/
|
||||||
|
public function setParts(array $parts)
|
||||||
|
{
|
||||||
|
if (count($parts) > 1) {
|
||||||
|
throw new \InvalidArgumentException('Only one value allowed');
|
||||||
|
}
|
||||||
|
if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
|
||||||
|
$this->setDateTime($parts[0]);
|
||||||
|
} else {
|
||||||
|
parent::setParts($parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current value.
|
||||||
|
*
|
||||||
|
* This may be either a single, or multiple strings in an array.
|
||||||
|
*
|
||||||
|
* Instead of strings, you may also use DateTimeInterface here.
|
||||||
|
*
|
||||||
|
* @param string|array|DateTimeInterface $value
|
||||||
|
*/
|
||||||
|
public function setValue($value)
|
||||||
|
{
|
||||||
|
if ($value instanceof DateTimeInterface) {
|
||||||
|
$this->setDateTime($value);
|
||||||
|
} else {
|
||||||
|
parent::setValue($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the property as a DateTime object.
|
||||||
|
*/
|
||||||
|
public function setDateTime(DateTimeInterface $dt)
|
||||||
|
{
|
||||||
|
$tz = $dt->getTimeZone();
|
||||||
|
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
||||||
|
|
||||||
|
if ($isUtc) {
|
||||||
|
$value = $dt->format('Ymd\\THis\\Z');
|
||||||
|
} else {
|
||||||
|
// Calculating the offset.
|
||||||
|
$value = $dt->format('Ymd\\THisO');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date-time value.
|
||||||
|
*
|
||||||
|
* Note that if this property contained more than 1 date-time, only the
|
||||||
|
* first will be returned. To get an array with multiple values, call
|
||||||
|
* getDateTimes.
|
||||||
|
*
|
||||||
|
* If no time was specified, we will always use midnight (in the default
|
||||||
|
* timezone) as the time.
|
||||||
|
*
|
||||||
|
* If parts of the date were omitted, such as the year, we will grab the
|
||||||
|
* current values for those. So at the time of writing, if the year was
|
||||||
|
* omitted, we would have filled in 2014.
|
||||||
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
|
*/
|
||||||
|
public function getDateTime()
|
||||||
|
{
|
||||||
|
$now = new DateTime();
|
||||||
|
|
||||||
|
$tzFormat = 0 === $now->getTimezone()->getOffset($now) ? '\\Z' : 'O';
|
||||||
|
$nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This'.$tzFormat));
|
||||||
|
|
||||||
|
$dateParts = DateTimeParser::parseVCardDateTime($this->getValue());
|
||||||
|
|
||||||
|
// This sets all the missing parts to the current date/time.
|
||||||
|
// So if the year was missing for a birthday, we're making it 'this
|
||||||
|
// year'.
|
||||||
|
foreach ($dateParts as $k => $v) {
|
||||||
|
if (is_null($v)) {
|
||||||
|
$dateParts[$k] = $nowParts[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DateTimeImmutable("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,8 +138,8 @@ class DateAndOrTime extends Text {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
||||||
|
|
||||||
$dateStr = '';
|
$dateStr = '';
|
||||||
|
|
@ -60,18 +153,14 @@ class DateAndOrTime extends Text {
|
||||||
// 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'];
|
||||||
|
|
||||||
|
|
@ -79,23 +168,20 @@ class DateAndOrTime extends Text {
|
||||||
// If month and date are set, we need the separator dash.
|
// If month and date are set, we need the separator dash.
|
||||||
$dateStr .= '-';
|
$dateStr .= '-';
|
||||||
}
|
}
|
||||||
} else {
|
} elseif (isset($parts['date'])) {
|
||||||
if (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'
|
||||||
$dateStr .= '-';
|
$dateStr .= '-';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Date
|
// Date
|
||||||
if (!is_null($parts['date'])) {
|
if (!is_null($parts['date'])) {
|
||||||
$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 array($dateStr);
|
return [$dateStr];
|
||||||
}
|
}
|
||||||
|
|
||||||
$dateStr .= 'T';
|
$dateStr .= 'T';
|
||||||
|
|
@ -120,12 +206,10 @@ class DateAndOrTime extends Text {
|
||||||
if (!is_null($parts['second'])) {
|
if (!is_null($parts['second'])) {
|
||||||
$dateStr .= ':';
|
$dateStr .= ':';
|
||||||
}
|
}
|
||||||
} else {
|
} elseif (isset($parts['second'])) {
|
||||||
if (isset($parts['second'])) {
|
|
||||||
// Dash for empty minute
|
// Dash for empty minute
|
||||||
$dateStr .= '-';
|
$dateStr .= '-';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Second
|
// Second
|
||||||
if (!is_null($parts['second'])) {
|
if (!is_null($parts['second'])) {
|
||||||
|
|
@ -137,8 +221,147 @@ class DateAndOrTime extends Text {
|
||||||
$dateStr .= $parts['timezone'];
|
$dateStr .= $parts['timezone'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($dateStr);
|
return [$dateStr];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$valueType = strtolower($this->getValueType());
|
||||||
|
$parts = DateTimeParser::parseVCardDateAndOrTime($this->getValue());
|
||||||
|
$value = '';
|
||||||
|
|
||||||
|
// $d = defined
|
||||||
|
$d = function ($part) use ($parts) {
|
||||||
|
return !is_null($parts[$part]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// $r = read
|
||||||
|
$r = function ($part) use ($parts) {
|
||||||
|
return $parts[$part];
|
||||||
|
};
|
||||||
|
|
||||||
|
// From the Relax NG Schema.
|
||||||
|
//
|
||||||
|
// # 4.3.1
|
||||||
|
// value-date = element date {
|
||||||
|
// xsd:string { pattern = "\d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d" }
|
||||||
|
// }
|
||||||
|
if (($d('year') || $d('month') || $d('date'))
|
||||||
|
&& (!$d('hour') && !$d('minute') && !$d('second') && !$d('timezone'))) {
|
||||||
|
if ($d('year') && $d('month') && $d('date')) {
|
||||||
|
$value .= $r('year').$r('month').$r('date');
|
||||||
|
} elseif ($d('year') && $d('month') && !$d('date')) {
|
||||||
|
$value .= $r('year').'-'.$r('month');
|
||||||
|
} elseif (!$d('year') && $d('month')) {
|
||||||
|
$value .= '--'.$r('month').$r('date');
|
||||||
|
} elseif (!$d('year') && !$d('month') && $d('date')) {
|
||||||
|
$value .= '---'.$r('date');
|
||||||
|
}
|
||||||
|
|
||||||
|
// # 4.3.2
|
||||||
|
// value-time = element time {
|
||||||
|
// xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d?)|--\d\d)"
|
||||||
|
// ~ "(Z|[+\-]\d\d(\d\d)?)?" }
|
||||||
|
// }
|
||||||
|
} elseif ((!$d('year') && !$d('month') && !$d('date'))
|
||||||
|
&& ($d('hour') || $d('minute') || $d('second'))) {
|
||||||
|
if ($d('hour')) {
|
||||||
|
$value .= $r('hour').$r('minute').$r('second');
|
||||||
|
} elseif ($d('minute')) {
|
||||||
|
$value .= '-'.$r('minute').$r('second');
|
||||||
|
} elseif ($d('second')) {
|
||||||
|
$value .= '--'.$r('second');
|
||||||
|
}
|
||||||
|
|
||||||
|
$value .= $r('timezone');
|
||||||
|
|
||||||
|
// # 4.3.3
|
||||||
|
// value-date-time = element date-time {
|
||||||
|
// xsd:string { pattern = "(\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?"
|
||||||
|
// ~ "(Z|[+\-]\d\d(\d\d)?)?" }
|
||||||
|
// }
|
||||||
|
} elseif ($d('date') && $d('hour')) {
|
||||||
|
if ($d('year') && $d('month') && $d('date')) {
|
||||||
|
$value .= $r('year').$r('month').$r('date');
|
||||||
|
} elseif (!$d('year') && $d('month') && $d('date')) {
|
||||||
|
$value .= '--'.$r('month').$r('date');
|
||||||
|
} elseif (!$d('year') && !$d('month') && $d('date')) {
|
||||||
|
$value .= '---'.$r('date');
|
||||||
|
}
|
||||||
|
|
||||||
|
$value .= 'T'.$r('hour').$r('minute').$r('second').
|
||||||
|
$r('timezone');
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->writeElement($valueType, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||||
|
*
|
||||||
|
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||||
|
* not yet done, but parameters are not included.
|
||||||
|
*
|
||||||
|
* @param string $val
|
||||||
|
*/
|
||||||
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
|
$this->setValue($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a raw mime-dir representation of the value.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
|
return implode($this->delimiter, $this->getParts());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the node for correctness.
|
||||||
|
*
|
||||||
|
* The following options are supported:
|
||||||
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
||||||
|
*
|
||||||
|
* This method returns an array with detected problems.
|
||||||
|
* Every element has the following properties:
|
||||||
|
*
|
||||||
|
* * level - problem level.
|
||||||
|
* * message - A human-readable string describing the issue.
|
||||||
|
* * node - A reference to the problematic node.
|
||||||
|
*
|
||||||
|
* The level means:
|
||||||
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
||||||
|
* 2 - An inconsequential issue
|
||||||
|
* 3 - A severe issue.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate($options = 0)
|
||||||
|
{
|
||||||
|
$messages = parent::validate($options);
|
||||||
|
$value = $this->getValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DateTimeParser::parseVCardDateTime($value);
|
||||||
|
} catch (InvalidDataException $e) {
|
||||||
|
$messages[] = [
|
||||||
|
'level' => 3,
|
||||||
|
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property',
|
||||||
|
'node' => $this,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,17 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\VCard;
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
use
|
|
||||||
Sabre\VObject\DateTimeParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DateTime property
|
* DateTime property.
|
||||||
*
|
*
|
||||||
* This object encodes DATE-TIME values for vCards.
|
* This object encodes DATE-TIME values for vCards.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class DateTime extends DateAndOrTime {
|
class DateTime extends DateAndOrTime
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
*
|
*
|
||||||
|
|
@ -24,10 +21,8 @@ class DateTime extends DateAndOrTime {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "DATE-TIME";
|
return 'DATE-TIME';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,19 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\VCard;
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\Property;
|
||||||
Sabre\VObject\Property;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LanguageTag property
|
* LanguageTag property.
|
||||||
*
|
*
|
||||||
* This object represents LANGUAGE-TAG values as used in vCards.
|
* This object represents LANGUAGE-TAG values as used in vCards.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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,12 +22,10 @@ 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) {
|
public function setRawMimeDirValue($val)
|
||||||
|
{
|
||||||
$this->setValue($val);
|
$this->setValue($val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,10 +33,9 @@ class LanguageTag extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue() {
|
public function getRawMimeDirValue()
|
||||||
|
{
|
||||||
return $this->value;
|
return $this->getValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,10 +46,8 @@ class LanguageTag extends Property {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "LANGUAGE-TAG";
|
return 'LANGUAGE-TAG';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
|
use Sabre\VObject\Property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PhoneNumber property.
|
||||||
|
*
|
||||||
|
* This object encodes PHONE-NUMBER values.
|
||||||
|
*
|
||||||
|
* @author Christian Kraus <christian@kraus.work>
|
||||||
|
*/
|
||||||
|
class PhoneNumber extends Property\Text
|
||||||
|
{
|
||||||
|
protected $structuredValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of value.
|
||||||
|
*
|
||||||
|
* This corresponds to the VALUE= parameter. Every property also has a
|
||||||
|
* 'default' valueType.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getValueType()
|
||||||
|
{
|
||||||
|
return 'PHONE-NUMBER';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,28 +2,28 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Property\VCard;
|
namespace Sabre\VObject\Property\VCard;
|
||||||
|
|
||||||
use
|
use Sabre\VObject\DateTimeParser;
|
||||||
Sabre\VObject\DateTimeParser,
|
use Sabre\VObject\Property\Text;
|
||||||
Sabre\VObject\Property\Text;
|
use Sabre\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TimeStamp property
|
* TimeStamp property.
|
||||||
*
|
*
|
||||||
* This object encodes TIMESTAMP values.
|
* This object encodes TIMESTAMP values.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $delimiter = null;
|
public $delimiter = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of value.
|
* Returns the type of value.
|
||||||
|
|
@ -33,10 +33,9 @@ class TimeStamp extends Text {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getValueType() {
|
public function getValueType()
|
||||||
|
{
|
||||||
return "TIMESTAMP";
|
return 'TIMESTAMP';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,8 +45,8 @@ class TimeStamp extends Text {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getJsonValue() {
|
public function getJsonValue()
|
||||||
|
{
|
||||||
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
||||||
|
|
||||||
$dateStr =
|
$dateStr =
|
||||||
|
|
@ -63,7 +62,20 @@ class TimeStamp extends Text {
|
||||||
$dateStr .= $parts['timezone'];
|
$dateStr .= $parts['timezone'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($dateStr);
|
return [$dateStr];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method serializes only the value of a property. This is used to
|
||||||
|
* create xCard or xCal documents.
|
||||||
|
*
|
||||||
|
* @param Xml\Writer $writer XML writer
|
||||||
|
*/
|
||||||
|
protected function xmlSerializeValue(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
// xCard is the only XML and JSON format that has the same date and time
|
||||||
|
// format than vCard.
|
||||||
|
$valueType = strtolower($this->getValueType());
|
||||||
|
$writer->writeElement($valueType, $this->getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iCalendar/vCard/jCal/jCard reader object.
|
* iCalendar/vCard/jCal/jCard/xCal/xCard reader object.
|
||||||
*
|
*
|
||||||
* This object provides a few (static) convenience methods to quickly access
|
* This object provides a few (static) convenience methods to quickly access
|
||||||
* the parsers.
|
* the parsers.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
|
|
@ -36,15 +36,17 @@ class Reader {
|
||||||
*
|
*
|
||||||
* @param string|resource $data
|
* @param string|resource $data
|
||||||
* @param int $options
|
* @param int $options
|
||||||
|
* @param string $charset
|
||||||
|
*
|
||||||
* @return Document
|
* @return Document
|
||||||
*/
|
*/
|
||||||
static function read($data, $options = 0) {
|
public static function read($data, $options = 0, $charset = 'UTF-8')
|
||||||
|
{
|
||||||
$parser = new Parser\MimeDir();
|
$parser = new Parser\MimeDir();
|
||||||
|
$parser->setCharset($charset);
|
||||||
$result = $parser->parse($data, $options);
|
$result = $parser->parse($data, $options);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -53,21 +55,41 @@ 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 it's input.
|
* You can either a string, a readable stream, or an array for its 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 Node
|
*
|
||||||
|
* @return Document
|
||||||
*/
|
*/
|
||||||
static function readJson($data, $options = 0) {
|
public 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a xCard or xCal object, and returns the top component.
|
||||||
|
*
|
||||||
|
* The options argument is a bitfield. Pass any of the OPTIONS constant to
|
||||||
|
* alter the parsers' behaviour.
|
||||||
|
*
|
||||||
|
* You can either supply a string, or a readable stream for input.
|
||||||
|
*
|
||||||
|
* @param string|resource $data
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return Document
|
||||||
|
*/
|
||||||
|
public static function readXML($data, $options = 0)
|
||||||
|
{
|
||||||
|
$parser = new Parser\XML();
|
||||||
|
$result = $parser->parse($data, $options);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,497 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Recur;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use DateTimeZone;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Sabre\VObject\Component;
|
||||||
|
use Sabre\VObject\Component\VEvent;
|
||||||
|
use Sabre\VObject\Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to determine new for a recurring event, when the next
|
||||||
|
* events occur.
|
||||||
|
*
|
||||||
|
* This iterator may loop infinitely in the future, therefore it is important
|
||||||
|
* that if you use this class, you set hard limits for the amount of iterations
|
||||||
|
* you want to handle.
|
||||||
|
*
|
||||||
|
* Note that currently there is not full support for the entire iCalendar
|
||||||
|
* specification, as it's very complex and contains a lot of permutations
|
||||||
|
* that's not yet used very often in software.
|
||||||
|
*
|
||||||
|
* For the focus has been on features as they actually appear in Calendaring
|
||||||
|
* software, but this may well get expanded as needed / on demand
|
||||||
|
*
|
||||||
|
* The following RRULE properties are supported
|
||||||
|
* * UNTIL
|
||||||
|
* * INTERVAL
|
||||||
|
* * COUNT
|
||||||
|
* * FREQ=DAILY
|
||||||
|
* * BYDAY
|
||||||
|
* * BYHOUR
|
||||||
|
* * BYMONTH
|
||||||
|
* * FREQ=WEEKLY
|
||||||
|
* * BYDAY
|
||||||
|
* * BYHOUR
|
||||||
|
* * WKST
|
||||||
|
* * FREQ=MONTHLY
|
||||||
|
* * BYMONTHDAY
|
||||||
|
* * BYDAY
|
||||||
|
* * BYSETPOS
|
||||||
|
* * FREQ=YEARLY
|
||||||
|
* * BYMONTH
|
||||||
|
* * BYYEARDAY
|
||||||
|
* * BYWEEKNO
|
||||||
|
* * BYMONTHDAY (only if BYMONTH is also set)
|
||||||
|
* * BYDAY (only if BYMONTH is also set)
|
||||||
|
*
|
||||||
|
* Anything beyond this is 'undefined', which means that it may get ignored, or
|
||||||
|
* you may get unexpected results. The effect is that in some applications the
|
||||||
|
* specified recurrence may look incorrect, or is missing.
|
||||||
|
*
|
||||||
|
* The recurrence iterator also does not yet support THISANDFUTURE.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class EventIterator implements \Iterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Reference timeZone for floating dates and times.
|
||||||
|
*
|
||||||
|
* @var DateTimeZone
|
||||||
|
*/
|
||||||
|
protected $timeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if we're iterating an all-day event.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $allDay = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the iterator.
|
||||||
|
*
|
||||||
|
* There's three ways to set up the iterator.
|
||||||
|
*
|
||||||
|
* 1. You can pass a VCALENDAR component and a UID.
|
||||||
|
* 2. You can pass an array of VEVENTs (all UIDS should match).
|
||||||
|
* 3. You can pass a single VEVENT component.
|
||||||
|
*
|
||||||
|
* Only the second method is recommended. The other 1 and 3 will be removed
|
||||||
|
* at some point in the future.
|
||||||
|
*
|
||||||
|
* The $uid parameter is only required for the first method.
|
||||||
|
*
|
||||||
|
* @param Component|array $input
|
||||||
|
* @param string|null $uid
|
||||||
|
* @param DateTimeZone $timeZone reference timezone for floating dates and
|
||||||
|
* times
|
||||||
|
*/
|
||||||
|
public function __construct($input, $uid = null, DateTimeZone $timeZone = null)
|
||||||
|
{
|
||||||
|
if (is_null($timeZone)) {
|
||||||
|
$timeZone = new DateTimeZone('UTC');
|
||||||
|
}
|
||||||
|
$this->timeZone = $timeZone;
|
||||||
|
|
||||||
|
if (is_array($input)) {
|
||||||
|
$events = $input;
|
||||||
|
} elseif ($input instanceof VEvent) {
|
||||||
|
// Single instance mode.
|
||||||
|
$events = [$input];
|
||||||
|
} else {
|
||||||
|
// Calendar + UID mode.
|
||||||
|
$uid = (string) $uid;
|
||||||
|
if (!$uid) {
|
||||||
|
throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
|
||||||
|
}
|
||||||
|
if (!isset($input->VEVENT)) {
|
||||||
|
throw new InvalidArgumentException('No events found in this calendar');
|
||||||
|
}
|
||||||
|
$events = $input->getByUID($uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($events as $vevent) {
|
||||||
|
if (!isset($vevent->{'RECURRENCE-ID'})) {
|
||||||
|
$this->masterEvent = $vevent;
|
||||||
|
} else {
|
||||||
|
$this->exceptions[
|
||||||
|
$vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
|
||||||
|
] = true;
|
||||||
|
$this->overriddenEvents[] = $vevent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->masterEvent) {
|
||||||
|
// No base event was found. CalDAV does allow cases where only
|
||||||
|
// overridden instances are stored.
|
||||||
|
//
|
||||||
|
// In this particular case, we're just going to grab the first
|
||||||
|
// event and use that instead. This may not always give the
|
||||||
|
// desired result.
|
||||||
|
if (!count($this->overriddenEvents)) {
|
||||||
|
throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
|
||||||
|
}
|
||||||
|
$this->masterEvent = array_shift($this->overriddenEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
|
||||||
|
$this->allDay = !$this->masterEvent->DTSTART->hasTime();
|
||||||
|
|
||||||
|
if (isset($this->masterEvent->EXDATE)) {
|
||||||
|
foreach ($this->masterEvent->EXDATE as $exDate) {
|
||||||
|
foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
|
||||||
|
$this->exceptions[$dt->getTimeStamp()] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->masterEvent->DTEND)) {
|
||||||
|
$this->eventDuration =
|
||||||
|
$this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
|
||||||
|
$this->startDate->getTimeStamp();
|
||||||
|
} elseif (isset($this->masterEvent->DURATION)) {
|
||||||
|
$duration = $this->masterEvent->DURATION->getDateInterval();
|
||||||
|
$end = clone $this->startDate;
|
||||||
|
$end = $end->add($duration);
|
||||||
|
$this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
|
||||||
|
} elseif ($this->allDay) {
|
||||||
|
$this->eventDuration = 3600 * 24;
|
||||||
|
} else {
|
||||||
|
$this->eventDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->masterEvent->RDATE)) {
|
||||||
|
$this->recurIterator = new RDateIterator(
|
||||||
|
$this->masterEvent->RDATE->getParts(),
|
||||||
|
$this->startDate
|
||||||
|
);
|
||||||
|
} elseif (isset($this->masterEvent->RRULE)) {
|
||||||
|
$this->recurIterator = new RRuleIterator(
|
||||||
|
$this->masterEvent->RRULE->getParts(),
|
||||||
|
$this->startDate
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->recurIterator = new RRuleIterator(
|
||||||
|
[
|
||||||
|
'FREQ' => 'DAILY',
|
||||||
|
'COUNT' => 1,
|
||||||
|
],
|
||||||
|
$this->startDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rewind();
|
||||||
|
if (!$this->valid()) {
|
||||||
|
throw new NoInstancesException('This recurrence rule does not generate any valid instances');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the date for the current position of the iterator.
|
||||||
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
if ($this->currentDate) {
|
||||||
|
return clone $this->currentDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the start date for the current iteration of the
|
||||||
|
* event.
|
||||||
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
|
*/
|
||||||
|
public function getDtStart()
|
||||||
|
{
|
||||||
|
if ($this->currentDate) {
|
||||||
|
return clone $this->currentDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the end date for the current iteration of the
|
||||||
|
* event.
|
||||||
|
*
|
||||||
|
* @return DateTimeImmutable
|
||||||
|
*/
|
||||||
|
public function getDtEnd()
|
||||||
|
{
|
||||||
|
if (!$this->valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
|
||||||
|
return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
|
||||||
|
} else {
|
||||||
|
$end = clone $this->currentDate;
|
||||||
|
|
||||||
|
return $end->modify('+'.$this->eventDuration.' seconds');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a VEVENT for the current iterations of the event.
|
||||||
|
*
|
||||||
|
* This VEVENT will have a recurrence id, and its DTSTART and DTEND
|
||||||
|
* altered.
|
||||||
|
*
|
||||||
|
* @return VEvent
|
||||||
|
*/
|
||||||
|
public function getEventObject()
|
||||||
|
{
|
||||||
|
if ($this->currentOverriddenEvent) {
|
||||||
|
return $this->currentOverriddenEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = clone $this->masterEvent;
|
||||||
|
|
||||||
|
// Ignoring the following block, because PHPUnit's code coverage
|
||||||
|
// ignores most of these lines, and this messes with our stats.
|
||||||
|
//
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
unset(
|
||||||
|
$event->RRULE,
|
||||||
|
$event->EXDATE,
|
||||||
|
$event->RDATE,
|
||||||
|
$event->EXRULE,
|
||||||
|
$event->{'RECURRENCE-ID'}
|
||||||
|
);
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
|
$event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
|
||||||
|
if (isset($event->DTEND)) {
|
||||||
|
$event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
|
||||||
|
}
|
||||||
|
$recurid = clone $event->DTSTART;
|
||||||
|
$recurid->name = 'RECURRENCE-ID';
|
||||||
|
$event->add($recurid);
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current position of the iterator.
|
||||||
|
*
|
||||||
|
* This is for us simply a 0-based index.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
// The counter is always 1 ahead.
|
||||||
|
return $this->counter - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called after next, to see if the iterator is still at a valid
|
||||||
|
* position, or if it's at the end.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) {
|
||||||
|
throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $this->currentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the iterator back to the starting point.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->recurIterator->rewind();
|
||||||
|
// re-creating overridden event index.
|
||||||
|
$index = [];
|
||||||
|
foreach ($this->overriddenEvents as $key => $event) {
|
||||||
|
$stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
|
||||||
|
$index[$stamp][] = $key;
|
||||||
|
}
|
||||||
|
krsort($index);
|
||||||
|
$this->counter = 0;
|
||||||
|
$this->overriddenEventsIndex = $index;
|
||||||
|
$this->currentOverriddenEvent = null;
|
||||||
|
|
||||||
|
$this->nextDate = null;
|
||||||
|
$this->currentDate = clone $this->startDate;
|
||||||
|
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the iterator with one step.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
$this->currentOverriddenEvent = null;
|
||||||
|
++$this->counter;
|
||||||
|
if ($this->nextDate) {
|
||||||
|
// We had a stored value.
|
||||||
|
$nextDate = $this->nextDate;
|
||||||
|
$this->nextDate = null;
|
||||||
|
} else {
|
||||||
|
// We need to ask rruleparser for the next date.
|
||||||
|
// We need to do this until we find a date that's not in the
|
||||||
|
// exception list.
|
||||||
|
do {
|
||||||
|
if (!$this->recurIterator->valid()) {
|
||||||
|
$nextDate = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$nextDate = $this->recurIterator->current();
|
||||||
|
$this->recurIterator->next();
|
||||||
|
} while (isset($this->exceptions[$nextDate->getTimeStamp()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// $nextDate now contains what rrule thinks is the next one, but an
|
||||||
|
// overridden event may cut ahead.
|
||||||
|
if ($this->overriddenEventsIndex) {
|
||||||
|
$offsets = end($this->overriddenEventsIndex);
|
||||||
|
$timestamp = key($this->overriddenEventsIndex);
|
||||||
|
$offset = end($offsets);
|
||||||
|
if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
|
||||||
|
// Overridden event comes first.
|
||||||
|
$this->currentOverriddenEvent = $this->overriddenEvents[$offset];
|
||||||
|
|
||||||
|
// Putting the rrule next date aside.
|
||||||
|
$this->nextDate = $nextDate;
|
||||||
|
$this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
|
||||||
|
|
||||||
|
// Ensuring that this item will only be used once.
|
||||||
|
array_pop($this->overriddenEventsIndex[$timestamp]);
|
||||||
|
if (!$this->overriddenEventsIndex[$timestamp]) {
|
||||||
|
array_pop($this->overriddenEventsIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit point!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentDate = $nextDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quickly jump to a date in the future.
|
||||||
|
*/
|
||||||
|
public function fastForward(DateTimeInterface $dateTime)
|
||||||
|
{
|
||||||
|
while ($this->valid() && $this->getDtEnd() <= $dateTime) {
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this recurring event never ends.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInfinite()
|
||||||
|
{
|
||||||
|
return $this->recurIterator->isInfinite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RRULE parser.
|
||||||
|
*
|
||||||
|
* @var RRuleIterator
|
||||||
|
*/
|
||||||
|
protected $recurIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration, in seconds, of the master event.
|
||||||
|
*
|
||||||
|
* We use this to calculate the DTEND for subsequent events.
|
||||||
|
*/
|
||||||
|
protected $eventDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the main (master) event.
|
||||||
|
*
|
||||||
|
* @var VEVENT
|
||||||
|
*/
|
||||||
|
protected $masterEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of overridden events.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $overriddenEvents = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden event index.
|
||||||
|
*
|
||||||
|
* Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
|
||||||
|
* property.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $overriddenEventsIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of recurrence-id's that are either part of EXDATE, or are
|
||||||
|
* overridden.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $exceptions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal event counter.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The very start of the iteration process.
|
||||||
|
*
|
||||||
|
* @var DateTimeImmutable
|
||||||
|
*/
|
||||||
|
protected $startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where we are currently in the iteration process.
|
||||||
|
*
|
||||||
|
* @var DateTimeImmutable
|
||||||
|
*/
|
||||||
|
protected $currentDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The next date from the rrule parser.
|
||||||
|
*
|
||||||
|
* Sometimes we need to temporary store the next date, because an
|
||||||
|
* overridden event came before.
|
||||||
|
*
|
||||||
|
* @var DateTimeImmutable
|
||||||
|
*/
|
||||||
|
protected $nextDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event that overwrites the current iteration.
|
||||||
|
*
|
||||||
|
* @var VEVENT
|
||||||
|
*/
|
||||||
|
protected $currentOverriddenEvent;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Recur;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception will get thrown when a recurrence rule generated more than
|
||||||
|
* the maximum number of instances.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||||
|
*/
|
||||||
|
class MaxInstancesExceededException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Recur;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception gets thrown when a recurrence iterator produces 0 instances.
|
||||||
|
*
|
||||||
|
* This may happen when every occurrence in a rrule is also in EXDATE.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||||
|
*/
|
||||||
|
class NoInstancesException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Recur;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Iterator;
|
||||||
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RRuleParser.
|
||||||
|
*
|
||||||
|
* This class receives an RRULE string, and allows you to iterate to get a list
|
||||||
|
* of dates in that recurrence.
|
||||||
|
*
|
||||||
|
* For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
|
||||||
|
* 5 items, one for each day.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class RDateIterator implements Iterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates the Iterator.
|
||||||
|
*
|
||||||
|
* @param string|array $rrule
|
||||||
|
*/
|
||||||
|
public function __construct($rrule, DateTimeInterface $start)
|
||||||
|
{
|
||||||
|
$this->startDate = $start;
|
||||||
|
$this->parseRDate($rrule);
|
||||||
|
$this->currentDate = clone $this->startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of the Iterator interface {{{ */
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
if (!$this->valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone $this->currentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current item number.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current item is a valid item for the recurrence
|
||||||
|
* iterator.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
return $this->counter <= count($this->dates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the iterator.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->currentDate = clone $this->startDate;
|
||||||
|
$this->counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes on to the next iteration.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
++$this->counter;
|
||||||
|
if (!$this->valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentDate =
|
||||||
|
DateTimeParser::parse(
|
||||||
|
$this->dates[$this->counter - 1],
|
||||||
|
$this->startDate->getTimezone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End of Iterator implementation }}} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this recurring event never ends.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInfinite()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to quickly go to the next occurrence after the
|
||||||
|
* specified date.
|
||||||
|
*/
|
||||||
|
public function fastForward(DateTimeInterface $dt)
|
||||||
|
{
|
||||||
|
while ($this->valid() && $this->currentDate < $dt) {
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reference start date/time for the rrule.
|
||||||
|
*
|
||||||
|
* All calculations are based on this initial date.
|
||||||
|
*
|
||||||
|
* @var DateTimeInterface
|
||||||
|
*/
|
||||||
|
protected $startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date of the current iteration. You can get this by calling
|
||||||
|
* ->current().
|
||||||
|
*
|
||||||
|
* @var DateTimeInterface
|
||||||
|
*/
|
||||||
|
protected $currentDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current item in the list.
|
||||||
|
*
|
||||||
|
* You can get this number with the key() method.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $counter = 0;
|
||||||
|
|
||||||
|
/* }}} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method receives a string from an RRULE property, and populates this
|
||||||
|
* class with all the values.
|
||||||
|
*
|
||||||
|
* @param string|array $rrule
|
||||||
|
*/
|
||||||
|
protected function parseRDate($rdate)
|
||||||
|
{
|
||||||
|
if (is_string($rdate)) {
|
||||||
|
$rdate = explode(',', $rdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dates = $rdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array with the RRULE dates.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $dates = [];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,988 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\Recur;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Iterator;
|
||||||
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
use Sabre\VObject\InvalidDataException;
|
||||||
|
use Sabre\VObject\Property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RRuleParser.
|
||||||
|
*
|
||||||
|
* This class receives an RRULE string, and allows you to iterate to get a list
|
||||||
|
* of dates in that recurrence.
|
||||||
|
*
|
||||||
|
* For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
|
||||||
|
* 5 items, one for each day.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class RRuleIterator implements Iterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates the Iterator.
|
||||||
|
*
|
||||||
|
* @param string|array $rrule
|
||||||
|
*/
|
||||||
|
public function __construct($rrule, DateTimeInterface $start)
|
||||||
|
{
|
||||||
|
$this->startDate = $start;
|
||||||
|
$this->parseRRule($rrule);
|
||||||
|
$this->currentDate = clone $this->startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of the Iterator interface {{{ */
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
if (!$this->valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone $this->currentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current item number.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current item is a valid item for the recurrence
|
||||||
|
* iterator. This will return false if we've gone beyond the UNTIL or COUNT
|
||||||
|
* statements.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
if (null === $this->currentDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!is_null($this->count)) {
|
||||||
|
return $this->counter < $this->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_null($this->until) || $this->currentDate <= $this->until;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the iterator.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->currentDate = clone $this->startDate;
|
||||||
|
$this->counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes on to the next iteration.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
// Otherwise, we find the next event in the normal RRULE
|
||||||
|
// sequence.
|
||||||
|
switch ($this->frequency) {
|
||||||
|
case 'hourly':
|
||||||
|
$this->nextHourly();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'daily':
|
||||||
|
$this->nextDaily();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'weekly':
|
||||||
|
$this->nextWeekly();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'monthly':
|
||||||
|
$this->nextMonthly();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'yearly':
|
||||||
|
$this->nextYearly();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++$this->counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End of Iterator implementation }}} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this recurring event never ends.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInfinite()
|
||||||
|
{
|
||||||
|
return !$this->count && !$this->until;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to quickly go to the next occurrence after the
|
||||||
|
* specified date.
|
||||||
|
*/
|
||||||
|
public function fastForward(DateTimeInterface $dt)
|
||||||
|
{
|
||||||
|
while ($this->valid() && $this->currentDate < $dt) {
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reference start date/time for the rrule.
|
||||||
|
*
|
||||||
|
* All calculations are based on this initial date.
|
||||||
|
*
|
||||||
|
* @var DateTimeInterface
|
||||||
|
*/
|
||||||
|
protected $startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date of the current iteration. You can get this by calling
|
||||||
|
* ->current().
|
||||||
|
*
|
||||||
|
* @var DateTimeInterface
|
||||||
|
*/
|
||||||
|
protected $currentDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
|
||||||
|
* yearly.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $frequency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of recurrences, or 'null' if infinitely recurring.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval.
|
||||||
|
*
|
||||||
|
* If for example frequency is set to daily, interval = 2 would mean every
|
||||||
|
* 2 days.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $interval = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last instance of this recurrence, inclusively.
|
||||||
|
*
|
||||||
|
* @var DateTimeInterface|null
|
||||||
|
*/
|
||||||
|
protected $until;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which seconds to recur.
|
||||||
|
*
|
||||||
|
* This is an array of integers (between 0 and 60)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $bySecond;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which minutes to recur.
|
||||||
|
*
|
||||||
|
* This is an array of integers (between 0 and 59)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byMinute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which hours to recur.
|
||||||
|
*
|
||||||
|
* This is an array of integers (between 0 and 23)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byHour;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current item in the list.
|
||||||
|
*
|
||||||
|
* You can get this number with the key() method.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $counter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which weekdays to recur.
|
||||||
|
*
|
||||||
|
* This is an array of weekdays
|
||||||
|
*
|
||||||
|
* This may also be preceded by a positive or negative integer. If present,
|
||||||
|
* this indicates the nth occurrence of a specific day within the monthly or
|
||||||
|
* yearly rrule. For instance, -2TU indicates the second-last tuesday of
|
||||||
|
* the month, or year.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which days of the month to recur.
|
||||||
|
*
|
||||||
|
* This is an array of days of the months (1-31). The value can also be
|
||||||
|
* negative. -5 for instance means the 5th last day of the month.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byMonthDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which days of the year to recur.
|
||||||
|
*
|
||||||
|
* This is an array with days of the year (1 to 366). The values can also
|
||||||
|
* be negative. For instance, -1 will always represent the last day of the
|
||||||
|
* year. (December 31st).
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byYearDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which week numbers to recur.
|
||||||
|
*
|
||||||
|
* This is an array of integers from 1 to 53. The values can also be
|
||||||
|
* negative. -1 will always refer to the last week of the year.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byWeekNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which months to recur.
|
||||||
|
*
|
||||||
|
* This is an array of integers from 1 to 12.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $byMonth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which items in an existing st to recur.
|
||||||
|
*
|
||||||
|
* These numbers work together with an existing by* rule. It specifies
|
||||||
|
* exactly which items of the existing by-rule to filter.
|
||||||
|
*
|
||||||
|
* Valid values are 1 to 366 and -1 to -366. As an example, this can be
|
||||||
|
* used to recur the last workday of the month.
|
||||||
|
*
|
||||||
|
* This would be done by setting frequency to 'monthly', byDay to
|
||||||
|
* 'MO,TU,WE,TH,FR' and bySetPos to -1.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $bySetPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the week starts.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $weekStart = 'MO';
|
||||||
|
|
||||||
|
/* Functions that advance the iterator {{{ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the processing for advancing the iterator for hourly frequency.
|
||||||
|
*/
|
||||||
|
protected function nextHourly()
|
||||||
|
{
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the processing for advancing the iterator for daily frequency.
|
||||||
|
*/
|
||||||
|
protected function nextDaily()
|
||||||
|
{
|
||||||
|
if (!$this->byHour && !$this->byDay) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurrenceHours = [];
|
||||||
|
if (!empty($this->byHour)) {
|
||||||
|
$recurrenceHours = $this->getHours();
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurrenceDays = [];
|
||||||
|
if (!empty($this->byDay)) {
|
||||||
|
$recurrenceDays = $this->getDays();
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurrenceMonths = [];
|
||||||
|
if (!empty($this->byMonth)) {
|
||||||
|
$recurrenceMonths = $this->getMonths();
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->byHour) {
|
||||||
|
if ('23' == $this->currentDate->format('G')) {
|
||||||
|
// to obey the interval rule
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentDate = $this->currentDate->modify('+1 hours');
|
||||||
|
} else {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current month of the year
|
||||||
|
$currentMonth = $this->currentDate->format('n');
|
||||||
|
|
||||||
|
// Current day of the week
|
||||||
|
$currentDay = $this->currentDate->format('w');
|
||||||
|
|
||||||
|
// Current hour of the day
|
||||||
|
$currentHour = $this->currentDate->format('G');
|
||||||
|
} while (
|
||||||
|
($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
|
||||||
|
($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
|
||||||
|
($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the processing for advancing the iterator for weekly frequency.
|
||||||
|
*/
|
||||||
|
protected function nextWeekly()
|
||||||
|
{
|
||||||
|
if (!$this->byHour && !$this->byDay) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurrenceHours = [];
|
||||||
|
if ($this->byHour) {
|
||||||
|
$recurrenceHours = $this->getHours();
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurrenceDays = [];
|
||||||
|
if ($this->byDay) {
|
||||||
|
$recurrenceDays = $this->getDays();
|
||||||
|
}
|
||||||
|
|
||||||
|
// First day of the week:
|
||||||
|
$firstDay = $this->dayMap[$this->weekStart];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->byHour) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+1 hours');
|
||||||
|
} else {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+1 days');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current day of the week
|
||||||
|
$currentDay = (int) $this->currentDate->format('w');
|
||||||
|
|
||||||
|
// Current hour of the day
|
||||||
|
$currentHour = (int) $this->currentDate->format('G');
|
||||||
|
|
||||||
|
// We need to roll over to the next week
|
||||||
|
if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
|
||||||
|
|
||||||
|
// We need to go to the first day of this week, but only if we
|
||||||
|
// are not already on this first day of this week.
|
||||||
|
if ($this->currentDate->format('w') != $firstDay) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a match
|
||||||
|
} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the processing for advancing the iterator for monthly frequency.
|
||||||
|
*/
|
||||||
|
protected function nextMonthly()
|
||||||
|
{
|
||||||
|
$currentDayOfMonth = $this->currentDate->format('j');
|
||||||
|
if (!$this->byMonthDay && !$this->byDay) {
|
||||||
|
// If the current day is higher than the 28th, rollover can
|
||||||
|
// occur to the next month. We Must skip these invalid
|
||||||
|
// entries.
|
||||||
|
if ($currentDayOfMonth < 29) {
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
|
||||||
|
} else {
|
||||||
|
$increase = 0;
|
||||||
|
do {
|
||||||
|
++$increase;
|
||||||
|
$tempDate = clone $this->currentDate;
|
||||||
|
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months');
|
||||||
|
} while ($tempDate->format('j') != $currentDayOfMonth);
|
||||||
|
$this->currentDate = $tempDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$occurrence = -1;
|
||||||
|
while (true) {
|
||||||
|
$occurrences = $this->getMonthlyOccurrences();
|
||||||
|
|
||||||
|
foreach ($occurrences as $occurrence) {
|
||||||
|
// The first occurrence thats higher than the current
|
||||||
|
// day of the month wins.
|
||||||
|
if ($occurrence > $currentDayOfMonth) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we made it all the way here, it means there were no
|
||||||
|
// valid occurrences, and we need to advance to the next
|
||||||
|
// month.
|
||||||
|
//
|
||||||
|
// This line does not currently work in hhvm. Temporary workaround
|
||||||
|
// follows:
|
||||||
|
// $this->currentDate->modify('first day of this month');
|
||||||
|
$this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
|
||||||
|
// end of workaround
|
||||||
|
$this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
|
||||||
|
|
||||||
|
// This goes to 0 because we need to start counting at the
|
||||||
|
// beginning.
|
||||||
|
$currentDayOfMonth = 0;
|
||||||
|
|
||||||
|
// For some reason the "until" parameter was not being used here,
|
||||||
|
// that's why the workaround of the 10000 year bug was needed at all
|
||||||
|
// let's stop it before the "until" parameter date
|
||||||
|
if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
|
||||||
|
// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
|
||||||
|
if ($this->currentDate->getTimestamp() > 253402300799) {
|
||||||
|
$this->currentDate = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
|
(int) $this->currentDate->format('Y'),
|
||||||
|
(int) $this->currentDate->format('n'),
|
||||||
|
(int) $occurrence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the processing for advancing the iterator for yearly frequency.
|
||||||
|
*/
|
||||||
|
protected function nextYearly()
|
||||||
|
{
|
||||||
|
$currentMonth = $this->currentDate->format('n');
|
||||||
|
$currentYear = $this->currentDate->format('Y');
|
||||||
|
$currentDayOfMonth = $this->currentDate->format('j');
|
||||||
|
|
||||||
|
// No sub-rules, so we just advance by year
|
||||||
|
if (empty($this->byMonth)) {
|
||||||
|
// Unless it was a leap day!
|
||||||
|
if (2 == $currentMonth && 29 == $currentDayOfMonth) {
|
||||||
|
$counter = 0;
|
||||||
|
do {
|
||||||
|
++$counter;
|
||||||
|
// Here we increase the year count by the interval, until
|
||||||
|
// we hit a date that's also in a leap year.
|
||||||
|
//
|
||||||
|
// We could just find the next interval that's dividable by
|
||||||
|
// 4, but that would ignore the rule that there's no leap
|
||||||
|
// year every year that's dividable by a 100, but not by
|
||||||
|
// 400. (1800, 1900, 2100). So we just rely on the datetime
|
||||||
|
// functions instead.
|
||||||
|
$nextDate = clone $this->currentDate;
|
||||||
|
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
|
||||||
|
} while (2 != $nextDate->format('n'));
|
||||||
|
|
||||||
|
$this->currentDate = $nextDate;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
|
||||||
|
$dayOffsets = [];
|
||||||
|
if ($this->byDay) {
|
||||||
|
foreach ($this->byDay as $byDay) {
|
||||||
|
$dayOffsets[] = $this->dayMap[$byDay];
|
||||||
|
}
|
||||||
|
} else { // default is Monday
|
||||||
|
$dayOffsets[] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentYear = $this->currentDate->format('Y');
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$checkDates = [];
|
||||||
|
|
||||||
|
// loop through all WeekNo and Days to check all the combinations
|
||||||
|
foreach ($this->byWeekNo as $byWeekNo) {
|
||||||
|
foreach ($dayOffsets as $dayOffset) {
|
||||||
|
$date = clone $this->currentDate;
|
||||||
|
$date = $date->setISODate($currentYear, $byWeekNo, $dayOffset);
|
||||||
|
|
||||||
|
if ($date > $this->currentDate) {
|
||||||
|
$checkDates[] = $date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($checkDates) > 0) {
|
||||||
|
$this->currentDate = min($checkDates);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is no date found, check the next year
|
||||||
|
$currentYear += $this->interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366
|
||||||
|
$dayOffsets = [];
|
||||||
|
if ($this->byDay) {
|
||||||
|
foreach ($this->byDay as $byDay) {
|
||||||
|
$dayOffsets[] = $this->dayMap[$byDay];
|
||||||
|
}
|
||||||
|
} else { // default is Monday-Sunday
|
||||||
|
$dayOffsets = [1, 2, 3, 4, 5, 6, 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentYear = $this->currentDate->format('Y');
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$checkDates = [];
|
||||||
|
|
||||||
|
// loop through all YearDay and Days to check all the combinations
|
||||||
|
foreach ($this->byYearDay as $byYearDay) {
|
||||||
|
$date = clone $this->currentDate;
|
||||||
|
$date = $date->setDate($currentYear, 1, 1);
|
||||||
|
if ($byYearDay > 0) {
|
||||||
|
$date = $date->add(new \DateInterval('P'.$byYearDay.'D'));
|
||||||
|
} else {
|
||||||
|
$date = $date->sub(new \DateInterval('P'.abs($byYearDay).'D'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
|
||||||
|
$checkDates[] = $date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($checkDates) > 0) {
|
||||||
|
$this->currentDate = min($checkDates);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is no date found, check the next year
|
||||||
|
$currentYear += $this->interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The easiest form
|
||||||
|
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentMonth = $this->currentDate->format('n');
|
||||||
|
$currentYear = $this->currentDate->format('Y');
|
||||||
|
$currentDayOfMonth = $this->currentDate->format('j');
|
||||||
|
|
||||||
|
$advancedToNewMonth = false;
|
||||||
|
|
||||||
|
// If we got a byDay or getMonthDay filter, we must first expand
|
||||||
|
// further.
|
||||||
|
if ($this->byDay || $this->byMonthDay) {
|
||||||
|
$occurrence = -1;
|
||||||
|
while (true) {
|
||||||
|
$occurrences = $this->getMonthlyOccurrences();
|
||||||
|
|
||||||
|
foreach ($occurrences as $occurrence) {
|
||||||
|
// The first occurrence that's higher than the current
|
||||||
|
// day of the month wins.
|
||||||
|
// If we advanced to the next month or year, the first
|
||||||
|
// occurrence is always correct.
|
||||||
|
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we made it here, it means we need to advance to
|
||||||
|
// the next month or year.
|
||||||
|
$currentDayOfMonth = 1;
|
||||||
|
$advancedToNewMonth = true;
|
||||||
|
do {
|
||||||
|
++$currentMonth;
|
||||||
|
if ($currentMonth > 12) {
|
||||||
|
$currentYear += $this->interval;
|
||||||
|
$currentMonth = 1;
|
||||||
|
}
|
||||||
|
} while (!in_array($currentMonth, $this->byMonth));
|
||||||
|
|
||||||
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
|
(int) $currentYear,
|
||||||
|
(int) $currentMonth,
|
||||||
|
(int) $currentDayOfMonth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we made it here, it means we got a valid occurrence
|
||||||
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
|
(int) $currentYear,
|
||||||
|
(int) $currentMonth,
|
||||||
|
(int) $occurrence
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// These are the 'byMonth' rules, if there are no byDay or
|
||||||
|
// byMonthDay sub-rules.
|
||||||
|
do {
|
||||||
|
++$currentMonth;
|
||||||
|
if ($currentMonth > 12) {
|
||||||
|
$currentYear += $this->interval;
|
||||||
|
$currentMonth = 1;
|
||||||
|
}
|
||||||
|
} while (!in_array($currentMonth, $this->byMonth));
|
||||||
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
|
(int) $currentYear,
|
||||||
|
(int) $currentMonth,
|
||||||
|
(int) $currentDayOfMonth
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* }}} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method receives a string from an RRULE property, and populates this
|
||||||
|
* class with all the values.
|
||||||
|
*
|
||||||
|
* @param string|array $rrule
|
||||||
|
*/
|
||||||
|
protected function parseRRule($rrule)
|
||||||
|
{
|
||||||
|
if (is_string($rrule)) {
|
||||||
|
$rrule = Property\ICalendar\Recur::stringToArray($rrule);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rrule as $key => $value) {
|
||||||
|
$key = strtoupper($key);
|
||||||
|
switch ($key) {
|
||||||
|
case 'FREQ':
|
||||||
|
$value = strtolower($value);
|
||||||
|
if (!in_array(
|
||||||
|
$value,
|
||||||
|
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
|
||||||
|
)) {
|
||||||
|
throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
|
||||||
|
}
|
||||||
|
$this->frequency = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'UNTIL':
|
||||||
|
$this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
|
||||||
|
|
||||||
|
// In some cases events are generated with an UNTIL=
|
||||||
|
// parameter before the actual start of the event.
|
||||||
|
//
|
||||||
|
// Not sure why this is happening. We assume that the
|
||||||
|
// intention was that the event only recurs once.
|
||||||
|
//
|
||||||
|
// So we are modifying the parameter so our code doesn't
|
||||||
|
// break.
|
||||||
|
if ($this->until < $this->startDate) {
|
||||||
|
$this->until = $this->startDate;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'INTERVAL':
|
||||||
|
case 'COUNT':
|
||||||
|
$val = (int) $value;
|
||||||
|
if ($val < 1) {
|
||||||
|
throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!');
|
||||||
|
}
|
||||||
|
$key = strtolower($key);
|
||||||
|
$this->$key = $val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYSECOND':
|
||||||
|
$this->bySecond = (array) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYMINUTE':
|
||||||
|
$this->byMinute = (array) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYHOUR':
|
||||||
|
$this->byHour = (array) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYDAY':
|
||||||
|
$value = (array) $value;
|
||||||
|
foreach ($value as $part) {
|
||||||
|
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
|
||||||
|
throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->byDay = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYMONTHDAY':
|
||||||
|
$this->byMonthDay = (array) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYYEARDAY':
|
||||||
|
$this->byYearDay = (array) $value;
|
||||||
|
foreach ($this->byYearDay as $byYearDay) {
|
||||||
|
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
|
||||||
|
throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYWEEKNO':
|
||||||
|
$this->byWeekNo = (array) $value;
|
||||||
|
foreach ($this->byWeekNo as $byWeekNo) {
|
||||||
|
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
|
||||||
|
throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYMONTH':
|
||||||
|
$this->byMonth = (array) $value;
|
||||||
|
foreach ($this->byMonth as $byMonth) {
|
||||||
|
if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) {
|
||||||
|
throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BYSETPOS':
|
||||||
|
$this->bySetPos = (array) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'WKST':
|
||||||
|
$this->weekStart = strtoupper($value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidDataException('Not supported: '.strtoupper($key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappings between the day number and english day name.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $dayNames = [
|
||||||
|
0 => 'Sunday',
|
||||||
|
1 => 'Monday',
|
||||||
|
2 => 'Tuesday',
|
||||||
|
3 => 'Wednesday',
|
||||||
|
4 => 'Thursday',
|
||||||
|
5 => 'Friday',
|
||||||
|
6 => 'Saturday',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the occurrences for a monthly frequency with a 'byDay' or
|
||||||
|
* 'byMonthDay' expansion for the current month.
|
||||||
|
*
|
||||||
|
* The returned list is an array of integers with the day of month (1-31).
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getMonthlyOccurrences()
|
||||||
|
{
|
||||||
|
$startDate = clone $this->currentDate;
|
||||||
|
|
||||||
|
$byDayResults = [];
|
||||||
|
|
||||||
|
// Our strategy is to simply go through the byDays, advance the date to
|
||||||
|
// that point and add it to the results.
|
||||||
|
if ($this->byDay) {
|
||||||
|
foreach ($this->byDay as $day) {
|
||||||
|
$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
|
||||||
|
|
||||||
|
// Dayname will be something like 'wednesday'. Now we need to find
|
||||||
|
// all wednesdays in this month.
|
||||||
|
$dayHits = [];
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
$dayHits[] = $checkDate->format('j');
|
||||||
|
$checkDate = $checkDate->modify('next '.$dayName);
|
||||||
|
} while ($checkDate->format('n') === $startDate->format('n'));
|
||||||
|
|
||||||
|
// So now we have 'all wednesdays' for month. It is however
|
||||||
|
// possible that the user only really wanted the 1st, 2nd or last
|
||||||
|
// wednesday.
|
||||||
|
if (strlen($day) > 2) {
|
||||||
|
$offset = (int) substr($day, 0, -2);
|
||||||
|
|
||||||
|
if ($offset > 0) {
|
||||||
|
// It is possible that the day does not exist, such as a
|
||||||
|
// 5th or 6th wednesday of the month.
|
||||||
|
if (isset($dayHits[$offset - 1])) {
|
||||||
|
$byDayResults[] = $dayHits[$offset - 1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if it was negative we count from the end of the array
|
||||||
|
// might not exist, fx. -5th tuesday
|
||||||
|
if (isset($dayHits[count($dayHits) + $offset])) {
|
||||||
|
$byDayResults[] = $dayHits[count($dayHits) + $offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// There was no counter (first, second, last wednesdays), so we
|
||||||
|
// just need to add the all to the list).
|
||||||
|
$byDayResults = array_merge($byDayResults, $dayHits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$byMonthDayResults = [];
|
||||||
|
if ($this->byMonthDay) {
|
||||||
|
foreach ($this->byMonthDay as $monthDay) {
|
||||||
|
// Removing values that are out of range for this month
|
||||||
|
if ($monthDay > $startDate->format('t') ||
|
||||||
|
$monthDay < 0 - $startDate->format('t')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($monthDay > 0) {
|
||||||
|
$byMonthDayResults[] = $monthDay;
|
||||||
|
} else {
|
||||||
|
// Negative values
|
||||||
|
$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was just byDay or just byMonthDay, they just specify our
|
||||||
|
// (almost) final list. If both were provided, then byDay limits the
|
||||||
|
// list.
|
||||||
|
if ($this->byMonthDay && $this->byDay) {
|
||||||
|
$result = array_intersect($byMonthDayResults, $byDayResults);
|
||||||
|
} elseif ($this->byMonthDay) {
|
||||||
|
$result = $byMonthDayResults;
|
||||||
|
} else {
|
||||||
|
$result = $byDayResults;
|
||||||
|
}
|
||||||
|
$result = array_unique($result);
|
||||||
|
sort($result, SORT_NUMERIC);
|
||||||
|
|
||||||
|
// The last thing that needs checking is the BYSETPOS. If it's set, it
|
||||||
|
// means only certain items in the set survive the filter.
|
||||||
|
if (!$this->bySetPos) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredResult = [];
|
||||||
|
foreach ($this->bySetPos as $setPos) {
|
||||||
|
if ($setPos < 0) {
|
||||||
|
$setPos = count($result) + ($setPos + 1);
|
||||||
|
}
|
||||||
|
if (isset($result[$setPos - 1])) {
|
||||||
|
$filteredResult[] = $result[$setPos - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($filteredResult, SORT_NUMERIC);
|
||||||
|
|
||||||
|
return $filteredResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple mapping from iCalendar day names to day numbers.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $dayMap = [
|
||||||
|
'SU' => 0,
|
||||||
|
'MO' => 1,
|
||||||
|
'TU' => 2,
|
||||||
|
'WE' => 3,
|
||||||
|
'TH' => 4,
|
||||||
|
'FR' => 5,
|
||||||
|
'SA' => 6,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function getHours()
|
||||||
|
{
|
||||||
|
$recurrenceHours = [];
|
||||||
|
foreach ($this->byHour as $byHour) {
|
||||||
|
$recurrenceHours[] = $byHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recurrenceHours;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDays()
|
||||||
|
{
|
||||||
|
$recurrenceDays = [];
|
||||||
|
foreach ($this->byDay as $byDay) {
|
||||||
|
// The day may be preceded with a positive (+n) or
|
||||||
|
// negative (-n) integer. However, this does not make
|
||||||
|
// sense in 'weekly' so we ignore it here.
|
||||||
|
$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recurrenceDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMonths()
|
||||||
|
{
|
||||||
|
$recurrenceMonths = [];
|
||||||
|
foreach ($this->byMonth as $byMonth) {
|
||||||
|
$recurrenceMonths[] = $byMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recurrenceMonths;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
55
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Settings.php
Normal file
55
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Settings.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a list of global defaults for vobject.
|
||||||
|
*
|
||||||
|
* Some of these started to appear in various classes, so it made a bit more
|
||||||
|
* sense to centralize them, so it's easier for user to find and change these.
|
||||||
|
*
|
||||||
|
* The global nature of them does mean that changing the settings for one
|
||||||
|
* instance has a global influence.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Settings
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The minimum date we accept for various calculations with dates, such as
|
||||||
|
* recurrences.
|
||||||
|
*
|
||||||
|
* The choice of 1900 is pretty arbitrary, but it covers most common
|
||||||
|
* use-cases. In particular, it covers birthdates for virtually everyone
|
||||||
|
* alive on earth, which is less than 5 people at the time of writing.
|
||||||
|
*/
|
||||||
|
public static $minDate = '1900-01-01';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum date we accept for various calculations with dates, such as
|
||||||
|
* recurrences.
|
||||||
|
*
|
||||||
|
* The choice of 2100 is pretty arbitrary, but should cover most
|
||||||
|
* appointments made for many years to come.
|
||||||
|
*/
|
||||||
|
public static $maxDate = '2100-01-01';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of recurrences that will be generated.
|
||||||
|
*
|
||||||
|
* This setting limits the maximum of recurring events that this library
|
||||||
|
* generates in its recurrence iterators.
|
||||||
|
*
|
||||||
|
* This is a security measure. Without this, it would be possible to craft
|
||||||
|
* specific events that recur many, many times, potentially DDOSing the
|
||||||
|
* server.
|
||||||
|
*
|
||||||
|
* The default (3500) allows creation of a daily event that goes on for 10
|
||||||
|
* years, which is hopefully long enough for most.
|
||||||
|
*
|
||||||
|
* Set this value to -1 to disable this control altogether.
|
||||||
|
*/
|
||||||
|
public static $maxRecurrences = 3500;
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Splitter;
|
namespace Sabre\VObject\Splitter;
|
||||||
|
|
||||||
use
|
use Sabre\VObject;
|
||||||
Sabre\VObject,
|
use Sabre\VObject\Component\VCalendar;
|
||||||
Sabre\VObject\Component\VCalendar;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splitter
|
* Splitter.
|
||||||
*
|
*
|
||||||
* This class is responsible for splitting up iCalendar objects.
|
* This class is responsible for splitting up iCalendar objects.
|
||||||
*
|
*
|
||||||
|
|
@ -15,40 +14,42 @@ use
|
||||||
* calendar-objects inside. Objects with identical UID's will be combined into
|
* calendar-objects inside. Objects with identical UID's will be combined into
|
||||||
* a single object.
|
* a single object.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @author Dominik Tobschall
|
* @author Dominik Tobschall (http://tobschall.de/)
|
||||||
* @author Armin Hackmann
|
* @author Armin Hackmann
|
||||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class ICalendar implements SplitterInterface {
|
class ICalendar implements SplitterInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Timezones
|
* Timezones.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $vtimezones = array();
|
protected $vtimezones = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iCalendar objects
|
* iCalendar objects.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $objects = array();
|
protected $objects = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor.
|
||||||
*
|
*
|
||||||
* The splitter should receive an readable file stream as it's input.
|
* The splitter should receive an readable file stream as its 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) {
|
public function __construct($input, $options = 0)
|
||||||
|
{
|
||||||
$data = VObject\Reader::read($input, $options);
|
$data = VObject\Reader::read($input, $options);
|
||||||
$vtimezones = array();
|
|
||||||
$components = array();
|
if (!$data instanceof VObject\Component\VCalendar) {
|
||||||
|
throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($data->children() as $component) {
|
foreach ($data->children() as $component) {
|
||||||
if (!$component instanceof VObject\Component) {
|
if (!$component instanceof VObject\Component) {
|
||||||
|
|
@ -56,18 +57,16 @@ class ICalendar implements SplitterInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all timezones
|
// Get all timezones
|
||||||
if ($component->name === 'VTIMEZONE') {
|
if ('VTIMEZONE' === $component->name) {
|
||||||
$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) {
|
||||||
$uid = (string)$component->UID;
|
$component->UID = sha1(microtime()).'-vobjectimport';
|
||||||
} else {
|
|
||||||
// Generating a random UID
|
|
||||||
$uid = sha1(microtime()) . '-vobjectimport';
|
|
||||||
}
|
}
|
||||||
|
$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)) {
|
||||||
|
|
@ -76,7 +75,6 @@ class ICalendar implements SplitterInterface {
|
||||||
|
|
||||||
$this->objects[$uid]->add(clone $component);
|
$this->objects[$uid]->add(clone $component);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -85,12 +83,11 @@ 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() {
|
public 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';
|
||||||
|
|
@ -102,13 +99,8 @@ class ICalendar implements SplitterInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
return;
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Sabre\VObject\Splitter;
|
namespace Sabre\VObject\Splitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VObject splitter
|
* VObject splitter.
|
||||||
*
|
*
|
||||||
* The splitter is responsible for reading a large vCard or iCalendar object,
|
* The splitter is responsible for reading a large vCard or iCalendar object,
|
||||||
* and splitting it into multiple objects.
|
* and splitting it into multiple objects.
|
||||||
|
|
@ -11,20 +11,20 @@ namespace Sabre\VObject\Splitter;
|
||||||
* This is for example for Card and CalDAV, which require every event and vcard
|
* This is for example for Card and CalDAV, which require every event and vcard
|
||||||
* to exist in their own objects, instead of one large one.
|
* to exist in their own objects, instead of one large one.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @author Dominik Tobschall
|
* @author Dominik Tobschall (http://tobschall.de/)
|
||||||
* @license http://code.google.com/p/sabredav/wiki/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 it's input.
|
* The splitter should receive an readable file stream as its input.
|
||||||
*
|
*
|
||||||
* @param resource $input
|
* @param resource $input
|
||||||
*/
|
*/
|
||||||
function __construct($input);
|
public 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,8 +32,7 @@ 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
|
||||||
*/
|
*/
|
||||||
function getNext();
|
public function getNext();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
namespace Sabre\VObject\Splitter;
|
namespace Sabre\VObject\Splitter;
|
||||||
|
|
||||||
use
|
use Sabre\VObject;
|
||||||
Sabre\VObject,
|
use Sabre\VObject\Parser\MimeDir;
|
||||||
Sabre\VObject\Parser\MimeDir;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splitter
|
* Splitter.
|
||||||
*
|
*
|
||||||
* This class is responsible for splitting up VCard objects.
|
* This class is responsible for splitting up VCard objects.
|
||||||
*
|
*
|
||||||
|
|
@ -15,40 +14,39 @@ use
|
||||||
* class checks for BEGIN:VCARD and END:VCARD and parses each encountered
|
* class checks for BEGIN:VCARD and END:VCARD and parses each encountered
|
||||||
* component individually.
|
* component individually.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @author Dominik Tobschall
|
* @author Dominik Tobschall (http://tobschall.de/)
|
||||||
* @author Armin Hackmann
|
* @author Armin Hackmann
|
||||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class VCard implements SplitterInterface {
|
class VCard implements SplitterInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* File handle
|
* File handle.
|
||||||
*
|
*
|
||||||
* @var resource
|
* @var resource
|
||||||
*/
|
*/
|
||||||
protected $input;
|
protected $input;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent parser
|
* Persistent parser.
|
||||||
*
|
*
|
||||||
* @var MimeDir
|
* @var MimeDir
|
||||||
*/
|
*/
|
||||||
protected $parser;
|
protected $parser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor.
|
||||||
*
|
*
|
||||||
* The splitter should receive an readable file stream as it's input.
|
* The splitter should receive an readable file stream as its 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) {
|
public function __construct($input, $options = 0)
|
||||||
|
{
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
$this->parser = new MimeDir($input, $options);
|
$this->parser = new MimeDir($input, $options);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,18 +55,20 @@ 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() {
|
public function getNext()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$object = $this->parser->parse();
|
$object = $this->parser->parse();
|
||||||
|
|
||||||
|
if (!$object instanceof VObject\Component\VCard) {
|
||||||
|
throw new VObject\ParseException('The supplied input contained non-VCARD data.');
|
||||||
|
}
|
||||||
} catch (VObject\EofException $e) {
|
} catch (VObject\EofException $e) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,32 +5,27 @@ namespace Sabre\VObject;
|
||||||
/**
|
/**
|
||||||
* Useful utilities for working with various strings.
|
* Useful utilities for working with various strings.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
* @param string $str
|
* @param string $str
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
static function isUTF8($str) {
|
public static function isUTF8($str)
|
||||||
|
{
|
||||||
// First check.. mb_check_encoding
|
|
||||||
if (!mb_check_encoding($str, 'UTF-8')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 true;
|
return (bool) preg_match('%%u', $str);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,22 +35,28 @@ class StringUtil {
|
||||||
* may be expanded upon if we receive other examples.
|
* may be expanded upon if we receive other examples.
|
||||||
*
|
*
|
||||||
* @param string $str
|
* @param string $str
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
static function convertToUTF8($str) {
|
public static function convertToUTF8($str)
|
||||||
|
{
|
||||||
|
$encoding = mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'WINDOWS-1252'], true);
|
||||||
|
|
||||||
$encoding = mb_detect_encoding($str , array('UTF-8','ISO-8859-1'), true);
|
switch ($encoding) {
|
||||||
|
case 'ISO-8859-1':
|
||||||
if ($encoding === 'ISO-8859-1') {
|
|
||||||
$newStr = utf8_encode($str);
|
$newStr = utf8_encode($str);
|
||||||
} else {
|
break;
|
||||||
|
/* Unreachable code. Not sure yet how we can improve this
|
||||||
|
* situation.
|
||||||
|
case 'WINDOWS-1252' :
|
||||||
|
$newStr = iconv('cp1252', 'UTF-8', $str);
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
default:
|
||||||
$newStr = $str;
|
$newStr = $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removing any control characters
|
// Removing any control characters
|
||||||
return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', '', $newStr));
|
return preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $newStr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,313 +2,158 @@
|
||||||
|
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\FindFromOffset;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\GuessFromMsTzId;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\TimezoneFinder;
|
||||||
|
use Sabre\VObject\TimezoneGuesser\TimezoneGuesser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time zone name translation
|
* Time zone name translation.
|
||||||
*
|
*
|
||||||
* This file translates well-known time zone names into "Olson database" time zone names.
|
* This file translates well-known time zone names into "Olson database" time zone names.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @author Frank Edelhaeuser (fedel@users.sourceforge.net)
|
* @author Frank Edelhaeuser (fedel@users.sourceforge.net)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class TimeZoneUtil {
|
class TimeZoneUtil
|
||||||
|
{
|
||||||
|
/** @var self */
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
public static $map = array(
|
/** @var TimezoneGuesser[] */
|
||||||
|
private $timezoneGuessers = [];
|
||||||
|
|
||||||
// from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
|
/** @var TimezoneFinder[] */
|
||||||
// snapshot taken on 2012/01/16
|
private $timezoneFinders = [];
|
||||||
|
|
||||||
// windows
|
private function __construct()
|
||||||
'AUS Central Standard Time'=>'Australia/Darwin',
|
{
|
||||||
'AUS Eastern Standard Time'=>'Australia/Sydney',
|
$this->addGuesser('lic', new GuessFromLicEntry());
|
||||||
'Afghanistan Standard Time'=>'Asia/Kabul',
|
$this->addGuesser('msTzId', new GuessFromMsTzId());
|
||||||
'Alaskan Standard Time'=>'America/Anchorage',
|
$this->addFinder('tzid', new FindFromTimezoneIdentifier());
|
||||||
'Arab Standard Time'=>'Asia/Riyadh',
|
$this->addFinder('tzmap', new FindFromTimezoneMap());
|
||||||
'Arabian Standard Time'=>'Asia/Dubai',
|
$this->addFinder('offset', new FindFromOffset());
|
||||||
'Arabic Standard Time'=>'Asia/Baghdad',
|
}
|
||||||
'Argentina Standard Time'=>'America/Buenos_Aires',
|
|
||||||
'Armenian Standard Time'=>'Asia/Yerevan',
|
|
||||||
'Atlantic Standard Time'=>'America/Halifax',
|
|
||||||
'Azerbaijan Standard Time'=>'Asia/Baku',
|
|
||||||
'Azores Standard Time'=>'Atlantic/Azores',
|
|
||||||
'Bangladesh Standard Time'=>'Asia/Dhaka',
|
|
||||||
'Canada Central Standard Time'=>'America/Regina',
|
|
||||||
'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
|
|
||||||
'Caucasus Standard Time'=>'Asia/Yerevan',
|
|
||||||
'Cen. Australia Standard Time'=>'Australia/Adelaide',
|
|
||||||
'Central America Standard Time'=>'America/Guatemala',
|
|
||||||
'Central Asia Standard Time'=>'Asia/Almaty',
|
|
||||||
'Central Brazilian Standard Time'=>'America/Cuiaba',
|
|
||||||
'Central Europe Standard Time'=>'Europe/Budapest',
|
|
||||||
'Central European Standard Time'=>'Europe/Warsaw',
|
|
||||||
'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
|
|
||||||
'Central Standard Time'=>'America/Chicago',
|
|
||||||
'Central Standard Time (Mexico)'=>'America/Mexico_City',
|
|
||||||
'China Standard Time'=>'Asia/Shanghai',
|
|
||||||
'Dateline Standard Time'=>'Etc/GMT+12',
|
|
||||||
'E. Africa Standard Time'=>'Africa/Nairobi',
|
|
||||||
'E. Australia Standard Time'=>'Australia/Brisbane',
|
|
||||||
'E. Europe Standard Time'=>'Europe/Minsk',
|
|
||||||
'E. South America Standard Time'=>'America/Sao_Paulo',
|
|
||||||
'Eastern Standard Time'=>'America/New_York',
|
|
||||||
'Egypt Standard Time'=>'Africa/Cairo',
|
|
||||||
'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
|
|
||||||
'FLE Standard Time'=>'Europe/Kiev',
|
|
||||||
'Fiji Standard Time'=>'Pacific/Fiji',
|
|
||||||
'GMT Standard Time'=>'Europe/London',
|
|
||||||
'GTB Standard Time'=>'Europe/Istanbul',
|
|
||||||
'Georgian Standard Time'=>'Asia/Tbilisi',
|
|
||||||
'Greenland Standard Time'=>'America/Godthab',
|
|
||||||
'Greenwich Standard Time'=>'Atlantic/Reykjavik',
|
|
||||||
'Hawaiian Standard Time'=>'Pacific/Honolulu',
|
|
||||||
'India Standard Time'=>'Asia/Calcutta',
|
|
||||||
'Iran Standard Time'=>'Asia/Tehran',
|
|
||||||
'Israel Standard Time'=>'Asia/Jerusalem',
|
|
||||||
'Jordan Standard Time'=>'Asia/Amman',
|
|
||||||
'Kamchatka Standard Time'=>'Asia/Kamchatka',
|
|
||||||
'Korea Standard Time'=>'Asia/Seoul',
|
|
||||||
'Magadan Standard Time'=>'Asia/Magadan',
|
|
||||||
'Mauritius Standard Time'=>'Indian/Mauritius',
|
|
||||||
'Mexico Standard Time'=>'America/Mexico_City',
|
|
||||||
'Mexico Standard Time 2'=>'America/Chihuahua',
|
|
||||||
'Mid-Atlantic Standard Time'=>'Etc/GMT-2',
|
|
||||||
'Middle East Standard Time'=>'Asia/Beirut',
|
|
||||||
'Montevideo Standard Time'=>'America/Montevideo',
|
|
||||||
'Morocco Standard Time'=>'Africa/Casablanca',
|
|
||||||
'Mountain Standard Time'=>'America/Denver',
|
|
||||||
'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
|
|
||||||
'Myanmar Standard Time'=>'Asia/Rangoon',
|
|
||||||
'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
|
|
||||||
'Namibia Standard Time'=>'Africa/Windhoek',
|
|
||||||
'Nepal Standard Time'=>'Asia/Katmandu',
|
|
||||||
'New Zealand Standard Time'=>'Pacific/Auckland',
|
|
||||||
'Newfoundland Standard Time'=>'America/St_Johns',
|
|
||||||
'North Asia East Standard Time'=>'Asia/Irkutsk',
|
|
||||||
'North Asia Standard Time'=>'Asia/Krasnoyarsk',
|
|
||||||
'Pacific SA Standard Time'=>'America/Santiago',
|
|
||||||
'Pacific Standard Time'=>'America/Los_Angeles',
|
|
||||||
'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
|
|
||||||
'Pakistan Standard Time'=>'Asia/Karachi',
|
|
||||||
'Paraguay Standard Time'=>'America/Asuncion',
|
|
||||||
'Romance Standard Time'=>'Europe/Paris',
|
|
||||||
'Russian Standard Time'=>'Europe/Moscow',
|
|
||||||
'SA Eastern Standard Time'=>'America/Cayenne',
|
|
||||||
'SA Pacific Standard Time'=>'America/Bogota',
|
|
||||||
'SA Western Standard Time'=>'America/La_Paz',
|
|
||||||
'SE Asia Standard Time'=>'Asia/Bangkok',
|
|
||||||
'Samoa Standard Time'=>'Pacific/Apia',
|
|
||||||
'Singapore Standard Time'=>'Asia/Singapore',
|
|
||||||
'South Africa Standard Time'=>'Africa/Johannesburg',
|
|
||||||
'Sri Lanka Standard Time'=>'Asia/Colombo',
|
|
||||||
'Syria Standard Time'=>'Asia/Damascus',
|
|
||||||
'Taipei Standard Time'=>'Asia/Taipei',
|
|
||||||
'Tasmania Standard Time'=>'Australia/Hobart',
|
|
||||||
'Tokyo Standard Time'=>'Asia/Tokyo',
|
|
||||||
'Tonga Standard Time'=>'Pacific/Tongatapu',
|
|
||||||
'US Eastern Standard Time'=>'America/Indianapolis',
|
|
||||||
'US Mountain Standard Time'=>'America/Phoenix',
|
|
||||||
'UTC+12'=>'Etc/GMT-12',
|
|
||||||
'UTC-02'=>'Etc/GMT+2',
|
|
||||||
'UTC-11'=>'Etc/GMT+11',
|
|
||||||
'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
|
|
||||||
'Venezuela Standard Time'=>'America/Caracas',
|
|
||||||
'Vladivostok Standard Time'=>'Asia/Vladivostok',
|
|
||||||
'W. Australia Standard Time'=>'Australia/Perth',
|
|
||||||
'W. Central Africa Standard Time'=>'Africa/Lagos',
|
|
||||||
'W. Europe Standard Time'=>'Europe/Berlin',
|
|
||||||
'West Asia Standard Time'=>'Asia/Tashkent',
|
|
||||||
'West Pacific Standard Time'=>'Pacific/Port_Moresby',
|
|
||||||
'Yakutsk Standard Time'=>'Asia/Yakutsk',
|
|
||||||
|
|
||||||
// Microsoft exchange timezones
|
private static function getInstance(): self
|
||||||
// Source:
|
{
|
||||||
// http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
|
if (null === self::$instance) {
|
||||||
//
|
self::$instance = new self();
|
||||||
// Correct timezones deduced with help from:
|
}
|
||||||
// http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
||||||
'Universal Coordinated Time' => 'UTC',
|
|
||||||
'Casablanca, Monrovia' => 'Africa/Casablanca',
|
|
||||||
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
|
||||||
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
|
||||||
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
|
||||||
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
|
||||||
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
|
||||||
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
|
||||||
'Prague, Central Europe' => 'Europe/Prague',
|
|
||||||
'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
|
|
||||||
'West Central Africa' => 'Africa/Luanda', // This was a best guess
|
|
||||||
'Athens, Istanbul, Minsk' => 'Europe/Athens',
|
|
||||||
'Bucharest' => 'Europe/Bucharest',
|
|
||||||
'Cairo' => 'Africa/Cairo',
|
|
||||||
'Harare, Pretoria' => 'Africa/Harare',
|
|
||||||
'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
|
|
||||||
'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
|
|
||||||
'Baghdad' => 'Asia/Baghdad',
|
|
||||||
'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
|
|
||||||
'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
|
|
||||||
'East Africa, Nairobi' => 'Africa/Nairobi',
|
|
||||||
'Tehran' => 'Asia/Tehran',
|
|
||||||
'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
|
|
||||||
'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
|
|
||||||
'Kabul' => 'Asia/Kabul',
|
|
||||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
|
||||||
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
|
||||||
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
|
|
||||||
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
|
||||||
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
|
||||||
'Astana, Dhaka' => 'Asia/Dhaka',
|
|
||||||
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
|
||||||
'Rangoon' => 'Asia/Rangoon',
|
|
||||||
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
|
||||||
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
|
||||||
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
|
||||||
'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
|
|
||||||
'Kuala Lumpur, Singapore' => 'Asia/Singapore',
|
|
||||||
'Perth, Western Australia' => 'Australia/Perth',
|
|
||||||
'Taipei' => 'Asia/Taipei',
|
|
||||||
'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
|
|
||||||
'Seoul, Korea Standard time' => 'Asia/Seoul',
|
|
||||||
'Yakutsk' => 'Asia/Yakutsk',
|
|
||||||
'Adelaide, Central Australia' => 'Australia/Adelaide',
|
|
||||||
'Darwin' => 'Australia/Darwin',
|
|
||||||
'Brisbane, East Australia' => 'Australia/Brisbane',
|
|
||||||
'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
|
|
||||||
'Guam, Port Moresby' => 'Pacific/Guam',
|
|
||||||
'Hobart, Tasmania' => 'Australia/Hobart',
|
|
||||||
'Vladivostok' => 'Asia/Vladivostok',
|
|
||||||
'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
|
|
||||||
'Auckland, Wellington' => 'Pacific/Auckland',
|
|
||||||
'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
|
|
||||||
'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
|
|
||||||
'Azores' => 'Atlantic/Azores',
|
|
||||||
'Cape Verde Is.' => 'Atlantic/Cape_Verde',
|
|
||||||
'Mid-Atlantic' => 'America/Noronha',
|
|
||||||
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
|
||||||
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
|
||||||
'Greenland' => 'America/Godthab',
|
|
||||||
'Newfoundland' => 'America/St_Johns',
|
|
||||||
'Atlantic Time (Canada)' => 'America/Halifax',
|
|
||||||
'Caracas, La Paz' => 'America/Caracas',
|
|
||||||
'Santiago' => 'America/Santiago',
|
|
||||||
'Bogota, Lima, Quito' => 'America/Bogota',
|
|
||||||
'Eastern Time (US & Canada)' => 'America/New_York',
|
|
||||||
'Indiana (East)' => 'America/Indiana/Indianapolis',
|
|
||||||
'Central America' => 'America/Guatemala',
|
|
||||||
'Central Time (US & Canada)' => 'America/Chicago',
|
|
||||||
'Mexico City, Tegucigalpa' => 'America/Mexico_City',
|
|
||||||
'Saskatchewan' => 'America/Edmonton',
|
|
||||||
'Arizona' => 'America/Phoenix',
|
|
||||||
'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
|
|
||||||
'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
|
|
||||||
'Alaska' => 'America/Anchorage',
|
|
||||||
'Hawaii' => 'Pacific/Honolulu',
|
|
||||||
'Midway Island, Samoa' => 'Pacific/Midway',
|
|
||||||
'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
|
|
||||||
|
|
||||||
// The following list are timezone names that could be generated by
|
return self::$instance;
|
||||||
// Lotus / Domino
|
}
|
||||||
'Dateline' => 'Etc/GMT-12',
|
|
||||||
'Samoa' => 'Pacific/Apia',
|
private function addGuesser(string $key, TimezoneGuesser $guesser): void
|
||||||
'Hawaiian' => 'Pacific/Honolulu',
|
{
|
||||||
'Alaskan' => 'America/Anchorage',
|
$this->timezoneGuessers[$key] = $guesser;
|
||||||
'Pacific' => 'America/Los_Angeles',
|
}
|
||||||
'Pacific Standard Time' => 'America/Los_Angeles',
|
|
||||||
'Mexico Standard Time 2' => 'America/Chihuahua',
|
private function addFinder(string $key, TimezoneFinder $finder): void
|
||||||
'Mountain' => 'America/Denver',
|
{
|
||||||
'Mountain Standard Time' => 'America/Chihuahua',
|
$this->timezoneFinders[$key] = $finder;
|
||||||
'US Mountain' => 'America/Phoenix',
|
}
|
||||||
'Canada Central' => 'America/Edmonton',
|
|
||||||
'Central America' => 'America/Guatemala',
|
/**
|
||||||
'Central' => 'America/Chicago',
|
* This method will try to find out the correct timezone for an iCalendar
|
||||||
'Central Standard Time' => 'America/Mexico_City',
|
* date-time value.
|
||||||
'Mexico' => 'America/Mexico_City',
|
*
|
||||||
'Eastern' => 'America/New_York',
|
* You must pass the contents of the TZID parameter, as well as the full
|
||||||
'SA Pacific' => 'America/Bogota',
|
* calendar.
|
||||||
'US Eastern' => 'America/Indiana/Indianapolis',
|
*
|
||||||
'Venezuela' => 'America/Caracas',
|
* If the lookup fails, this method will return the default PHP timezone
|
||||||
'Atlantic' => 'America/Halifax',
|
* (as configured using date_default_timezone_set, or the date.timezone ini
|
||||||
'Central Brazilian' => 'America/Manaus',
|
* setting).
|
||||||
'Pacific SA' => 'America/Santiago',
|
*
|
||||||
'SA Western' => 'America/La_Paz',
|
* Alternatively, if $failIfUncertain is set to true, it will throw an
|
||||||
'Newfoundland' => 'America/St_Johns',
|
* exception if we cannot accurately determine the timezone.
|
||||||
'Argentina' => 'America/Argentina/Buenos_Aires',
|
*/
|
||||||
'E. South America' => 'America/Belem',
|
private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone
|
||||||
'Greenland' => 'America/Godthab',
|
{
|
||||||
'Montevideo' => 'America/Montevideo',
|
foreach ($this->timezoneFinders as $timezoneFinder) {
|
||||||
'SA Eastern' => 'America/Belem',
|
$timezone = $timezoneFinder->find($tzid, $failIfUncertain);
|
||||||
'Mid-Atlantic' => 'Etc/GMT-2',
|
if (!$timezone instanceof DateTimeZone) {
|
||||||
'Azores' => 'Atlantic/Azores',
|
continue;
|
||||||
'Cape Verde' => 'Atlantic/Cape_Verde',
|
}
|
||||||
'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
|
|
||||||
'Morocco' => 'Africa/Casablanca',
|
return $timezone;
|
||||||
'Central Europe' => 'Europe/Prague',
|
}
|
||||||
'Central European' => 'Europe/Sarajevo',
|
|
||||||
'Romance' => 'Europe/Paris',
|
if ($vcalendar) {
|
||||||
'W. Central Africa' => 'Africa/Lagos', // Best guess
|
// If that didn't work, we will scan VTIMEZONE objects
|
||||||
'W. Europe' => 'Europe/Amsterdam',
|
foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) {
|
||||||
'E. Europe' => 'Europe/Minsk',
|
if ((string) $vtimezone->TZID === $tzid) {
|
||||||
'Egypt' => 'Africa/Cairo',
|
foreach ($this->timezoneGuessers as $timezoneGuesser) {
|
||||||
'FLE' => 'Europe/Helsinki',
|
$timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain);
|
||||||
'GTB' => 'Europe/Athens',
|
if (!$timezone instanceof DateTimeZone) {
|
||||||
'Israel' => 'Asia/Jerusalem',
|
continue;
|
||||||
'Jordan' => 'Asia/Amman',
|
}
|
||||||
'Middle East' => 'Asia/Beirut',
|
|
||||||
'Namibia' => 'Africa/Windhoek',
|
return $timezone;
|
||||||
'South Africa' => 'Africa/Harare',
|
}
|
||||||
'Arab' => 'Asia/Kuwait',
|
}
|
||||||
'Arabic' => 'Asia/Baghdad',
|
}
|
||||||
'E. Africa' => 'Africa/Nairobi',
|
}
|
||||||
'Georgian' => 'Asia/Tbilisi',
|
|
||||||
'Russian' => 'Europe/Moscow',
|
if ($failIfUncertain) {
|
||||||
'Iran' => 'Asia/Tehran',
|
throw new InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid);
|
||||||
'Arabian' => 'Asia/Muscat',
|
}
|
||||||
'Armenian' => 'Asia/Yerevan',
|
|
||||||
'Azerbijan' => 'Asia/Baku',
|
// If we got all the way here, we default to whatever has been set as the PHP default timezone.
|
||||||
'Caucasus' => 'Asia/Yerevan',
|
return new DateTimeZone(date_default_timezone_get());
|
||||||
'Mauritius' => 'Indian/Mauritius',
|
}
|
||||||
'Afghanistan' => 'Asia/Kabul',
|
|
||||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
public static function addTimezoneGuesser(string $key, TimezoneGuesser $guesser): void
|
||||||
'Pakistan' => 'Asia/Karachi',
|
{
|
||||||
'West Asia' => 'Asia/Tashkent',
|
self::getInstance()->addGuesser($key, $guesser);
|
||||||
'India' => 'Asia/Calcutta',
|
}
|
||||||
'Sri Lanka' => 'Asia/Colombo',
|
|
||||||
'Nepal' => 'Asia/Kathmandu',
|
public static function addTimezoneFinder(string $key, TimezoneFinder $finder): void
|
||||||
'Central Asia' => 'Asia/Dhaka',
|
{
|
||||||
'N. Central Asia' => 'Asia/Almaty',
|
self::getInstance()->addFinder($key, $finder);
|
||||||
'Myanmar' => 'Asia/Rangoon',
|
}
|
||||||
'North Asia' => 'Asia/Krasnoyarsk',
|
|
||||||
'SE Asia' => 'Asia/Bangkok',
|
/**
|
||||||
'China' => 'Asia/Shanghai',
|
* @param string $tzid
|
||||||
'North Asia East' => 'Asia/Irkutsk',
|
* @param false $failIfUncertain
|
||||||
'Singapore' => 'Asia/Singapore',
|
*
|
||||||
'Taipei' => 'Asia/Taipei',
|
* @return DateTimeZone
|
||||||
'W. Australia' => 'Australia/Perth',
|
*/
|
||||||
'Korea' => 'Asia/Seoul',
|
public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false)
|
||||||
'Tokyo' => 'Asia/Tokyo',
|
{
|
||||||
'Yakutsk' => 'Asia/Yakutsk',
|
return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain);
|
||||||
'AUS Central' => 'Australia/Darwin',
|
}
|
||||||
'Cen. Australia' => 'Australia/Adelaide',
|
|
||||||
'AUS Eastern' => 'Australia/Sydney',
|
public static function clean(): void
|
||||||
'E. Australia' => 'Australia/Brisbane',
|
{
|
||||||
'Tasmania' => 'Australia/Hobart',
|
self::$instance = null;
|
||||||
'Vladivostok' => 'Asia/Vladivostok',
|
}
|
||||||
'West Pacific' => 'Pacific/Guam',
|
|
||||||
'Central Pacific' => 'Asia/Magadan',
|
// Keeping things for backwards compatibility
|
||||||
'Fiji' => 'Pacific/Fiji',
|
/**
|
||||||
'New Zealand' => 'Pacific/Auckland',
|
* @var array|null
|
||||||
'Tonga' => 'Pacific/Tongatapu',
|
*
|
||||||
);
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public 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
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public static $microsoftExchangeMap = array(
|
public 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.
|
||||||
|
|
@ -385,98 +230,43 @@ class TimeZoneUtil {
|
||||||
15 => 'Pacific/Honolulu',
|
15 => 'Pacific/Honolulu',
|
||||||
16 => 'Pacific/Midway',
|
16 => 'Pacific/Midway',
|
||||||
39 => 'Pacific/Kwajalein',
|
39 => 'Pacific/Kwajalein',
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will try to find out the correct timezone for an iCalendar
|
* This method will load in all the tz mapping information, if it's not yet
|
||||||
* date-time value.
|
* done.
|
||||||
*
|
*
|
||||||
* You must pass the contents of the TZID parameter, as well as the full
|
* @deprecated
|
||||||
* calendar.
|
|
||||||
*
|
|
||||||
* If the lookup fails, this method will return the default PHP timezone
|
|
||||||
* (as configured using date_default_timezone_set, or the date.timezone ini
|
|
||||||
* setting).
|
|
||||||
*
|
|
||||||
* Alternatively, if $failIfUncertain is set to true, it will throw an
|
|
||||||
* exception if we cannot accurately determine the timezone.
|
|
||||||
*
|
|
||||||
* @param string $tzid
|
|
||||||
* @param Sabre\VObject\Component $vcalendar
|
|
||||||
* @return DateTimeZone
|
|
||||||
*/
|
*/
|
||||||
static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
|
public static function loadTzMaps()
|
||||||
|
{
|
||||||
// First we will just see if the tzid is a support timezone identifier.
|
if (!is_null(self::$map)) {
|
||||||
try {
|
return;
|
||||||
return new \DateTimeZone($tzid);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we check if the tzid is somewhere in our tzid map.
|
self::$map = array_merge(
|
||||||
if (isset(self::$map[$tzid])) {
|
include __DIR__.'/timezonedata/windowszones.php',
|
||||||
return new \DateTimeZone(self::$map[$tzid]);
|
include __DIR__.'/timezonedata/lotuszones.php',
|
||||||
|
include __DIR__.'/timezonedata/exchangezones.php',
|
||||||
|
include __DIR__.'/timezonedata/php-workaround.php'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe the author was hyper-lazy and just included an offset. We
|
/**
|
||||||
// support it, but we aren't happy about it.
|
* This method returns an array of timezone identifiers, that are supported
|
||||||
if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
|
* by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers().
|
||||||
return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
|
*
|
||||||
}
|
* We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
|
||||||
|
* - It's not supported by some PHP versions as well as HHVM.
|
||||||
if ($vcalendar) {
|
* - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
|
||||||
|
* (See timezonedata/php-bc.php and timezonedata php-workaround.php)
|
||||||
// If that didn't work, we will scan VTIMEZONE objects
|
*
|
||||||
foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
|
* @return array
|
||||||
|
*
|
||||||
if ((string)$vtimezone->TZID === $tzid) {
|
* @deprecated
|
||||||
|
*/
|
||||||
// Some clients add 'X-LIC-LOCATION' with the olson name.
|
public static function getIdentifiersBC()
|
||||||
if (isset($vtimezone->{'X-LIC-LOCATION'})) {
|
{
|
||||||
|
return include __DIR__.'/timezonedata/php-bc.php';
|
||||||
$lic = (string)$vtimezone->{'X-LIC-LOCATION'};
|
|
||||||
|
|
||||||
// Libical generators may specify strings like
|
|
||||||
// "SystemV/EST5EDT". For those we must remove the
|
|
||||||
// SystemV part.
|
|
||||||
if (substr($lic,0,8)==='SystemV/') {
|
|
||||||
$lic = substr($lic,8);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new \DateTimeZone($lic);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// Microsoft may add a magic number, which we also have an
|
|
||||||
// answer for.
|
|
||||||
if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
|
|
||||||
$cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
|
||||||
|
|
||||||
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
|
||||||
if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
|
|
||||||
return new \DateTimeZone('Europe/Sarajevo');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset(self::$microsoftExchangeMap[$cdoId])) {
|
|
||||||
return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($failIfUncertain) {
|
|
||||||
throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got all the way here, we default to UTC.
|
|
||||||
return new \DateTimeZone(date_default_timezone_get());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some clients add 'X-LIC-LOCATION' with the olson name.
|
||||||
|
*/
|
||||||
|
class FindFromOffset implements TimezoneFinder
|
||||||
|
{
|
||||||
|
public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone
|
||||||
|
{
|
||||||
|
// Maybe the author was hyper-lazy and just included an offset. We
|
||||||
|
// support it, but we aren't happy about it.
|
||||||
|
if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
|
||||||
|
// Note that the path in the source will never be taken from PHP 5.5.10
|
||||||
|
// onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
|
||||||
|
// already gets returned early in this function. Once we drop support
|
||||||
|
// for versions under PHP 5.5.10, this bit can be taken out of the
|
||||||
|
// source.
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return new DateTimeZone('Etc/GMT'.$matches[1].ltrim(substr($matches[2], 0, 2), '0'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some clients add 'X-LIC-LOCATION' with the olson name.
|
||||||
|
*/
|
||||||
|
class FindFromTimezoneIdentifier implements TimezoneFinder
|
||||||
|
{
|
||||||
|
public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone
|
||||||
|
{
|
||||||
|
// First we will just see if the tzid is a support timezone identifier.
|
||||||
|
//
|
||||||
|
// The only exception is if the timezone starts with (. This is to
|
||||||
|
// handle cases where certain microsoft products generate timezone
|
||||||
|
// identifiers that for instance look like:
|
||||||
|
//
|
||||||
|
// (GMT+01.00) Sarajevo/Warsaw/Zagreb
|
||||||
|
//
|
||||||
|
// Since PHP 5.5.10, the first bit will be used as the timezone and
|
||||||
|
// this method will return just GMT+01:00. This is wrong, because it
|
||||||
|
// doesn't take DST into account
|
||||||
|
if (!isset($tzid[0])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ('(' === $tzid[0]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// PHP has a bug that logs PHP warnings even it shouldn't:
|
||||||
|
// https://bugs.php.net/bug.php?id=67881
|
||||||
|
//
|
||||||
|
// That's why we're checking if we'll be able to successfully instantiate
|
||||||
|
// \DateTimeZone() before doing so. Otherwise we could simply instantiate
|
||||||
|
// and catch the exception.
|
||||||
|
$tzIdentifiers = DateTimeZone::listIdentifiers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
(in_array($tzid, $tzIdentifiers)) ||
|
||||||
|
(preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
|
||||||
|
(in_array($tzid, $this->getIdentifiersBC()))
|
||||||
|
) {
|
||||||
|
return new DateTimeZone($tzid);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns an array of timezone identifiers, that are supported
|
||||||
|
* by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers().
|
||||||
|
*
|
||||||
|
* We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
|
||||||
|
* - It's not supported by some PHP versions as well as HHVM.
|
||||||
|
* - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
|
||||||
|
* (See timezonedata/php-bc.php and timezonedata php-workaround.php)
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getIdentifiersBC()
|
||||||
|
{
|
||||||
|
return include __DIR__.'/../timezonedata/php-bc.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some clients add 'X-LIC-LOCATION' with the olson name.
|
||||||
|
*/
|
||||||
|
class FindFromTimezoneMap implements TimezoneFinder
|
||||||
|
{
|
||||||
|
private $map = [];
|
||||||
|
|
||||||
|
private $patterns = [
|
||||||
|
'/^\((UTC|GMT)(\+|\-)[\d]{2}\:[\d]{2}\) (.*)/',
|
||||||
|
'/^\((UTC|GMT)(\+|\-)[\d]{2}\.[\d]{2}\) (.*)/',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone
|
||||||
|
{
|
||||||
|
// Next, we check if the tzid is somewhere in our tzid map.
|
||||||
|
if ($this->hasTzInMap($tzid)) {
|
||||||
|
return new DateTimeZone($this->getTzFromMap($tzid));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some Microsoft products prefix the offset first, so let's strip that off
|
||||||
|
// and see if it is our tzid map. We don't want to check for this first just
|
||||||
|
// in case there are overrides in our tzid map.
|
||||||
|
foreach ($this->patterns as $pattern) {
|
||||||
|
if (!preg_match($pattern, $tzid, $matches)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$tzidAlternate = $matches[3];
|
||||||
|
if ($this->hasTzInMap($tzidAlternate)) {
|
||||||
|
return new DateTimeZone($this->getTzFromMap($tzidAlternate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns an array of timezone identifiers, that are supported
|
||||||
|
* by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers().
|
||||||
|
*
|
||||||
|
* We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
|
||||||
|
* - It's not supported by some PHP versions as well as HHVM.
|
||||||
|
* - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
|
||||||
|
* (See timezonedata/php-bc.php and timezonedata php-workaround.php)
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getTzMaps()
|
||||||
|
{
|
||||||
|
if ([] === $this->map) {
|
||||||
|
$this->map = array_merge(
|
||||||
|
include __DIR__.'/../timezonedata/windowszones.php',
|
||||||
|
include __DIR__.'/../timezonedata/lotuszones.php',
|
||||||
|
include __DIR__.'/../timezonedata/exchangezones.php',
|
||||||
|
include __DIR__.'/../timezonedata/php-workaround.php'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTzFromMap(string $tzid): string
|
||||||
|
{
|
||||||
|
return $this->getTzMaps()[$tzid];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasTzInMap(string $tzid): bool
|
||||||
|
{
|
||||||
|
return isset($this->getTzMaps()[$tzid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
use Sabre\VObject\Component\VTimeZone;
|
||||||
|
use Sabre\VObject\TimeZoneUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some clients add 'X-LIC-LOCATION' with the olson name.
|
||||||
|
*/
|
||||||
|
class GuessFromLicEntry implements TimezoneGuesser
|
||||||
|
{
|
||||||
|
public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?DateTimeZone
|
||||||
|
{
|
||||||
|
if (!isset($vtimezone->{'X-LIC-LOCATION'})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lic = (string) $vtimezone->{'X-LIC-LOCATION'};
|
||||||
|
|
||||||
|
// Libical generators may specify strings like
|
||||||
|
// "SystemV/EST5EDT". For those we must remove the
|
||||||
|
// SystemV part.
|
||||||
|
if ('SystemV/' === substr($lic, 0, 8)) {
|
||||||
|
$lic = substr($lic, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeZoneUtil::getTimeZone($lic, null, $failIfUncertain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
use Sabre\VObject\Component\VTimeZone;
|
||||||
|
|
||||||
|
class GuessFromMsTzId implements TimezoneGuesser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* List of microsoft exchange timezone ids.
|
||||||
|
*
|
||||||
|
* Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
|
||||||
|
*/
|
||||||
|
public static $microsoftExchangeMap = [
|
||||||
|
0 => 'UTC',
|
||||||
|
31 => 'Africa/Casablanca',
|
||||||
|
|
||||||
|
// Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
|
||||||
|
// I'm not even kidding.. We handle this special case in the
|
||||||
|
// getTimeZone method.
|
||||||
|
2 => 'Europe/Lisbon',
|
||||||
|
1 => 'Europe/London',
|
||||||
|
4 => 'Europe/Berlin',
|
||||||
|
6 => 'Europe/Prague',
|
||||||
|
3 => 'Europe/Paris',
|
||||||
|
69 => 'Africa/Luanda', // This was a best guess
|
||||||
|
7 => 'Europe/Athens',
|
||||||
|
5 => 'Europe/Bucharest',
|
||||||
|
49 => 'Africa/Cairo',
|
||||||
|
50 => 'Africa/Harare',
|
||||||
|
59 => 'Europe/Helsinki',
|
||||||
|
27 => 'Asia/Jerusalem',
|
||||||
|
26 => 'Asia/Baghdad',
|
||||||
|
74 => 'Asia/Kuwait',
|
||||||
|
51 => 'Europe/Moscow',
|
||||||
|
56 => 'Africa/Nairobi',
|
||||||
|
25 => 'Asia/Tehran',
|
||||||
|
24 => 'Asia/Muscat', // Best guess
|
||||||
|
54 => 'Asia/Baku',
|
||||||
|
48 => 'Asia/Kabul',
|
||||||
|
58 => 'Asia/Yekaterinburg',
|
||||||
|
47 => 'Asia/Karachi',
|
||||||
|
23 => 'Asia/Calcutta',
|
||||||
|
62 => 'Asia/Kathmandu',
|
||||||
|
46 => 'Asia/Almaty',
|
||||||
|
71 => 'Asia/Dhaka',
|
||||||
|
66 => 'Asia/Colombo',
|
||||||
|
61 => 'Asia/Rangoon',
|
||||||
|
22 => 'Asia/Bangkok',
|
||||||
|
64 => 'Asia/Krasnoyarsk',
|
||||||
|
45 => 'Asia/Shanghai',
|
||||||
|
63 => 'Asia/Irkutsk',
|
||||||
|
21 => 'Asia/Singapore',
|
||||||
|
73 => 'Australia/Perth',
|
||||||
|
75 => 'Asia/Taipei',
|
||||||
|
20 => 'Asia/Tokyo',
|
||||||
|
72 => 'Asia/Seoul',
|
||||||
|
70 => 'Asia/Yakutsk',
|
||||||
|
19 => 'Australia/Adelaide',
|
||||||
|
44 => 'Australia/Darwin',
|
||||||
|
18 => 'Australia/Brisbane',
|
||||||
|
76 => 'Australia/Sydney',
|
||||||
|
43 => 'Pacific/Guam',
|
||||||
|
42 => 'Australia/Hobart',
|
||||||
|
68 => 'Asia/Vladivostok',
|
||||||
|
41 => 'Asia/Magadan',
|
||||||
|
17 => 'Pacific/Auckland',
|
||||||
|
40 => 'Pacific/Fiji',
|
||||||
|
67 => 'Pacific/Tongatapu',
|
||||||
|
29 => 'Atlantic/Azores',
|
||||||
|
53 => 'Atlantic/Cape_Verde',
|
||||||
|
30 => 'America/Noronha',
|
||||||
|
8 => 'America/Sao_Paulo', // Best guess
|
||||||
|
32 => 'America/Argentina/Buenos_Aires',
|
||||||
|
60 => 'America/Godthab',
|
||||||
|
28 => 'America/St_Johns',
|
||||||
|
9 => 'America/Halifax',
|
||||||
|
33 => 'America/Caracas',
|
||||||
|
65 => 'America/Santiago',
|
||||||
|
35 => 'America/Bogota',
|
||||||
|
10 => 'America/New_York',
|
||||||
|
34 => 'America/Indiana/Indianapolis',
|
||||||
|
55 => 'America/Guatemala',
|
||||||
|
11 => 'America/Chicago',
|
||||||
|
37 => 'America/Mexico_City',
|
||||||
|
36 => 'America/Edmonton',
|
||||||
|
38 => 'America/Phoenix',
|
||||||
|
12 => 'America/Denver', // Best guess
|
||||||
|
13 => 'America/Los_Angeles', // Best guess
|
||||||
|
14 => 'America/Anchorage',
|
||||||
|
15 => 'Pacific/Honolulu',
|
||||||
|
16 => 'Pacific/Midway',
|
||||||
|
39 => 'Pacific/Kwajalein',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function guess(VTimeZone $vtimezone, bool $throwIfUnsure = false): ?DateTimeZone
|
||||||
|
{
|
||||||
|
// Microsoft may add a magic number, which we also have an
|
||||||
|
// answer for.
|
||||||
|
if (!isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
||||||
|
|
||||||
|
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
||||||
|
if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) {
|
||||||
|
return new DateTimeZone('Europe/Sarajevo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset(self::$microsoftExchangeMap[$cdoId])) {
|
||||||
|
return new DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
interface TimezoneFinder
|
||||||
|
{
|
||||||
|
public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject\TimezoneGuesser;
|
||||||
|
|
||||||
|
use DateTimeZone;
|
||||||
|
use Sabre\VObject\Component\VTimeZone;
|
||||||
|
|
||||||
|
interface TimezoneGuesser
|
||||||
|
{
|
||||||
|
public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?DateTimeZone;
|
||||||
|
}
|
||||||
66
snappymail/v/0.0.0/app/libraries/Sabre/VObject/UUIDUtil.php
Normal file
66
snappymail/v/0.0.0/app/libraries/Sabre/VObject/UUIDUtil.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID Utility.
|
||||||
|
*
|
||||||
|
* This class has static methods to generate and validate UUID's.
|
||||||
|
* UUIDs are used a decent amount within various *DAV standards, so it made
|
||||||
|
* sense to include it.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class UUIDUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a pseudo-random v4 UUID.
|
||||||
|
*
|
||||||
|
* This function is based on a comment by Andrew Moore on php.net
|
||||||
|
*
|
||||||
|
* @see http://www.php.net/manual/en/function.uniqid.php#94959
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getUUID()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||||
|
|
||||||
|
// 32 bits for "time_low"
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||||
|
|
||||||
|
// 16 bits for "time_mid"
|
||||||
|
mt_rand(0, 0xffff),
|
||||||
|
|
||||||
|
// 16 bits for "time_hi_and_version",
|
||||||
|
// four most significant bits holds version number 4
|
||||||
|
mt_rand(0, 0x0fff) | 0x4000,
|
||||||
|
|
||||||
|
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||||
|
// 8 bits for "clk_seq_low",
|
||||||
|
// two most significant bits holds zero and one for variant DCE1.1
|
||||||
|
mt_rand(0, 0x3fff) | 0x8000,
|
||||||
|
|
||||||
|
// 48 bits for "node"
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string is a valid UUID.
|
||||||
|
*
|
||||||
|
* @param string $uuid
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function validateUUID($uuid)
|
||||||
|
{
|
||||||
|
return 0 !== preg_match(
|
||||||
|
'/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
|
||||||
|
$uuid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,12 @@ namespace Sabre\VObject;
|
||||||
/**
|
/**
|
||||||
* This utility converts vcards from one version to another.
|
* This utility converts vcards from one version to another.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://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.
|
||||||
*
|
*
|
||||||
|
|
@ -26,57 +26,51 @@ 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 int $targetVersion
|
* @param int $targetVersion
|
||||||
*/
|
*/
|
||||||
public function convert(Component\VCard $input, $targetVersion) {
|
public function convert(Component\VCard $input, $targetVersion)
|
||||||
|
{
|
||||||
$inputVersion = $input->getDocumentType();
|
$inputVersion = $input->getDocumentType();
|
||||||
if ($inputVersion === $targetVersion) {
|
if ($inputVersion === $targetVersion) {
|
||||||
return clone $input;
|
return clone $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) {
|
if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) {
|
||||||
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
||||||
}
|
}
|
||||||
if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) {
|
if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) {
|
||||||
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 = $targetVersion===Document::VCARD40?'4.0':'3.0';
|
$newVersion = Document::VCARD40 === $targetVersion ? '4.0' : '3.0';
|
||||||
|
|
||||||
$output = new Component\VCard(array(
|
$output = new Component\VCard([
|
||||||
'VERSION' => $newVersion,
|
'VERSION' => $newVersion,
|
||||||
));
|
]);
|
||||||
|
|
||||||
foreach($input->children as $property) {
|
// We might have generated a default UID. Remove it!
|
||||||
|
unset($output->UID);
|
||||||
|
|
||||||
|
foreach ($input->children() as $property) {
|
||||||
$this->convertProperty($input, $output, $property, $targetVersion);
|
$this->convertProperty($input, $output, $property, $targetVersion);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles conversion of a single property.
|
* Handles conversion of a single property.
|
||||||
*
|
*
|
||||||
* @param Component\VCard $input
|
|
||||||
* @param Component\VCard $output
|
|
||||||
* @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, array('VERSION', 'PRODID'))) {
|
if (in_array($property->name, ['VERSION', 'PRODID'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$parameters = $property->parameters();
|
$parameters = $property->parameters();
|
||||||
|
|
||||||
$valueType = null;
|
$valueType = null;
|
||||||
if (isset($parameters['VALUE'])) {
|
if (isset($parameters['VALUE'])) {
|
||||||
$valueType = $parameters['VALUE']->getValue();
|
$valueType = $parameters['VALUE']->getValue();
|
||||||
|
|
@ -85,24 +79,60 @@ class VCardConverter {
|
||||||
if (!$valueType) {
|
if (!$valueType) {
|
||||||
$valueType = $property->getValueType();
|
$valueType = $property->getValueType();
|
||||||
}
|
}
|
||||||
|
if (Document::VCARD30 !== $targetVersion && 'PHONE-NUMBER' === $valueType) {
|
||||||
|
$valueType = null;
|
||||||
|
}
|
||||||
|
$newProperty = $output->createProperty(
|
||||||
|
$property->name,
|
||||||
|
$property->getParts(),
|
||||||
|
[], // parameters will get added a bit later.
|
||||||
|
$valueType
|
||||||
|
);
|
||||||
|
|
||||||
$newProperty = null;
|
if (Document::VCARD30 === $targetVersion) {
|
||||||
|
if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {
|
||||||
|
$newProperty = $this->convertUriToBinary($output, $newProperty);
|
||||||
|
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
|
||||||
|
// In vCard 4, the birth year may be optional. This is not the
|
||||||
|
// case for vCard 3. Apple has a workaround for this that
|
||||||
|
// allows applications that support Apple's extension still
|
||||||
|
// omit birthyears in vCard 3, but applications that do not
|
||||||
|
// support this, will just use a random birthyear. We're
|
||||||
|
// choosing 1604 for the birthyear, because that's what apple
|
||||||
|
// uses.
|
||||||
|
$parts = DateTimeParser::parseVCardDateTime($property->getValue());
|
||||||
|
if (is_null($parts['year'])) {
|
||||||
|
$newValue = '1604-'.$parts['month'].'-'.$parts['date'];
|
||||||
|
$newProperty->setValue($newValue);
|
||||||
|
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
|
||||||
|
}
|
||||||
|
|
||||||
if ($targetVersion===Document::VCARD30) {
|
if ('ANNIVERSARY' == $newProperty->name) {
|
||||||
|
// Microsoft non-standard anniversary
|
||||||
if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
|
$newProperty->name = 'X-ANNIVERSARY';
|
||||||
|
|
||||||
$newProperty = $this->convertUriToBinary($output, $property, $parameters);
|
|
||||||
|
|
||||||
} elseif ($property->name === 'KIND') {
|
|
||||||
|
|
||||||
|
// We also need to add a new apple property for the same
|
||||||
|
// purpose. This apple property needs a 'label' in the same
|
||||||
|
// group, so we first need to find a groupname that doesn't
|
||||||
|
// exist yet.
|
||||||
|
$x = 1;
|
||||||
|
while ($output->select('ITEM'.$x.'.')) {
|
||||||
|
++$x;
|
||||||
|
}
|
||||||
|
$output->add('ITEM'.$x.'.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']);
|
||||||
|
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_');
|
||||||
|
}
|
||||||
|
} elseif ('KIND' === $property->name) {
|
||||||
switch (strtolower($property->getValue())) {
|
switch (strtolower($property->getValue())) {
|
||||||
case 'org':
|
case 'org':
|
||||||
// OS X addressbook property.
|
// vCard 3.0 does not have an equivalent to KIND:ORG,
|
||||||
|
// but apple has an extension that means the same
|
||||||
|
// thing.
|
||||||
$newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY');
|
$newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'individual':
|
case 'individual':
|
||||||
// Individual is implied, so we can just skip it.
|
// Individual is implicit, so we skip it.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'group':
|
case 'group':
|
||||||
|
|
@ -110,55 +140,85 @@ class VCardConverter {
|
||||||
$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, array('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);
|
||||||
|
} elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
|
||||||
|
// If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
|
||||||
|
// then we're stripping the year from the vcard 4 value.
|
||||||
|
$parts = DateTimeParser::parseVCardDateTime($property->getValue());
|
||||||
|
if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) {
|
||||||
|
$newValue = '--'.$parts['month'].'-'.$parts['date'];
|
||||||
|
$newProperty->setValue($newValue);
|
||||||
|
}
|
||||||
|
|
||||||
$newProperty = $this->convertBinaryToUri($output, $property, $parameters);
|
// Regardless if the year matched or not, we do need to strip
|
||||||
|
// X-APPLE-OMIT-YEAR.
|
||||||
} else {
|
unset($parameters['X-APPLE-OMIT-YEAR']);
|
||||||
|
}
|
||||||
switch ($property->name) {
|
switch ($property->name) {
|
||||||
case 'X-ABSHOWAS':
|
case 'X-ABSHOWAS':
|
||||||
if (strtoupper($property->getValue()) === 'COMPANY') {
|
if ('COMPANY' === strtoupper($property->getValue())) {
|
||||||
$newProperty = $output->createProperty('KIND','org');
|
$newProperty = $output->createProperty('KIND', 'ORG');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'X-ADDRESSBOOKSERVER-KIND':
|
case 'X-ADDRESSBOOKSERVER-KIND':
|
||||||
if (strtoupper($property->getValue()) === 'GROUP') {
|
if ('GROUP' === strtoupper($property->getValue())) {
|
||||||
$newProperty = $output->createProperty('KIND','group');
|
$newProperty = $output->createProperty('KIND', 'GROUP');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'X-ANNIVERSARY':
|
||||||
|
$newProperty->name = 'ANNIVERSARY';
|
||||||
|
// If we already have an anniversary property with the same
|
||||||
|
// value, ignore.
|
||||||
|
foreach ($output->select('ANNIVERSARY') as $anniversary) {
|
||||||
|
if ($anniversary->getValue() === $newProperty->getValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'X-ABDATE':
|
||||||
|
// Find out what the label was, if it exists.
|
||||||
|
if (!$property->group) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$label = $input->{$property->group.'.X-ABLABEL'};
|
||||||
|
|
||||||
|
// We only support converting anniversaries.
|
||||||
|
if (!$label || '_$!<Anniversary>!$_' !== $label->getValue()) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we already have an anniversary property with the same
|
||||||
|
// value, ignore.
|
||||||
|
foreach ($output->select('ANNIVERSARY') as $anniversary) {
|
||||||
|
if ($anniversary->getValue() === $newProperty->getValue()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
$newProperty->name = 'ANNIVERSARY';
|
||||||
|
break;
|
||||||
if (is_null($newProperty)) {
|
// Apple's per-property label system.
|
||||||
|
case 'X-ABLABEL':
|
||||||
$newProperty = $output->createProperty(
|
if ('_$!<Anniversary>!$_' === $newProperty->getValue()) {
|
||||||
$property->name,
|
// We can safely remove these, as they are converted to
|
||||||
$property->getParts(),
|
// ANNIVERSARY properties.
|
||||||
array(), // no parameters yet
|
return;
|
||||||
$valueType
|
}
|
||||||
);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set property group
|
// set property group
|
||||||
$newProperty->group = $property->group;
|
$newProperty->group = $property->group;
|
||||||
|
|
||||||
if ($targetVersion===Document::VCARD40) {
|
if (Document::VCARD40 === $targetVersion) {
|
||||||
$this->convertParameters40($newProperty, $parameters);
|
$this->convertParameters40($newProperty, $parameters);
|
||||||
} else {
|
} else {
|
||||||
$this->convertParameters30($newProperty, $parameters);
|
$this->convertParameters30($newProperty, $parameters);
|
||||||
|
|
@ -166,7 +226,7 @@ class VCardConverter {
|
||||||
|
|
||||||
// Lastly, we need to see if there's a need for a VALUE parameter.
|
// Lastly, we need to see if there's a need for a VALUE parameter.
|
||||||
//
|
//
|
||||||
// We can do that by instantating a empty property with that name, and
|
// We can do that by instantiating a empty property with that name, and
|
||||||
// seeing if the default valueType is identical to the current one.
|
// seeing if the default valueType is identical to the current one.
|
||||||
$tempProperty = $output->createProperty($newProperty->name);
|
$tempProperty = $output->createProperty($newProperty->name);
|
||||||
if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
|
if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
|
||||||
|
|
@ -174,8 +234,6 @@ class VCardConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
$output->add($newProperty);
|
$output->add($newProperty);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,18 +241,19 @@ class VCardConverter {
|
||||||
*
|
*
|
||||||
* vCard 4.0 no longer supports BINARY properties.
|
* vCard 4.0 no longer supports BINARY properties.
|
||||||
*
|
*
|
||||||
* @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 $property, array &$parameters) {
|
protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters)
|
||||||
|
{
|
||||||
|
$value = $newProperty->getValue();
|
||||||
$newProperty = $output->createProperty(
|
$newProperty = $output->createProperty(
|
||||||
$property->name,
|
$newProperty->name,
|
||||||
null, // no value
|
null, // no value
|
||||||
array(), // no parameters yet
|
[], // no parameters yet
|
||||||
'URI' // Forcing the BINARY type
|
'URI' // Forcing the BINARY type
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -202,12 +261,11 @@ 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 = array();
|
|
||||||
foreach ($parameters['TYPE']->getParts() as $typePart) {
|
foreach ($parameters['TYPE']->getParts() as $typePart) {
|
||||||
if (in_array(
|
if (in_array(
|
||||||
strtoupper($typePart),
|
strtoupper($typePart),
|
||||||
array('JPEG','PNG','GIF')
|
['JPEG', 'PNG', 'GIF']
|
||||||
)) {
|
)) {
|
||||||
$mimeType = 'image/'.strtolower($typePart);
|
$mimeType = 'image/'.strtolower($typePart);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -222,13 +280,11 @@ class VCardConverter {
|
||||||
} else {
|
} else {
|
||||||
unset($parameters['TYPE']);
|
unset($parameters['TYPE']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($property->getValue()));
|
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value));
|
||||||
|
|
||||||
return $newProperty;
|
return $newProperty;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -238,25 +294,23 @@ class VCardConverter {
|
||||||
* be valid in vCard 3.0 as well, we should convert those to BINARY if
|
* be valid in vCard 3.0 as well, we should convert those to BINARY if
|
||||||
* possible, to improve compatibility.
|
* possible, to improve compatibility.
|
||||||
*
|
*
|
||||||
* @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
|
|
||||||
* the new property.
|
|
||||||
* @return Property\Binary|null
|
* @return Property\Binary|null
|
||||||
*/
|
*/
|
||||||
protected function convertUriToBinary(Component\VCard $output, Property\Uri $property, array &$parameters) {
|
protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty)
|
||||||
|
{
|
||||||
$value = $property->getValue();
|
$value = $newProperty->getValue();
|
||||||
|
|
||||||
// Only converting data: uris
|
// Only converting data: uris
|
||||||
if (substr($value, 0, 5)!=='data:') {
|
if ('data:' !== substr($value, 0, 5)) {
|
||||||
return;
|
return $newProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
$newProperty = $output->createProperty(
|
$newProperty = $output->createProperty(
|
||||||
$property->name,
|
$newProperty->name,
|
||||||
null, // no value
|
null, // no value
|
||||||
array(), // no parameters yet
|
[], // no parameters yet
|
||||||
'BINARY'
|
'BINARY'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -271,7 +325,6 @@ 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;
|
||||||
|
|
@ -281,42 +334,33 @@ class VCardConverter {
|
||||||
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 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) $param->noName = false;
|
if ($param->noName) {
|
||||||
|
$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
|
||||||
|
|
@ -327,34 +371,27 @@ class VCardConverter {
|
||||||
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 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) $param->noName = false;
|
if ($param->noName) {
|
||||||
|
$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 (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') {
|
if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) {
|
||||||
$newProperty->add($param->name, $param->getParts());
|
$newProperty->add($param->name, $param->getParts());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -365,7 +402,7 @@ class VCardConverter {
|
||||||
* Any other PREF numbers we'll drop.
|
* Any other PREF numbers we'll drop.
|
||||||
*/
|
*/
|
||||||
case 'PREF':
|
case 'PREF':
|
||||||
if ($param->getValue()=='1') {
|
if ('1' == $param->getValue()) {
|
||||||
$newProperty->add('TYPE', 'PREF');
|
$newProperty->add('TYPE', 'PREF');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -373,10 +410,7 @@ class VCardConverter {
|
||||||
default:
|
default:
|
||||||
$newProperty->add($param->name, $param->getParts());
|
$newProperty->add($param->name, $param->getParts());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,16 @@
|
||||||
namespace Sabre\VObject;
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains the version number for the VObject package
|
* This class contains the version number for the VObject package.
|
||||||
*
|
*
|
||||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
* @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://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Version {
|
class Version
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Full version number
|
* Full version number.
|
||||||
*/
|
*/
|
||||||
const VERSION = '3.1.3';
|
const VERSION = '4.4.1';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Writer.php
Normal file
68
snappymail/v/0.0.0/app/libraries/Sabre/VObject/Writer.php
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Sabre\VObject;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iCalendar/vCard/jCal/jCard/xCal/xCard writer object.
|
||||||
|
*
|
||||||
|
* This object provides a few (static) convenience methods to quickly access
|
||||||
|
* the serializers.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @author Ivan Enderlin
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Writer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Serializes a vCard or iCalendar object.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function write(Component $component)
|
||||||
|
{
|
||||||
|
return $component->serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a jCal or jCard object.
|
||||||
|
*
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function writeJson(Component $component, $options = 0)
|
||||||
|
{
|
||||||
|
return json_encode($component, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a xCal or xCard object.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function writeXml(Component $component)
|
||||||
|
{
|
||||||
|
$writer = new Xml\Writer();
|
||||||
|
$writer->openMemory();
|
||||||
|
$writer->setIndent(true);
|
||||||
|
|
||||||
|
$writer->startDocument('1.0', 'utf-8');
|
||||||
|
|
||||||
|
if ($component instanceof Component\VCalendar) {
|
||||||
|
$writer->startElement('icalendar');
|
||||||
|
$writer->writeAttribute('xmlns', Parser\XML::XCAL_NAMESPACE);
|
||||||
|
} else {
|
||||||
|
$writer->startElement('vcards');
|
||||||
|
$writer->writeAttribute('xmlns', Parser\XML::XCARD_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$component->xmlSerialize($writer);
|
||||||
|
|
||||||
|
$writer->endElement();
|
||||||
|
|
||||||
|
return $writer->outputMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Microsoft exchange timezones
|
||||||
|
* Source:
|
||||||
|
* http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx.
|
||||||
|
*
|
||||||
|
* Correct timezones deduced with help from:
|
||||||
|
* http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'Universal Coordinated Time' => 'UTC',
|
||||||
|
'Casablanca, Monrovia' => 'Africa/Casablanca',
|
||||||
|
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
||||||
|
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
||||||
|
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
||||||
|
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
||||||
|
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
||||||
|
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
||||||
|
'Prague, Central Europe' => 'Europe/Prague',
|
||||||
|
'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
|
||||||
|
'West Central Africa' => 'Africa/Luanda', // This was a best guess
|
||||||
|
'Athens, Istanbul, Minsk' => 'Europe/Athens',
|
||||||
|
'Bucharest' => 'Europe/Bucharest',
|
||||||
|
'Cairo' => 'Africa/Cairo',
|
||||||
|
'Harare, Pretoria' => 'Africa/Harare',
|
||||||
|
'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
|
||||||
|
'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
|
||||||
|
'Baghdad' => 'Asia/Baghdad',
|
||||||
|
'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
|
||||||
|
'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
|
||||||
|
'East Africa, Nairobi' => 'Africa/Nairobi',
|
||||||
|
'Tehran' => 'Asia/Tehran',
|
||||||
|
'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
|
||||||
|
'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
|
||||||
|
'Kabul' => 'Asia/Kabul',
|
||||||
|
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||||
|
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
||||||
|
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
|
||||||
|
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
||||||
|
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
||||||
|
'Astana, Dhaka' => 'Asia/Dhaka',
|
||||||
|
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
||||||
|
'Rangoon' => 'Asia/Rangoon',
|
||||||
|
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
||||||
|
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
||||||
|
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
||||||
|
'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
|
||||||
|
'Kuala Lumpur, Singapore' => 'Asia/Singapore',
|
||||||
|
'Perth, Western Australia' => 'Australia/Perth',
|
||||||
|
'Taipei' => 'Asia/Taipei',
|
||||||
|
'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
|
||||||
|
'Seoul, Korea Standard time' => 'Asia/Seoul',
|
||||||
|
'Yakutsk' => 'Asia/Yakutsk',
|
||||||
|
'Adelaide, Central Australia' => 'Australia/Adelaide',
|
||||||
|
'Darwin' => 'Australia/Darwin',
|
||||||
|
'Brisbane, East Australia' => 'Australia/Brisbane',
|
||||||
|
'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
|
||||||
|
'Guam, Port Moresby' => 'Pacific/Guam',
|
||||||
|
'Hobart, Tasmania' => 'Australia/Hobart',
|
||||||
|
'Vladivostok' => 'Asia/Vladivostok',
|
||||||
|
'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
|
||||||
|
'Auckland, Wellington' => 'Pacific/Auckland',
|
||||||
|
'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
|
||||||
|
'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
|
||||||
|
'Azores' => 'Atlantic/Azores',
|
||||||
|
'Cape Verde Is.' => 'Atlantic/Cape_Verde',
|
||||||
|
'Mid-Atlantic' => 'America/Noronha',
|
||||||
|
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
||||||
|
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
||||||
|
'Greenland' => 'America/Godthab',
|
||||||
|
'Newfoundland' => 'America/St_Johns',
|
||||||
|
'Atlantic Time (Canada)' => 'America/Halifax',
|
||||||
|
'Caracas, La Paz' => 'America/Caracas',
|
||||||
|
'Santiago' => 'America/Santiago',
|
||||||
|
'Bogota, Lima, Quito' => 'America/Bogota',
|
||||||
|
'Eastern Time (US & Canada)' => 'America/New_York',
|
||||||
|
'Indiana (East)' => 'America/Indiana/Indianapolis',
|
||||||
|
'Central America' => 'America/Guatemala',
|
||||||
|
'Central Time (US & Canada)' => 'America/Chicago',
|
||||||
|
'Mexico City, Tegucigalpa' => 'America/Mexico_City',
|
||||||
|
'Saskatchewan' => 'America/Edmonton',
|
||||||
|
'Arizona' => 'America/Phoenix',
|
||||||
|
'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
|
||||||
|
'Pacific Time (US & Canada)' => 'America/Los_Angeles', // Best guess
|
||||||
|
'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
|
||||||
|
'Alaska' => 'America/Anchorage',
|
||||||
|
'Hawaii' => 'Pacific/Honolulu',
|
||||||
|
'Midway Island, Samoa' => 'Pacific/Midway',
|
||||||
|
'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following list are timezone names that could be generated by
|
||||||
|
* Lotus / Domino.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'Dateline' => 'Etc/GMT-12',
|
||||||
|
'Samoa' => 'Pacific/Apia',
|
||||||
|
'Hawaiian' => 'Pacific/Honolulu',
|
||||||
|
'Alaskan' => 'America/Anchorage',
|
||||||
|
'Pacific' => 'America/Los_Angeles',
|
||||||
|
'Pacific Standard Time' => 'America/Los_Angeles',
|
||||||
|
'Mexico Standard Time 2' => 'America/Chihuahua',
|
||||||
|
'Mountain' => 'America/Denver',
|
||||||
|
// 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones.
|
||||||
|
'US Mountain' => 'America/Phoenix',
|
||||||
|
'Canada Central' => 'America/Edmonton',
|
||||||
|
'Central America' => 'America/Guatemala',
|
||||||
|
'Central' => 'America/Chicago',
|
||||||
|
// 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones.
|
||||||
|
'Mexico' => 'America/Mexico_City',
|
||||||
|
'Eastern' => 'America/New_York',
|
||||||
|
'SA Pacific' => 'America/Bogota',
|
||||||
|
'US Eastern' => 'America/Indiana/Indianapolis',
|
||||||
|
'Venezuela' => 'America/Caracas',
|
||||||
|
'Atlantic' => 'America/Halifax',
|
||||||
|
'Central Brazilian' => 'America/Manaus',
|
||||||
|
'Pacific SA' => 'America/Santiago',
|
||||||
|
'SA Western' => 'America/La_Paz',
|
||||||
|
'Newfoundland' => 'America/St_Johns',
|
||||||
|
'Argentina' => 'America/Argentina/Buenos_Aires',
|
||||||
|
'E. South America' => 'America/Belem',
|
||||||
|
'Greenland' => 'America/Godthab',
|
||||||
|
'Montevideo' => 'America/Montevideo',
|
||||||
|
'SA Eastern' => 'America/Belem',
|
||||||
|
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
|
||||||
|
'Azores' => 'Atlantic/Azores',
|
||||||
|
'Cape Verde' => 'Atlantic/Cape_Verde',
|
||||||
|
'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
|
||||||
|
'Morocco' => 'Africa/Casablanca',
|
||||||
|
'Central Europe' => 'Europe/Prague',
|
||||||
|
'Central European' => 'Europe/Sarajevo',
|
||||||
|
'Romance' => 'Europe/Paris',
|
||||||
|
'W. Central Africa' => 'Africa/Lagos', // Best guess
|
||||||
|
'W. Europe' => 'Europe/Amsterdam',
|
||||||
|
'E. Europe' => 'Europe/Minsk',
|
||||||
|
'Egypt' => 'Africa/Cairo',
|
||||||
|
'FLE' => 'Europe/Helsinki',
|
||||||
|
'GTB' => 'Europe/Athens',
|
||||||
|
'Israel' => 'Asia/Jerusalem',
|
||||||
|
'Jordan' => 'Asia/Amman',
|
||||||
|
'Middle East' => 'Asia/Beirut',
|
||||||
|
'Namibia' => 'Africa/Windhoek',
|
||||||
|
'South Africa' => 'Africa/Harare',
|
||||||
|
'Arab' => 'Asia/Kuwait',
|
||||||
|
'Arabic' => 'Asia/Baghdad',
|
||||||
|
'E. Africa' => 'Africa/Nairobi',
|
||||||
|
'Georgian' => 'Asia/Tbilisi',
|
||||||
|
'Russian' => 'Europe/Moscow',
|
||||||
|
'Iran' => 'Asia/Tehran',
|
||||||
|
'Arabian' => 'Asia/Muscat',
|
||||||
|
'Armenian' => 'Asia/Yerevan',
|
||||||
|
'Azerbijan' => 'Asia/Baku',
|
||||||
|
'Caucasus' => 'Asia/Yerevan',
|
||||||
|
'Mauritius' => 'Indian/Mauritius',
|
||||||
|
'Afghanistan' => 'Asia/Kabul',
|
||||||
|
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||||
|
'Pakistan' => 'Asia/Karachi',
|
||||||
|
'West Asia' => 'Asia/Tashkent',
|
||||||
|
'India' => 'Asia/Calcutta',
|
||||||
|
'Sri Lanka' => 'Asia/Colombo',
|
||||||
|
'Nepal' => 'Asia/Kathmandu',
|
||||||
|
'Central Asia' => 'Asia/Dhaka',
|
||||||
|
'N. Central Asia' => 'Asia/Almaty',
|
||||||
|
'Myanmar' => 'Asia/Rangoon',
|
||||||
|
'North Asia' => 'Asia/Krasnoyarsk',
|
||||||
|
'SE Asia' => 'Asia/Bangkok',
|
||||||
|
'China' => 'Asia/Shanghai',
|
||||||
|
'North Asia East' => 'Asia/Irkutsk',
|
||||||
|
'Singapore' => 'Asia/Singapore',
|
||||||
|
'Taipei' => 'Asia/Taipei',
|
||||||
|
'W. Australia' => 'Australia/Perth',
|
||||||
|
'Korea' => 'Asia/Seoul',
|
||||||
|
'Tokyo' => 'Asia/Tokyo',
|
||||||
|
'Yakutsk' => 'Asia/Yakutsk',
|
||||||
|
'AUS Central' => 'Australia/Darwin',
|
||||||
|
'Cen. Australia' => 'Australia/Adelaide',
|
||||||
|
'AUS Eastern' => 'Australia/Sydney',
|
||||||
|
'E. Australia' => 'Australia/Brisbane',
|
||||||
|
'Tasmania' => 'Australia/Hobart',
|
||||||
|
'Vladivostok' => 'Asia/Vladivostok',
|
||||||
|
'West Pacific' => 'Pacific/Guam',
|
||||||
|
'Central Pacific' => 'Asia/Magadan',
|
||||||
|
'Fiji' => 'Pacific/Fiji',
|
||||||
|
'New Zealand' => 'Pacific/Auckland',
|
||||||
|
'Tonga' => 'Pacific/Tongatapu',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of additional PHP timezones that are returned by
|
||||||
|
* DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
|
||||||
|
* valid for new DateTimeZone().
|
||||||
|
*
|
||||||
|
* This list does not include those timezone identifiers that we have to map to
|
||||||
|
* a different identifier for some PHP versions (see php-workaround.php).
|
||||||
|
*
|
||||||
|
* Instead of using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
|
||||||
|
* directly, we use this file because DateTimeZone::ALL_WITH_BC is not properly
|
||||||
|
* supported by all PHP version and HHVM.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'Africa/Asmera',
|
||||||
|
'Africa/Timbuktu',
|
||||||
|
'America/Argentina/ComodRivadavia',
|
||||||
|
'America/Atka',
|
||||||
|
'America/Buenos_Aires',
|
||||||
|
'America/Catamarca',
|
||||||
|
'America/Coral_Harbour',
|
||||||
|
'America/Cordoba',
|
||||||
|
'America/Ensenada',
|
||||||
|
'America/Fort_Wayne',
|
||||||
|
'America/Indianapolis',
|
||||||
|
'America/Jujuy',
|
||||||
|
'America/Knox_IN',
|
||||||
|
'America/Louisville',
|
||||||
|
'America/Mendoza',
|
||||||
|
'America/Montreal',
|
||||||
|
'America/Porto_Acre',
|
||||||
|
'America/Rosario',
|
||||||
|
'America/Shiprock',
|
||||||
|
'America/Virgin',
|
||||||
|
'Antarctica/South_Pole',
|
||||||
|
'Asia/Ashkhabad',
|
||||||
|
'Asia/Calcutta',
|
||||||
|
'Asia/Chungking',
|
||||||
|
'Asia/Dacca',
|
||||||
|
'Asia/Istanbul',
|
||||||
|
'Asia/Katmandu',
|
||||||
|
'Asia/Macao',
|
||||||
|
'Asia/Saigon',
|
||||||
|
'Asia/Tel_Aviv',
|
||||||
|
'Asia/Thimbu',
|
||||||
|
'Asia/Ujung_Pandang',
|
||||||
|
'Asia/Ulan_Bator',
|
||||||
|
'Atlantic/Faeroe',
|
||||||
|
'Atlantic/Jan_Mayen',
|
||||||
|
'Australia/ACT',
|
||||||
|
'Australia/Canberra',
|
||||||
|
'Australia/LHI',
|
||||||
|
'Australia/North',
|
||||||
|
'Australia/NSW',
|
||||||
|
'Australia/Queensland',
|
||||||
|
'Australia/South',
|
||||||
|
'Australia/Tasmania',
|
||||||
|
'Australia/Victoria',
|
||||||
|
'Australia/West',
|
||||||
|
'Australia/Yancowinna',
|
||||||
|
'Brazil/Acre',
|
||||||
|
'Brazil/DeNoronha',
|
||||||
|
'Brazil/East',
|
||||||
|
'Brazil/West',
|
||||||
|
'Canada/Atlantic',
|
||||||
|
'Canada/Central',
|
||||||
|
'Canada/Eastern',
|
||||||
|
'Canada/Mountain',
|
||||||
|
'Canada/Newfoundland',
|
||||||
|
'Canada/Pacific',
|
||||||
|
'Canada/Saskatchewan',
|
||||||
|
'Canada/Yukon',
|
||||||
|
'CET',
|
||||||
|
'Chile/Continental',
|
||||||
|
'Chile/EasterIsland',
|
||||||
|
'EET',
|
||||||
|
'EST',
|
||||||
|
'Etc/GMT',
|
||||||
|
'Etc/GMT+0',
|
||||||
|
'Etc/GMT+1',
|
||||||
|
'Etc/GMT+10',
|
||||||
|
'Etc/GMT+11',
|
||||||
|
'Etc/GMT+12',
|
||||||
|
'Etc/GMT+2',
|
||||||
|
'Etc/GMT+3',
|
||||||
|
'Etc/GMT+4',
|
||||||
|
'Etc/GMT+5',
|
||||||
|
'Etc/GMT+6',
|
||||||
|
'Etc/GMT+7',
|
||||||
|
'Etc/GMT+8',
|
||||||
|
'Etc/GMT+9',
|
||||||
|
'Etc/GMT-0',
|
||||||
|
'Etc/GMT-1',
|
||||||
|
'Etc/GMT-10',
|
||||||
|
'Etc/GMT-11',
|
||||||
|
'Etc/GMT-12',
|
||||||
|
'Etc/GMT-13',
|
||||||
|
'Etc/GMT-14',
|
||||||
|
'Etc/GMT-2',
|
||||||
|
'Etc/GMT-3',
|
||||||
|
'Etc/GMT-4',
|
||||||
|
'Etc/GMT-5',
|
||||||
|
'Etc/GMT-6',
|
||||||
|
'Etc/GMT-7',
|
||||||
|
'Etc/GMT-8',
|
||||||
|
'Etc/GMT-9',
|
||||||
|
'Etc/GMT0',
|
||||||
|
'Etc/Greenwich',
|
||||||
|
'Etc/UCT',
|
||||||
|
'Etc/Universal',
|
||||||
|
'Etc/UTC',
|
||||||
|
'Etc/Zulu',
|
||||||
|
'Europe/Belfast',
|
||||||
|
'Europe/Nicosia',
|
||||||
|
'Europe/Tiraspol',
|
||||||
|
'GB',
|
||||||
|
'GMT',
|
||||||
|
'GMT+0',
|
||||||
|
'GMT-0',
|
||||||
|
'HST',
|
||||||
|
'MET',
|
||||||
|
'Mexico/BajaNorte',
|
||||||
|
'Mexico/BajaSur',
|
||||||
|
'Mexico/General',
|
||||||
|
'MST',
|
||||||
|
'NZ',
|
||||||
|
'Pacific/Ponape',
|
||||||
|
'Pacific/Samoa',
|
||||||
|
'Pacific/Truk',
|
||||||
|
'Pacific/Yap',
|
||||||
|
'PRC',
|
||||||
|
'ROC',
|
||||||
|
'ROK',
|
||||||
|
'UCT',
|
||||||
|
'US/Alaska',
|
||||||
|
'US/Aleutian',
|
||||||
|
'US/Arizona',
|
||||||
|
'US/Central',
|
||||||
|
'US/East-Indiana',
|
||||||
|
'US/Eastern',
|
||||||
|
'US/Hawaii',
|
||||||
|
'US/Indiana-Starke',
|
||||||
|
'US/Michigan',
|
||||||
|
'US/Mountain',
|
||||||
|
'US/Pacific',
|
||||||
|
'US/Samoa',
|
||||||
|
'WET',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of PHP timezones that were supported until 5.5.9, removed in
|
||||||
|
* PHP 5.5.10 and re-introduced in PHP 5.5.17.
|
||||||
|
*
|
||||||
|
* DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) returns them,
|
||||||
|
* but they are invalid for new DateTimeZone(). Fixed in PHP 5.5.17.
|
||||||
|
* https://bugs.php.net/bug.php?id=66985
|
||||||
|
*
|
||||||
|
* Some more info here:
|
||||||
|
* http://evertpot.com/php-5-5-10-timezone-changes/
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'CST6CDT' => 'America/Chicago',
|
||||||
|
'Cuba' => 'America/Havana',
|
||||||
|
'Egypt' => 'Africa/Cairo',
|
||||||
|
'Eire' => 'Europe/Dublin',
|
||||||
|
'EST5EDT' => 'America/New_York',
|
||||||
|
'Factory' => 'UTC',
|
||||||
|
'GB-Eire' => 'Europe/London',
|
||||||
|
'GMT0' => 'UTC',
|
||||||
|
'Greenwich' => 'UTC',
|
||||||
|
'Hongkong' => 'Asia/Hong_Kong',
|
||||||
|
'Iceland' => 'Atlantic/Reykjavik',
|
||||||
|
'Iran' => 'Asia/Tehran',
|
||||||
|
'Israel' => 'Asia/Jerusalem',
|
||||||
|
'Jamaica' => 'America/Jamaica',
|
||||||
|
'Japan' => 'Asia/Tokyo',
|
||||||
|
'Kwajalein' => 'Pacific/Kwajalein',
|
||||||
|
'Libya' => 'Africa/Tripoli',
|
||||||
|
'MST7MDT' => 'America/Denver',
|
||||||
|
'Navajo' => 'America/Denver',
|
||||||
|
'NZ-CHAT' => 'Pacific/Chatham',
|
||||||
|
'Poland' => 'Europe/Warsaw',
|
||||||
|
'Portugal' => 'Europe/Lisbon',
|
||||||
|
'PST8PDT' => 'America/Los_Angeles',
|
||||||
|
'Singapore' => 'Asia/Singapore',
|
||||||
|
'Turkey' => 'Europe/Istanbul',
|
||||||
|
'Universal' => 'UTC',
|
||||||
|
'W-SU' => 'Europe/Moscow',
|
||||||
|
'Zulu' => 'UTC',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically generated timezone file.
|
||||||
|
*
|
||||||
|
* Last update: 2020-12-13T17:38:12+05:45
|
||||||
|
* Source: https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/).
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
'AUS Central Standard Time' => 'Australia/Darwin',
|
||||||
|
'AUS Eastern Standard Time' => 'Australia/Sydney',
|
||||||
|
'Afghanistan Standard Time' => 'Asia/Kabul',
|
||||||
|
'Alaskan Standard Time' => 'America/Anchorage',
|
||||||
|
'Aleutian Standard Time' => 'America/Adak',
|
||||||
|
'Altai Standard Time' => 'Asia/Barnaul',
|
||||||
|
'Arab Standard Time' => 'Asia/Riyadh',
|
||||||
|
'Arabian Standard Time' => 'Asia/Dubai',
|
||||||
|
'Arabic Standard Time' => 'Asia/Baghdad',
|
||||||
|
'Argentina Standard Time' => 'America/Buenos_Aires',
|
||||||
|
'Astrakhan Standard Time' => 'Europe/Astrakhan',
|
||||||
|
'Atlantic Standard Time' => 'America/Halifax',
|
||||||
|
'Aus Central W. Standard Time' => 'Australia/Eucla',
|
||||||
|
'Azerbaijan Standard Time' => 'Asia/Baku',
|
||||||
|
'Azores Standard Time' => 'Atlantic/Azores',
|
||||||
|
'Bahia Standard Time' => 'America/Bahia',
|
||||||
|
'Bangladesh Standard Time' => 'Asia/Dhaka',
|
||||||
|
'Belarus Standard Time' => 'Europe/Minsk',
|
||||||
|
'Bougainville Standard Time' => 'Pacific/Bougainville',
|
||||||
|
'Canada Central Standard Time' => 'America/Regina',
|
||||||
|
'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
|
||||||
|
'Caucasus Standard Time' => 'Asia/Yerevan',
|
||||||
|
'Cen. Australia Standard Time' => 'Australia/Adelaide',
|
||||||
|
'Central America Standard Time' => 'America/Guatemala',
|
||||||
|
'Central Asia Standard Time' => 'Asia/Almaty',
|
||||||
|
'Central Brazilian Standard Time' => 'America/Cuiaba',
|
||||||
|
'Central Europe Standard Time' => 'Europe/Budapest',
|
||||||
|
'Central European Standard Time' => 'Europe/Warsaw',
|
||||||
|
'Central Pacific Standard Time' => 'Pacific/Guadalcanal',
|
||||||
|
'Central Standard Time' => 'America/Chicago',
|
||||||
|
'Central Standard Time (Mexico)' => 'America/Mexico_City',
|
||||||
|
'Chatham Islands Standard Time' => 'Pacific/Chatham',
|
||||||
|
'China Standard Time' => 'Asia/Shanghai',
|
||||||
|
'Cuba Standard Time' => 'America/Havana',
|
||||||
|
'Dateline Standard Time' => 'Etc/GMT+12',
|
||||||
|
'E. Africa Standard Time' => 'Africa/Nairobi',
|
||||||
|
'E. Australia Standard Time' => 'Australia/Brisbane',
|
||||||
|
'E. Europe Standard Time' => 'Europe/Chisinau',
|
||||||
|
'E. South America Standard Time' => 'America/Sao_Paulo',
|
||||||
|
'Easter Island Standard Time' => 'Pacific/Easter',
|
||||||
|
'Eastern Standard Time' => 'America/New_York',
|
||||||
|
'Eastern Standard Time (Mexico)' => 'America/Cancun',
|
||||||
|
'Egypt Standard Time' => 'Africa/Cairo',
|
||||||
|
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
|
||||||
|
'FLE Standard Time' => 'Europe/Kiev',
|
||||||
|
'Fiji Standard Time' => 'Pacific/Fiji',
|
||||||
|
'GMT Standard Time' => 'Europe/London',
|
||||||
|
'GTB Standard Time' => 'Europe/Bucharest',
|
||||||
|
'Georgian Standard Time' => 'Asia/Tbilisi',
|
||||||
|
'Greenland Standard Time' => 'America/Godthab',
|
||||||
|
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
|
||||||
|
'Haiti Standard Time' => 'America/Port-au-Prince',
|
||||||
|
'Hawaiian Standard Time' => 'Pacific/Honolulu',
|
||||||
|
'India Standard Time' => 'Asia/Calcutta',
|
||||||
|
'Iran Standard Time' => 'Asia/Tehran',
|
||||||
|
'Israel Standard Time' => 'Asia/Jerusalem',
|
||||||
|
'Jordan Standard Time' => 'Asia/Amman',
|
||||||
|
'Kaliningrad Standard Time' => 'Europe/Kaliningrad',
|
||||||
|
'Korea Standard Time' => 'Asia/Seoul',
|
||||||
|
'Libya Standard Time' => 'Africa/Tripoli',
|
||||||
|
'Line Islands Standard Time' => 'Pacific/Kiritimati',
|
||||||
|
'Lord Howe Standard Time' => 'Australia/Lord_Howe',
|
||||||
|
'Magadan Standard Time' => 'Asia/Magadan',
|
||||||
|
'Magallanes Standard Time' => 'America/Punta_Arenas',
|
||||||
|
'Marquesas Standard Time' => 'Pacific/Marquesas',
|
||||||
|
'Mauritius Standard Time' => 'Indian/Mauritius',
|
||||||
|
'Middle East Standard Time' => 'Asia/Beirut',
|
||||||
|
'Montevideo Standard Time' => 'America/Montevideo',
|
||||||
|
'Morocco Standard Time' => 'Africa/Casablanca',
|
||||||
|
'Mountain Standard Time' => 'America/Denver',
|
||||||
|
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
|
||||||
|
'Myanmar Standard Time' => 'Asia/Rangoon',
|
||||||
|
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
|
||||||
|
'Namibia Standard Time' => 'Africa/Windhoek',
|
||||||
|
'Nepal Standard Time' => 'Asia/Katmandu',
|
||||||
|
'New Zealand Standard Time' => 'Pacific/Auckland',
|
||||||
|
'Newfoundland Standard Time' => 'America/St_Johns',
|
||||||
|
'Norfolk Standard Time' => 'Pacific/Norfolk',
|
||||||
|
'North Asia East Standard Time' => 'Asia/Irkutsk',
|
||||||
|
'North Asia Standard Time' => 'Asia/Krasnoyarsk',
|
||||||
|
'North Korea Standard Time' => 'Asia/Pyongyang',
|
||||||
|
'Omsk Standard Time' => 'Asia/Omsk',
|
||||||
|
'Pacific SA Standard Time' => 'America/Santiago',
|
||||||
|
'Pacific Standard Time' => 'America/Los_Angeles',
|
||||||
|
'Pacific Standard Time (Mexico)' => 'America/Tijuana',
|
||||||
|
'Pakistan Standard Time' => 'Asia/Karachi',
|
||||||
|
'Paraguay Standard Time' => 'America/Asuncion',
|
||||||
|
'Qyzylorda Standard Time' => 'Asia/Qyzylorda',
|
||||||
|
'Romance Standard Time' => 'Europe/Paris',
|
||||||
|
'Russia Time Zone 10' => 'Asia/Srednekolymsk',
|
||||||
|
'Russia Time Zone 11' => 'Asia/Kamchatka',
|
||||||
|
'Russia Time Zone 3' => 'Europe/Samara',
|
||||||
|
'Russian Standard Time' => 'Europe/Moscow',
|
||||||
|
'SA Eastern Standard Time' => 'America/Cayenne',
|
||||||
|
'SA Pacific Standard Time' => 'America/Bogota',
|
||||||
|
'SA Western Standard Time' => 'America/La_Paz',
|
||||||
|
'SE Asia Standard Time' => 'Asia/Bangkok',
|
||||||
|
'Saint Pierre Standard Time' => 'America/Miquelon',
|
||||||
|
'Sakhalin Standard Time' => 'Asia/Sakhalin',
|
||||||
|
'Samoa Standard Time' => 'Pacific/Apia',
|
||||||
|
'Sao Tome Standard Time' => 'Africa/Sao_Tome',
|
||||||
|
'Saratov Standard Time' => 'Europe/Saratov',
|
||||||
|
'Singapore Standard Time' => 'Asia/Singapore',
|
||||||
|
'South Africa Standard Time' => 'Africa/Johannesburg',
|
||||||
|
'Sri Lanka Standard Time' => 'Asia/Colombo',
|
||||||
|
'Sudan Standard Time' => 'Africa/Khartoum',
|
||||||
|
'Syria Standard Time' => 'Asia/Damascus',
|
||||||
|
'Taipei Standard Time' => 'Asia/Taipei',
|
||||||
|
'Tasmania Standard Time' => 'Australia/Hobart',
|
||||||
|
'Tocantins Standard Time' => 'America/Araguaina',
|
||||||
|
'Tokyo Standard Time' => 'Asia/Tokyo',
|
||||||
|
'Tomsk Standard Time' => 'Asia/Tomsk',
|
||||||
|
'Tonga Standard Time' => 'Pacific/Tongatapu',
|
||||||
|
'Transbaikal Standard Time' => 'Asia/Chita',
|
||||||
|
'Turkey Standard Time' => 'Europe/Istanbul',
|
||||||
|
'Turks And Caicos Standard Time' => 'America/Grand_Turk',
|
||||||
|
'US Eastern Standard Time' => 'America/Indianapolis',
|
||||||
|
'US Mountain Standard Time' => 'America/Phoenix',
|
||||||
|
'UTC' => 'Etc/GMT',
|
||||||
|
'UTC+12' => 'Etc/GMT-12',
|
||||||
|
'UTC+13' => 'Etc/GMT-13',
|
||||||
|
'UTC-02' => 'Etc/GMT+2',
|
||||||
|
'UTC-08' => 'Etc/GMT+8',
|
||||||
|
'UTC-09' => 'Etc/GMT+9',
|
||||||
|
'UTC-11' => 'Etc/GMT+11',
|
||||||
|
'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar',
|
||||||
|
'Venezuela Standard Time' => 'America/Caracas',
|
||||||
|
'Vladivostok Standard Time' => 'Asia/Vladivostok',
|
||||||
|
'Volgograd Standard Time' => 'Europe/Volgograd',
|
||||||
|
'W. Australia Standard Time' => 'Australia/Perth',
|
||||||
|
'W. Central Africa Standard Time' => 'Africa/Lagos',
|
||||||
|
'W. Europe Standard Time' => 'Europe/Berlin',
|
||||||
|
'W. Mongolia Standard Time' => 'Asia/Hovd',
|
||||||
|
'West Asia Standard Time' => 'Asia/Tashkent',
|
||||||
|
'West Bank Standard Time' => 'Asia/Hebron',
|
||||||
|
'West Pacific Standard Time' => 'Pacific/Port_Moresby',
|
||||||
|
'Yakutsk Standard Time' => 'Asia/Yakutsk',
|
||||||
|
'Yukon Standard Time' => 'America/Whitehorse',
|
||||||
|
];
|
||||||
118
snappymail/v/0.0.0/app/libraries/Sabre/Xml/ContextStackTrait.php
Normal file
118
snappymail/v/0.0.0/app/libraries/Sabre/Xml/ContextStackTrait.php
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context Stack.
|
||||||
|
*
|
||||||
|
* The Context maintains information about a document during either reading or
|
||||||
|
* writing.
|
||||||
|
*
|
||||||
|
* During this process, it may be necessary to override this context
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This trait allows easy access to the context, and allows the end-user to
|
||||||
|
* override its settings for document fragments, and easily restore it again
|
||||||
|
* later.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
trait ContextStackTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This is the element map. It contains a list of XML elements (in clark
|
||||||
|
* notation) as keys and PHP class names as values.
|
||||||
|
*
|
||||||
|
* The PHP class names must implement Sabre\Xml\Element.
|
||||||
|
*
|
||||||
|
* Values may also be a callable. In that case the function will be called
|
||||||
|
* directly.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $elementMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contextUri pointing to the document being parsed / written.
|
||||||
|
* This uri may be used to resolve relative urls that may appear in the
|
||||||
|
* document.
|
||||||
|
*
|
||||||
|
* The reader and writer don't use this property, but as it's an extremely
|
||||||
|
* common use-case for parsing XML documents, it's added here as a
|
||||||
|
* convenience.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $contextUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a list of namespaces that you want to give default prefixes.
|
||||||
|
*
|
||||||
|
* You must make sure you create this entire list before starting to write.
|
||||||
|
* They should be registered on the root element.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $namespaceMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a list of custom serializers for specific classes.
|
||||||
|
*
|
||||||
|
* The writer may use this if you attempt to serialize an object with a
|
||||||
|
* class that does not implement XmlSerializable.
|
||||||
|
*
|
||||||
|
* Instead it will look at this classmap to see if there is a custom
|
||||||
|
* serializer here. This is useful if you don't want your value objects
|
||||||
|
* to be responsible for serializing themselves.
|
||||||
|
*
|
||||||
|
* The keys in this classmap need to be fully qualified PHP class names,
|
||||||
|
* the values must be callbacks. The callbacks take two arguments. The
|
||||||
|
* writer class, and the value that must be written.
|
||||||
|
*
|
||||||
|
* function (Writer $writer, object $value)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $classMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backups of previous contexts.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $contextStack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new "context".
|
||||||
|
*
|
||||||
|
* This allows you to safely modify the elementMap, contextUri or
|
||||||
|
* namespaceMap. After you're done, you can restore the old data again
|
||||||
|
* with popContext.
|
||||||
|
*/
|
||||||
|
public function pushContext()
|
||||||
|
{
|
||||||
|
$this->contextStack[] = [
|
||||||
|
$this->elementMap,
|
||||||
|
$this->contextUri,
|
||||||
|
$this->namespaceMap,
|
||||||
|
$this->classMap,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the previous "context".
|
||||||
|
*/
|
||||||
|
public function popContext()
|
||||||
|
{
|
||||||
|
list(
|
||||||
|
$this->elementMap,
|
||||||
|
$this->contextUri,
|
||||||
|
$this->namespaceMap,
|
||||||
|
$this->classMap
|
||||||
|
) = array_pop($this->contextStack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,359 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Deserializer;
|
||||||
|
|
||||||
|
use Sabre\Xml\Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a number of 'deserializer' helper functions.
|
||||||
|
* These can be used to easily specify custom deserializers for specific
|
||||||
|
* XML elements.
|
||||||
|
*
|
||||||
|
* You can either use these functions from within the $elementMap in the
|
||||||
|
* Service or Reader class, or you can call them from within your own
|
||||||
|
* deserializer functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'keyValue' deserializer parses all child elements, and outputs them as
|
||||||
|
* a "key=>value" array.
|
||||||
|
*
|
||||||
|
* For example, keyvalue will parse:
|
||||||
|
*
|
||||||
|
* <?xml version="1.0"?>
|
||||||
|
* <s:root xmlns:s="http://sabredav.org/ns">
|
||||||
|
* <s:elem1>value1</s:elem1>
|
||||||
|
* <s:elem2>value2</s:elem2>
|
||||||
|
* <s:elem3 />
|
||||||
|
* </s:root>
|
||||||
|
*
|
||||||
|
* Into:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "{http://sabredav.org/ns}elem1" => "value1",
|
||||||
|
* "{http://sabredav.org/ns}elem2" => "value2",
|
||||||
|
* "{http://sabredav.org/ns}elem3" => null,
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* If you specify the 'namespace' argument, the deserializer will remove
|
||||||
|
* the namespaces of the keys that match that namespace.
|
||||||
|
*
|
||||||
|
* For example, if you call keyValue like this:
|
||||||
|
*
|
||||||
|
* keyValue($reader, 'http://sabredav.org/ns')
|
||||||
|
*
|
||||||
|
* it's output will instead be:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "elem1" => "value1",
|
||||||
|
* "elem2" => "value2",
|
||||||
|
* "elem3" => null,
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* Attributes will be removed from the top-level elements. If elements with
|
||||||
|
* the same name appear twice in the list, only the last one will be kept.
|
||||||
|
*/
|
||||||
|
function keyValue(Reader $reader, string $namespace = null): array
|
||||||
|
{
|
||||||
|
// If there's no children, we don't do anything.
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$reader->read()) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Reader::END_ELEMENT === $reader->nodeType) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (Reader::ELEMENT === $reader->nodeType) {
|
||||||
|
if (null !== $namespace && $reader->namespaceURI === $namespace) {
|
||||||
|
$values[$reader->localName] = $reader->parseCurrentElement()['value'];
|
||||||
|
} else {
|
||||||
|
$clark = $reader->getClark();
|
||||||
|
$values[$clark] = $reader->parseCurrentElement()['value'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$reader->read()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (Reader::END_ELEMENT !== $reader->nodeType);
|
||||||
|
|
||||||
|
$reader->read();
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'enum' deserializer parses elements into a simple list
|
||||||
|
* without values or attributes.
|
||||||
|
*
|
||||||
|
* For example, Elements will parse:
|
||||||
|
*
|
||||||
|
* <?xml version="1.0"? >
|
||||||
|
* <s:root xmlns:s="http://sabredav.org/ns">
|
||||||
|
* <s:elem1 />
|
||||||
|
* <s:elem2 />
|
||||||
|
* <s:elem3 />
|
||||||
|
* <s:elem4>content</s:elem4>
|
||||||
|
* <s:elem5 attr="val" />
|
||||||
|
* </s:root>
|
||||||
|
*
|
||||||
|
* Into:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "{http://sabredav.org/ns}elem1",
|
||||||
|
* "{http://sabredav.org/ns}elem2",
|
||||||
|
* "{http://sabredav.org/ns}elem3",
|
||||||
|
* "{http://sabredav.org/ns}elem4",
|
||||||
|
* "{http://sabredav.org/ns}elem5",
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* This is useful for 'enum'-like structures.
|
||||||
|
*
|
||||||
|
* If the $namespace argument is specified, it will strip the namespace
|
||||||
|
* for all elements that match that.
|
||||||
|
*
|
||||||
|
* For example,
|
||||||
|
*
|
||||||
|
* enum($reader, 'http://sabredav.org/ns')
|
||||||
|
*
|
||||||
|
* would return:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "elem1",
|
||||||
|
* "elem2",
|
||||||
|
* "elem3",
|
||||||
|
* "elem4",
|
||||||
|
* "elem5",
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
function enum(Reader $reader, string $namespace = null): array
|
||||||
|
{
|
||||||
|
// If there's no children, we don't do anything.
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!$reader->read()) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Reader::END_ELEMENT === $reader->nodeType) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$currentDepth = $reader->depth;
|
||||||
|
|
||||||
|
$values = [];
|
||||||
|
do {
|
||||||
|
if (Reader::ELEMENT !== $reader->nodeType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
|
||||||
|
$values[] = $reader->localName;
|
||||||
|
} else {
|
||||||
|
$values[] = (string) $reader->getClark();
|
||||||
|
}
|
||||||
|
} while ($reader->depth >= $currentDepth && $reader->next());
|
||||||
|
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The valueObject deserializer turns an xml element into a PHP object of
|
||||||
|
* a specific class.
|
||||||
|
*
|
||||||
|
* This is primarily used by the mapValueObject function from the Service
|
||||||
|
* class, but it can also easily be used for more specific situations.
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
function valueObject(Reader $reader, string $className, string $namespace)
|
||||||
|
{
|
||||||
|
$valueObject = new $className();
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return $valueObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultProperties = get_class_vars($className);
|
||||||
|
|
||||||
|
$reader->read();
|
||||||
|
do {
|
||||||
|
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
|
||||||
|
if (property_exists($valueObject, $reader->localName)) {
|
||||||
|
if (is_array($defaultProperties[$reader->localName])) {
|
||||||
|
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
|
||||||
|
} else {
|
||||||
|
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ignore property
|
||||||
|
$reader->next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$reader->read()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (Reader::END_ELEMENT !== $reader->nodeType);
|
||||||
|
|
||||||
|
$reader->read();
|
||||||
|
|
||||||
|
return $valueObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This deserializer helps you deserialize xml structures that look like
|
||||||
|
* this:.
|
||||||
|
*
|
||||||
|
* <collection>
|
||||||
|
* <item>...</item>
|
||||||
|
* <item>...</item>
|
||||||
|
* <item>...</item>
|
||||||
|
* </collection>
|
||||||
|
*
|
||||||
|
* Many XML documents use patterns like that, and this deserializer
|
||||||
|
* allow you to get all the 'items' as an array.
|
||||||
|
*
|
||||||
|
* In that previous example, you would register the deserializer as such:
|
||||||
|
*
|
||||||
|
* $reader->elementMap['{}collection'] = function($reader) {
|
||||||
|
* return repeatingElements($reader, '{}item');
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* The repeatingElements deserializer simply returns everything as an array.
|
||||||
|
*
|
||||||
|
* $childElementName must either be a a clark-notation element name, or if no
|
||||||
|
* namespace is used, the bare element name.
|
||||||
|
*/
|
||||||
|
function repeatingElements(Reader $reader, string $childElementName): array
|
||||||
|
{
|
||||||
|
if ('{' !== $childElementName[0]) {
|
||||||
|
$childElementName = '{}'.$childElementName;
|
||||||
|
}
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($reader->parseGetElements() as $element) {
|
||||||
|
if ($element['name'] === $childElementName) {
|
||||||
|
$result[] = $element['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This deserializer helps you to deserialize structures which contain mixed content like this:.
|
||||||
|
*
|
||||||
|
* <p>some text <extref>and a inline tag</extref>and even more text</p>
|
||||||
|
*
|
||||||
|
* The above example will return
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* 'some text',
|
||||||
|
* [
|
||||||
|
* 'name' => '{}extref',
|
||||||
|
* 'value' => 'and a inline tag',
|
||||||
|
* 'attributes' => []
|
||||||
|
* ],
|
||||||
|
* 'and even more text'
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* In strict XML documents you wont find this kind of markup but in html this is a quite common pattern.
|
||||||
|
*/
|
||||||
|
function mixedContent(Reader $reader): array
|
||||||
|
{
|
||||||
|
// If there's no children, we don't do anything.
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousDepth = $reader->depth;
|
||||||
|
|
||||||
|
$content = [];
|
||||||
|
$reader->read();
|
||||||
|
while (true) {
|
||||||
|
if (Reader::ELEMENT == $reader->nodeType) {
|
||||||
|
$content[] = $reader->parseCurrentElement();
|
||||||
|
} elseif ($reader->depth >= $previousDepth && in_array($reader->nodeType, [Reader::TEXT, Reader::CDATA, Reader::WHITESPACE])) {
|
||||||
|
$content[] = $reader->value;
|
||||||
|
$reader->read();
|
||||||
|
} elseif (Reader::END_ELEMENT == $reader->nodeType) {
|
||||||
|
// Ensuring we are moving the cursor after the end element.
|
||||||
|
$reader->read();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
$reader->read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functionCaller deserializer turns an xml element into whatever your callable return.
|
||||||
|
*
|
||||||
|
* You can use, e.g., a named constructor (factory method) to create an object using
|
||||||
|
* this function.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function functionCaller(Reader $reader, callable $func, string $namespace)
|
||||||
|
{
|
||||||
|
if ($reader->isEmptyElement) {
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$funcArgs = [];
|
||||||
|
$func = is_string($func) && false !== strpos($func, '::') ? explode('::', $func) : $func;
|
||||||
|
$ref = is_array($func) ? new \ReflectionMethod($func[0], $func[1]) : new \ReflectionFunction($func);
|
||||||
|
foreach ($ref->getParameters() as $parameter) {
|
||||||
|
$funcArgs[$parameter->getName()] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reader->read();
|
||||||
|
do {
|
||||||
|
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
|
||||||
|
if (array_key_exists($reader->localName, $funcArgs)) {
|
||||||
|
$funcArgs[$reader->localName] = $reader->parseCurrentElement()['value'];
|
||||||
|
} else {
|
||||||
|
// Ignore property
|
||||||
|
$reader->next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$reader->read();
|
||||||
|
}
|
||||||
|
} while (Reader::END_ELEMENT !== $reader->nodeType);
|
||||||
|
$reader->read();
|
||||||
|
|
||||||
|
return $func(...array_values($funcArgs));
|
||||||
|
}
|
||||||
22
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element.php
Normal file
22
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the XML element interface.
|
||||||
|
*
|
||||||
|
* Elements are responsible for serializing and deserializing part of an XML
|
||||||
|
* document into PHP values.
|
||||||
|
*
|
||||||
|
* It combines XmlSerializable and XmlDeserializable into one logical class
|
||||||
|
* that does both.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
interface Element extends XmlSerializable, XmlDeserializable
|
||||||
|
{
|
||||||
|
}
|
||||||
84
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Base.php
Normal file
84
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Base.php
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Base XML element is the standard parser & generator that's used by the
|
||||||
|
* XML reader and writer.
|
||||||
|
*
|
||||||
|
* It spits out a simple PHP array structure during deserialization, that can
|
||||||
|
* also be directly injected back into Writer::write.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Base implements Xml\Element
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* PHP value to serialize.
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct($value = null)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$writer->write($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deserialize method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* Important note 2: You are responsible for advancing the reader to the
|
||||||
|
* next element. Not doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(Xml\Reader $reader)
|
||||||
|
{
|
||||||
|
$subTree = $reader->parseInnerTree();
|
||||||
|
|
||||||
|
return $subTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Cdata.php
Normal file
59
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Cdata.php
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CDATA element.
|
||||||
|
*
|
||||||
|
* This element allows you to easily inject CDATA.
|
||||||
|
*
|
||||||
|
* Note that we strongly recommend avoiding CDATA nodes, unless you definitely
|
||||||
|
* know what you're doing, or you're working with unchangeable systems that
|
||||||
|
* require CDATA.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Cdata implements Xml\XmlSerializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* CDATA element value.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(string $value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$writer->writeCData($this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Elements.php
Normal file
100
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Elements.php
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
use Sabre\Xml\Deserializer;
|
||||||
|
use Sabre\Xml\Serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'Elements' is a simple list of elements, without values or attributes.
|
||||||
|
* For example, Elements will parse:.
|
||||||
|
*
|
||||||
|
* <?xml version="1.0"?>
|
||||||
|
* <s:root xmlns:s="http://sabredav.org/ns">
|
||||||
|
* <s:elem1 />
|
||||||
|
* <s:elem2 />
|
||||||
|
* <s:elem3 />
|
||||||
|
* <s:elem4>content</s:elem4>
|
||||||
|
* <s:elem5 attr="val" />
|
||||||
|
* </s:root>
|
||||||
|
*
|
||||||
|
* Into:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "{http://sabredav.org/ns}elem1",
|
||||||
|
* "{http://sabredav.org/ns}elem2",
|
||||||
|
* "{http://sabredav.org/ns}elem3",
|
||||||
|
* "{http://sabredav.org/ns}elem4",
|
||||||
|
* "{http://sabredav.org/ns}elem5",
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Elements implements Xml\Element
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Value to serialize.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(array $value = [])
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
Serializer\enum($writer, $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deserialize method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* Important note 2: You are responsible for advancing the reader to the
|
||||||
|
* next element. Not doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseSubTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(Xml\Reader $reader)
|
||||||
|
{
|
||||||
|
return Deserializer\enum($reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/KeyValue.php
Normal file
100
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/KeyValue.php
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
use Sabre\Xml\Deserializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'KeyValue' parses out all child elements from a single node, and outputs a
|
||||||
|
* key=>value struct.
|
||||||
|
*
|
||||||
|
* Attributes will be removed, and duplicate child elements are discarded.
|
||||||
|
* Complex values within the elements will be parsed by the 'standard' parser.
|
||||||
|
*
|
||||||
|
* For example, KeyValue will parse:
|
||||||
|
*
|
||||||
|
* <?xml version="1.0"?>
|
||||||
|
* <s:root xmlns:s="http://sabredav.org/ns">
|
||||||
|
* <s:elem1>value1</s:elem1>
|
||||||
|
* <s:elem2>value2</s:elem2>
|
||||||
|
* <s:elem3 />
|
||||||
|
* </s:root>
|
||||||
|
*
|
||||||
|
* Into:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "{http://sabredav.org/ns}elem1" => "value1",
|
||||||
|
* "{http://sabredav.org/ns}elem2" => "value2",
|
||||||
|
* "{http://sabredav.org/ns}elem3" => null,
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class KeyValue implements Xml\Element
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Value to serialize.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(array $value = [])
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$writer->write($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deserialize method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* Important note 2: You are responsible for advancing the reader to the
|
||||||
|
* next element. Not doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(Xml\Reader $reader)
|
||||||
|
{
|
||||||
|
return Deserializer\keyValue($reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Uri.php
Normal file
99
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Element/Uri.php
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uri element.
|
||||||
|
*
|
||||||
|
* This represents a single uri. An example of how this may be encoded:
|
||||||
|
*
|
||||||
|
* <link>/foo/bar</link>
|
||||||
|
* <d:href xmlns:d="DAV:">http://example.org/hi</d:href>
|
||||||
|
*
|
||||||
|
* If the uri is relative, it will be automatically expanded to an absolute
|
||||||
|
* url during writing and reading, if the contextUri property is set on the
|
||||||
|
* reader and/or writer.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Uri implements Xml\Element
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Uri element value.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Xml\Writer $writer)
|
||||||
|
{
|
||||||
|
$writer->text(
|
||||||
|
\Sabre\Uri\resolve(
|
||||||
|
$writer->contextUri,
|
||||||
|
$this->value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* Important note 2: You are responsible for advancing the reader to the
|
||||||
|
* next element. Not doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseSubTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(Xml\Reader $reader)
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
\Sabre\Uri\resolve(
|
||||||
|
(string) $reader->contextUri,
|
||||||
|
$reader->readText()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Element;
|
||||||
|
|
||||||
|
use Sabre\Xml\Element;
|
||||||
|
use Sabre\Xml\Reader;
|
||||||
|
use Sabre\Xml\Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The XmlFragment element allows you to extract a portion of your xml tree,
|
||||||
|
* and get a well-formed xml string.
|
||||||
|
*
|
||||||
|
* This goes a bit beyond `innerXml` and friends, as we'll also match all the
|
||||||
|
* correct namespaces.
|
||||||
|
*
|
||||||
|
* Please note that the XML fragment:
|
||||||
|
*
|
||||||
|
* 1. Will not have an <?xml declaration.
|
||||||
|
* 2. Or a DTD
|
||||||
|
* 3. It will have all the relevant xmlns attributes.
|
||||||
|
* 4. It may not have a root element.
|
||||||
|
*/
|
||||||
|
class XmlFragment implements Element
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The inner XML value.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(string $xml)
|
||||||
|
{
|
||||||
|
$this->xml = $xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the inner XML document.
|
||||||
|
*/
|
||||||
|
public function getXml(): string
|
||||||
|
{
|
||||||
|
return $this->xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The xmlSerialize method is called during xml writing.
|
||||||
|
*
|
||||||
|
* Use the $writer argument to write its own xml serialization.
|
||||||
|
*
|
||||||
|
* An important note: do _not_ create a parent element. Any element
|
||||||
|
* implementing XmlSerializable should only ever write what's considered
|
||||||
|
* its 'inner xml'.
|
||||||
|
*
|
||||||
|
* The parent of the current element is responsible for writing a
|
||||||
|
* containing element.
|
||||||
|
*
|
||||||
|
* This allows serializers to be re-used for different element names.
|
||||||
|
*
|
||||||
|
* If you are opening new elements, you must also close them again.
|
||||||
|
*/
|
||||||
|
public function xmlSerialize(Writer $writer)
|
||||||
|
{
|
||||||
|
$reader = new Reader();
|
||||||
|
|
||||||
|
// Wrapping the xml in a container, so root-less values can still be
|
||||||
|
// parsed.
|
||||||
|
$xml = <<<XML
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment>
|
||||||
|
XML;
|
||||||
|
|
||||||
|
$reader->xml($xml);
|
||||||
|
|
||||||
|
while ($reader->read()) {
|
||||||
|
if ($reader->depth < 1) {
|
||||||
|
// Skipping the root node.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($reader->nodeType) {
|
||||||
|
case Reader::ELEMENT:
|
||||||
|
$writer->startElement(
|
||||||
|
(string) $reader->getClark()
|
||||||
|
);
|
||||||
|
$empty = $reader->isEmptyElement;
|
||||||
|
while ($reader->moveToNextAttribute()) {
|
||||||
|
switch ($reader->namespaceURI) {
|
||||||
|
case '':
|
||||||
|
$writer->writeAttribute($reader->localName, $reader->value);
|
||||||
|
break;
|
||||||
|
case 'http://www.w3.org/2000/xmlns/':
|
||||||
|
// Skip namespace declarations
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$writer->writeAttribute((string) $reader->getClark(), $reader->value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($empty) {
|
||||||
|
$writer->endElement();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Reader::CDATA:
|
||||||
|
case Reader::TEXT:
|
||||||
|
$writer->text(
|
||||||
|
$reader->value
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Reader::END_ELEMENT:
|
||||||
|
$writer->endElement();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deserialize method is called during xml parsing.
|
||||||
|
*
|
||||||
|
* This method is called statically, this is because in theory this method
|
||||||
|
* may be used as a type of constructor, or factory method.
|
||||||
|
*
|
||||||
|
* Often you want to return an instance of the current class, but you are
|
||||||
|
* free to return other data as well.
|
||||||
|
*
|
||||||
|
* You are responsible for advancing the reader to the next element. Not
|
||||||
|
* doing anything will result in a never-ending loop.
|
||||||
|
*
|
||||||
|
* If you just want to skip parsing for this element altogether, you can
|
||||||
|
* just call $reader->next();
|
||||||
|
*
|
||||||
|
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||||
|
* the next element.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function xmlDeserialize(Reader $reader)
|
||||||
|
{
|
||||||
|
$result = new self($reader->readInnerXml());
|
||||||
|
$reader->next();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml;
|
||||||
|
|
||||||
|
use LibXMLError;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown when the Reader runs into a parsing error.
|
||||||
|
*
|
||||||
|
* This exception effectively wraps 1 or more LibXMLError objects.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class LibXMLException extends ParseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The error list.
|
||||||
|
*
|
||||||
|
* @var \LibXMLError[]
|
||||||
|
*/
|
||||||
|
protected $errors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the exception.
|
||||||
|
*
|
||||||
|
* You should pass a list of LibXMLError objects in its constructor.
|
||||||
|
*
|
||||||
|
* @param LibXMLError[] $errors
|
||||||
|
* @param Throwable $previousException
|
||||||
|
*/
|
||||||
|
public function __construct(array $errors, int $code = 0, Throwable $previousException = null)
|
||||||
|
{
|
||||||
|
$this->errors = $errors;
|
||||||
|
parent::__construct($errors[0]->message.' on line '.$errors[0]->line.', column '.$errors[0]->column, $code, $previousException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LibXML errors.
|
||||||
|
*/
|
||||||
|
public function getErrors(): array
|
||||||
|
{
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a base exception for any exception related to parsing xml files.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class ParseException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
307
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Reader.php
Normal file
307
snappymail/v/0.0.0/app/libraries/Sabre/Xml/Reader.php
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml;
|
||||||
|
|
||||||
|
use XMLReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Reader class expands upon PHP's built-in XMLReader.
|
||||||
|
*
|
||||||
|
* The intended usage, is to assign certain XML elements to PHP classes. These
|
||||||
|
* need to be registered using the $elementMap public property.
|
||||||
|
*
|
||||||
|
* After this is done, a single call to parse() will parse the entire document,
|
||||||
|
* and delegate sub-sections of the document to element classes.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
|
||||||
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
|
*/
|
||||||
|
class Reader extends XMLReader
|
||||||
|
{
|
||||||
|
use ContextStackTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current nodename in clark-notation.
|
||||||
|
*
|
||||||
|
* For example: "{http://www.w3.org/2005/Atom}feed".
|
||||||
|
* Or if no namespace is defined: "{}feed".
|
||||||
|
*
|
||||||
|
* This method returns null if we're not currently on an element.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getClark()
|
||||||
|
{
|
||||||
|
if (!$this->localName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '{'.$this->namespaceURI.'}'.$this->localName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the entire document.
|
||||||
|
*
|
||||||
|
* This function returns an array with the following three elements:
|
||||||
|
* * name - The root element name.
|
||||||
|
* * value - The value for the root element.
|
||||||
|
* * attributes - An array of attributes.
|
||||||
|
*
|
||||||
|
* This function will also disable the standard libxml error handler (which
|
||||||
|
* usually just results in PHP errors), and throw exceptions instead.
|
||||||
|
*/
|
||||||
|
public function parse(): array
|
||||||
|
{
|
||||||
|
$previousEntityState = null;
|
||||||
|
$shouldCallLibxmlDisableEntityLoader = (\LIBXML_VERSION < 20900);
|
||||||
|
if ($shouldCallLibxmlDisableEntityLoader) {
|
||||||
|
$previousEntityState = libxml_disable_entity_loader(true);
|
||||||
|
}
|
||||||
|
$previousSetting = libxml_use_internal_errors(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (self::ELEMENT !== $this->nodeType) {
|
||||||
|
if (!$this->read()) {
|
||||||
|
$errors = libxml_get_errors();
|
||||||
|
libxml_clear_errors();
|
||||||
|
if ($errors) {
|
||||||
|
throw new LibXMLException($errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result = $this->parseCurrentElement();
|
||||||
|
|
||||||
|
// last line of defense in case errors did occur above
|
||||||
|
$errors = libxml_get_errors();
|
||||||
|
libxml_clear_errors();
|
||||||
|
if ($errors) {
|
||||||
|
throw new LibXMLException($errors);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
libxml_use_internal_errors($previousSetting);
|
||||||
|
if ($shouldCallLibxmlDisableEntityLoader) {
|
||||||
|
libxml_disable_entity_loader($previousEntityState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseGetElements parses everything in the current sub-tree,
|
||||||
|
* and returns an array of elements.
|
||||||
|
*
|
||||||
|
* Each element has a 'name', 'value' and 'attributes' key.
|
||||||
|
*
|
||||||
|
* If the element didn't contain sub-elements, an empty array is always
|
||||||
|
* returned. If there was any text inside the element, it will be
|
||||||
|
* discarded.
|
||||||
|
*
|
||||||
|
* If the $elementMap argument is specified, the existing elementMap will
|
||||||
|
* be overridden while parsing the tree, and restored after this process.
|
||||||
|
*/
|
||||||
|
public function parseGetElements(array $elementMap = null): array
|
||||||
|
{
|
||||||
|
$result = $this->parseInnerTree($elementMap);
|
||||||
|
if (!is_array($result)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses all elements below the current element.
|
||||||
|
*
|
||||||
|
* This method will return a string if this was a text-node, or an array if
|
||||||
|
* there were sub-elements.
|
||||||
|
*
|
||||||
|
* If there's both text and sub-elements, the text will be discarded.
|
||||||
|
*
|
||||||
|
* If the $elementMap argument is specified, the existing elementMap will
|
||||||
|
* be overridden while parsing the tree, and restored after this process.
|
||||||
|
*
|
||||||
|
* @return array|string|null
|
||||||
|
*/
|
||||||
|
public function parseInnerTree(array $elementMap = null)
|
||||||
|
{
|
||||||
|
$text = null;
|
||||||
|
$elements = [];
|
||||||
|
|
||||||
|
if (self::ELEMENT === $this->nodeType && $this->isEmptyElement) {
|
||||||
|
// Easy!
|
||||||
|
$this->next();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($elementMap)) {
|
||||||
|
$this->pushContext();
|
||||||
|
$this->elementMap = $elementMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!$this->read()) {
|
||||||
|
$errors = libxml_get_errors();
|
||||||
|
libxml_clear_errors();
|
||||||
|
if ($errors) {
|
||||||
|
throw new LibXMLException($errors);
|
||||||
|
}
|
||||||
|
throw new ParseException('This should never happen (famous last words)');
|
||||||
|
}
|
||||||
|
|
||||||
|
$keepOnParsing = true;
|
||||||
|
|
||||||
|
while ($keepOnParsing) {
|
||||||
|
if (!$this->isValid()) {
|
||||||
|
$errors = libxml_get_errors();
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
libxml_clear_errors();
|
||||||
|
throw new LibXMLException($errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->nodeType) {
|
||||||
|
case self::ELEMENT:
|
||||||
|
$elements[] = $this->parseCurrentElement();
|
||||||
|
break;
|
||||||
|
case self::TEXT:
|
||||||
|
case self::CDATA:
|
||||||
|
$text .= $this->value;
|
||||||
|
$this->read();
|
||||||
|
break;
|
||||||
|
case self::END_ELEMENT:
|
||||||
|
// Ensuring we are moving the cursor after the end element.
|
||||||
|
$this->read();
|
||||||
|
$keepOnParsing = false;
|
||||||
|
break;
|
||||||
|
case self::NONE:
|
||||||
|
throw new ParseException('We hit the end of the document prematurely. This likely means that some parser "eats" too many elements. Do not attempt to continue parsing.');
|
||||||
|
default:
|
||||||
|
// Advance to the next element
|
||||||
|
$this->read();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!is_null($elementMap)) {
|
||||||
|
$this->popContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $elements ? $elements : $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all text below the current element, and returns this as a string.
|
||||||
|
*/
|
||||||
|
public function readText(): string
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
$previousDepth = $this->depth;
|
||||||
|
|
||||||
|
while ($this->read() && $this->depth != $previousDepth) {
|
||||||
|
if (in_array($this->nodeType, [XMLReader::TEXT, XMLReader::CDATA, XMLReader::WHITESPACE])) {
|
||||||
|
$result .= $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the current XML element.
|
||||||
|
*
|
||||||
|
* This method returns arn array with 3 properties:
|
||||||
|
* * name - A clark-notation XML element name.
|
||||||
|
* * value - The parsed value.
|
||||||
|
* * attributes - A key-value list of attributes.
|
||||||
|
*/
|
||||||
|
public function parseCurrentElement(): array
|
||||||
|
{
|
||||||
|
$name = $this->getClark();
|
||||||
|
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
if ($this->hasAttributes) {
|
||||||
|
$attributes = $this->parseAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = call_user_func(
|
||||||
|
$this->getDeserializerForElementName((string) $name),
|
||||||
|
$this
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $name,
|
||||||
|
'value' => $value,
|
||||||
|
'attributes' => $attributes,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs all the attributes from the current element, and returns them as a
|
||||||
|
* key-value array.
|
||||||
|
*
|
||||||
|
* If the attributes are part of the same namespace, they will simply be
|
||||||
|
* short keys. If they are defined on a different namespace, the attribute
|
||||||
|
* name will be returned in clark-notation.
|
||||||
|
*/
|
||||||
|
public function parseAttributes(): array
|
||||||
|
{
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
while ($this->moveToNextAttribute()) {
|
||||||
|
if ($this->namespaceURI) {
|
||||||
|
// Ignoring 'xmlns', it doesn't make any sense.
|
||||||
|
if ('http://www.w3.org/2000/xmlns/' === $this->namespaceURI) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->getClark();
|
||||||
|
$attributes[$name] = $this->value;
|
||||||
|
} else {
|
||||||
|
$attributes[$this->localName] = $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->moveToElement();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the function that should be used to parse the element identified
|
||||||
|
* by its clark-notation name.
|
||||||
|
*/
|
||||||
|
public function getDeserializerForElementName(string $name): callable
|
||||||
|
{
|
||||||
|
if (!array_key_exists($name, $this->elementMap)) {
|
||||||
|
if ('{}' == substr($name, 0, 2) && array_key_exists(substr($name, 2), $this->elementMap)) {
|
||||||
|
$name = substr($name, 2);
|
||||||
|
} else {
|
||||||
|
return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$deserializer = $this->elementMap[$name];
|
||||||
|
if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
|
||||||
|
return [$deserializer, 'xmlDeserialize'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_callable($deserializer)) {
|
||||||
|
return $deserializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = gettype($deserializer);
|
||||||
|
if ('string' === $type) {
|
||||||
|
$type .= ' ('.$deserializer.')';
|
||||||
|
} elseif ('object' === $type) {
|
||||||
|
$type .= ' ('.get_class($deserializer).')';
|
||||||
|
}
|
||||||
|
throw new \LogicException('Could not use this type as a deserializer: '.$type.' for element: '.$name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Sabre\Xml\Serializer;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Sabre\Xml\Writer;
|
||||||
|
use Sabre\Xml\XmlSerializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file provides a number of 'serializer' helper functions.
|
||||||
|
*
|
||||||
|
* These helper functions can be used to easily xml-encode common PHP
|
||||||
|
* data structures, or can be placed in the $classMap.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'enum' serializer writes simple list of elements.
|
||||||
|
*
|
||||||
|
* For example, calling:
|
||||||
|
*
|
||||||
|
* enum($writer, [
|
||||||
|
* "{http://sabredav.org/ns}elem1",
|
||||||
|
* "{http://sabredav.org/ns}elem2",
|
||||||
|
* "{http://sabredav.org/ns}elem3",
|
||||||
|
* "{http://sabredav.org/ns}elem4",
|
||||||
|
* "{http://sabredav.org/ns}elem5",
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* Will generate something like this (if the correct namespace is declared):
|
||||||
|
*
|
||||||
|
* <s:elem1 />
|
||||||
|
* <s:elem2 />
|
||||||
|
* <s:elem3 />
|
||||||
|
* <s:elem4>content</s:elem4>
|
||||||
|
* <s:elem5 attr="val" />
|
||||||
|
*
|
||||||
|
* @param string[] $values
|
||||||
|
*/
|
||||||
|
function enum(Writer $writer, array $values)
|
||||||
|
{
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$writer->writeElement($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The valueObject serializer turns a simple PHP object into a classname.
|
||||||
|
*
|
||||||
|
* Every public property will be encoded as an xml element with the same
|
||||||
|
* name, in the XML namespace as specified.
|
||||||
|
*
|
||||||
|
* Values that are set to null or an empty array are not serialized. To
|
||||||
|
* serialize empty properties, you must specify them as an empty string.
|
||||||
|
*
|
||||||
|
* @param object $valueObject
|
||||||
|
*/
|
||||||
|
function valueObject(Writer $writer, $valueObject, string $namespace)
|
||||||
|
{
|
||||||
|
foreach (get_object_vars($valueObject) as $key => $val) {
|
||||||
|
if (is_array($val)) {
|
||||||
|
// If $val is an array, it has a special meaning. We need to
|
||||||
|
// generate one child element for each item in $val
|
||||||
|
foreach ($val as $child) {
|
||||||
|
$writer->writeElement('{'.$namespace.'}'.$key, $child);
|
||||||
|
}
|
||||||
|
} elseif (null !== $val) {
|
||||||
|
$writer->writeElement('{'.$namespace.'}'.$key, $val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This serializer helps you serialize xml structures that look like
|
||||||
|
* this:.
|
||||||
|
*
|
||||||
|
* <collection>
|
||||||
|
* <item>...</item>
|
||||||
|
* <item>...</item>
|
||||||
|
* <item>...</item>
|
||||||
|
* </collection>
|
||||||
|
*
|
||||||
|
* In that previous example, this serializer just serializes the item element,
|
||||||
|
* and this could be called like this:
|
||||||
|
*
|
||||||
|
* repeatingElements($writer, $items, '{}item');
|
||||||
|
*/
|
||||||
|
function repeatingElements(Writer $writer, array $items, string $childElementName)
|
||||||
|
{
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$writer->writeElement($childElementName, $item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is the 'default' serializer that is able to serialize most
|
||||||
|
* things, and delegates to other serializers if needed.
|
||||||
|
*
|
||||||
|
* The standardSerializer supports a wide-array of values.
|
||||||
|
*
|
||||||
|
* $value may be a string or integer, it will just write out the string as text.
|
||||||
|
* $value may be an instance of XmlSerializable or Element, in which case it
|
||||||
|
* calls it's xmlSerialize() method.
|
||||||
|
* $value may be a PHP callback/function/closure, in case we call the callback
|
||||||
|
* and give it the Writer as an argument.
|
||||||
|
* $value may be a an object, and if it's in the classMap we automatically call
|
||||||
|
* the correct serializer for it.
|
||||||
|
* $value may be null, in which case we do nothing.
|
||||||
|
*
|
||||||
|
* If $value is an array, the array must look like this:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* [
|
||||||
|
* 'name' => '{namespaceUri}element-name',
|
||||||
|
* 'value' => '...',
|
||||||
|
* 'attributes' => [ 'attName' => 'attValue' ]
|
||||||
|
* ]
|
||||||
|
* [,
|
||||||
|
* 'name' => '{namespaceUri}element-name2',
|
||||||
|
* 'value' => '...',
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* This would result in xml like:
|
||||||
|
*
|
||||||
|
* <element-name xmlns="namespaceUri" attName="attValue">
|
||||||
|
* ...
|
||||||
|
* </element-name>
|
||||||
|
* <element-name2>
|
||||||
|
* ...
|
||||||
|
* </element-name2>
|
||||||
|
*
|
||||||
|
* The value property may be any value standardSerializer supports, so you can
|
||||||
|
* nest data-structures this way. Both value and attributes are optional.
|
||||||
|
*
|
||||||
|
* Alternatively, you can also specify the array using this syntax:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* [
|
||||||
|
* '{namespaceUri}element-name' => '...',
|
||||||
|
* '{namespaceUri}element-name2' => '...',
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* This is excellent for simple key->value structures, and here you can also
|
||||||
|
* specify anything for the value.
|
||||||
|
*
|
||||||
|
* You can even mix the two array syntaxes.
|
||||||
|
*
|
||||||
|
* @param string|int|float|bool|array|object $value
|
||||||
|
*/
|
||||||
|
function standardSerializer(Writer $writer, $value)
|
||||||
|
{
|
||||||
|
if (is_scalar($value)) {
|
||||||
|
// String, integer, float, boolean
|
||||||
|
$writer->text((string) $value);
|
||||||
|
} elseif ($value instanceof XmlSerializable) {
|
||||||
|
// XmlSerializable classes or Element classes.
|
||||||
|
$value->xmlSerialize($writer);
|
||||||
|
} elseif (is_object($value) && isset($writer->classMap[get_class($value)])) {
|
||||||
|
// It's an object which class appears in the classmap.
|
||||||
|
$writer->classMap[get_class($value)]($writer, $value);
|
||||||
|
} elseif (is_callable($value)) {
|
||||||
|
// A callback
|
||||||
|
$value($writer);
|
||||||
|
} elseif (is_array($value) && array_key_exists('name', $value)) {
|
||||||
|
// if the array had a 'name' element, we assume that this array
|
||||||
|
// describes a 'name' and optionally 'attributes' and 'value'.
|
||||||
|
|
||||||
|
$name = $value['name'];
|
||||||
|
$attributes = isset($value['attributes']) ? $value['attributes'] : [];
|
||||||
|
$value = isset($value['value']) ? $value['value'] : null;
|
||||||
|
|
||||||
|
$writer->startElement($name);
|
||||||
|
$writer->writeAttributes($attributes);
|
||||||
|
$writer->write($value);
|
||||||
|
$writer->endElement();
|
||||||
|
} elseif (is_array($value)) {
|
||||||
|
foreach ($value as $name => $item) {
|
||||||
|
if (is_int($name)) {
|
||||||
|
// This item has a numeric index. We just loop through the
|
||||||
|
// array and throw it back in the writer.
|
||||||
|
standardSerializer($writer, $item);
|
||||||
|
} elseif (is_string($name) && is_array($item) && isset($item['attributes'])) {
|
||||||
|
// The key is used for a name, but $item has 'attributes' and
|
||||||
|
// possibly 'value'
|
||||||
|
$writer->startElement($name);
|
||||||
|
$writer->writeAttributes($item['attributes']);
|
||||||
|
if (isset($item['value'])) {
|
||||||
|
$writer->write($item['value']);
|
||||||
|
}
|
||||||
|
$writer->endElement();
|
||||||
|
} elseif (is_string($name)) {
|
||||||
|
// This was a plain key-value array.
|
||||||
|
$writer->startElement($name);
|
||||||
|
$writer->write($item);
|
||||||
|
$writer->endElement();
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException('The writer does not know how to serialize arrays with keys of type: '.gettype($name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (is_object($value)) {
|
||||||
|
throw new InvalidArgumentException('The writer cannot serialize objects of class: '.get_class($value));
|
||||||
|
} elseif (!is_null($value)) {
|
||||||
|
throw new InvalidArgumentException('The writer cannot serialize values of type: '.gettype($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue