commit vendor

This commit is contained in:
2025-11-11 14:49:30 +01:00
parent f33121a308
commit 6d03080c00
2436 changed files with 483781 additions and 0 deletions

View File

@ -0,0 +1,109 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Property;
/**
* BINARY property.
*
* This object represents BINARY values.
*
* Binary values are most commonly used by the iCalendar ATTACH property, and
* the vCard PHOTO property.
*
* This property will transparently encode and decode to base64.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Binary extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Updates the current value.
*
* This may be either a single, or multiple strings in an array.
*
* @param string|array $value
*/
public function setValue($value)
{
if (is_array($value)) {
if (1 === count($value)) {
$this->value = $value[0];
} else {
throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
}
} else {
$this->value = $value;
}
}
/**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
*
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
* not yet done, but parameters are not included.
*
* @param string $val
*/
public function setRawMimeDirValue($val)
{
$this->value = base64_decode($val);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return base64_encode($this->value);
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'BINARY';
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
return [base64_encode($this->getValue())];
}
/**
* Sets the json value, as it would appear in a jCard or jCal object.
*
* The value must always be an array.
*/
public function setJsonValue(array $value)
{
$value = array_map('base64_decode', $value);
parent::setJsonValue($value);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Sabre\VObject\Property;
use
Sabre\VObject\Property;
/**
* Boolean property.
*
* This object represents BOOLEAN values. These are always the case-insensitive
* string TRUE or FALSE.
*
* Automatic conversion to PHP's true and false are done.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Boolean extends Property
{
/**
* 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)
{
$val = 'TRUE' === strtoupper($val) ? true : false;
$this->setValue($val);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return $this->value ? 'TRUE' : 'FALSE';
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'BOOLEAN';
}
/**
* 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);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Sabre\VObject\Property;
/**
* FlatText property.
*
* This object represents certain TEXT values.
*
* Specifically, this property is used for text values where there is only 1
* part. Semi-colons and colons will be de-escaped when deserializing, but if
* any semi-colons or commas appear without a backslash, we will not assume
* that they are delimiters.
*
* vCard 2.1 specifically has a whole bunch of properties where this may
* happen, as it only defines a delimiter for a few properties.
*
* vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
* delimiter, depending on the property.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class FlatText extends Text
{
/**
* Field separator.
*
* @var string
*/
public $delimiter = ',';
/**
* Sets the value as a quoted-printable encoded string.
*
* Overriding this so we're not splitting on a ; delimiter.
*
* @param string $val
*/
public function setQuotedPrintableValue($val)
{
$val = quoted_printable_decode($val);
$this->setValue($val);
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Property;
use Sabre\Xml;
/**
* Float property.
*
* This object represents FLOAT values. These can be 1 or more floating-point
* numbers.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class FloatValue extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = ';';
/**
* 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)
{
$val = explode($this->delimiter, $val);
foreach ($val as &$item) {
$item = (float) $item;
}
$this->setParts($val);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return implode(
$this->delimiter,
$this->getParts()
);
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'FLOAT';
}
/**
* Returns the value, in the format it should be encoded for JSON.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$val = array_map('floatval', $this->getParts());
// Special-casing the GEO property.
//
// See:
// http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
if ('GEO' === $this->name) {
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);
}
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use
Sabre\VObject\Property\Text;
/**
* CalAddress property.
*
* This object encodes CAL-ADDRESS values, as defined in rfc5545
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalAddress extends Text
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
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;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
/**
* DateTime property.
*
* This object represents DATE values, as defined here:
*
* http://tools.ietf.org/html/rfc5545#section-3.3.5
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Date extends DateTime
{
}

View File

@ -0,0 +1,363 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use DateTimeInterface;
use DateTimeZone;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;
use Sabre\VObject\TimeZoneUtil;
/**
* DateTime property.
*
* This object represents DATE-TIME values, as defined here:
*
* http://tools.ietf.org/html/rfc5545#section-3.3.4
*
* This particular object has a bit of hackish magic that it may also in some
* cases represent a DATE value. This is because it's a common usecase to be
* able to change a DATE-TIME into a DATE.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class DateTime extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = ',';
/**
* Sets a multi-valued property.
*
* You may also specify DateTime objects here.
*/
public function setParts(array $parts)
{
if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
$this->setDateTimes($parts);
} else {
parent::setParts($parts);
}
}
/**
* Updates the current value.
*
* This may be either a single, or multiple strings in an array.
*
* Instead of strings, you may also use DateTime here.
*
* @param string|array|DateTimeInterface $value
*/
public function setValue($value)
{
if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) {
$this->setDateTimes($value);
} elseif ($value instanceof DateTimeInterface) {
$this->setDateTimes([$value]);
} else {
parent::setValue($value);
}
}
/**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
*
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
* not yet done, but parameters are not included.
*
* @param string $val
*/
public function setRawMimeDirValue($val)
{
$this->setValue(explode($this->delimiter, $val));
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return implode($this->delimiter, $this->getParts());
}
/**
* Returns true if this is a DATE-TIME value, false if it's a DATE.
*
* @return bool
*/
public function hasTime()
{
return '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')
);
}
/**
* Returns a date-time value.
*
* Note that if this property contained more than 1 date-time, only the
* first will be returned. To get an array with multiple values, call
* getDateTimes.
*
* If no timezone information is known, because it's either an all-day
* property or floating time, we will use the DateTimeZone argument to
* figure out the exact date.
*
* @param DateTimeZone $timeZone
*
* @return \DateTimeImmutable
*/
public function getDateTime(DateTimeZone $timeZone = null)
{
$dt = $this->getDateTimes($timeZone);
if (!$dt) {
return;
}
return $dt[0];
}
/**
* Returns multiple date-time values.
*
* If no timezone information is known, because it's either an all-day
* property or floating time, we will use the DateTimeZone argument to
* figure out the exact date.
*
* @param DateTimeZone $timeZone
*
* @return \DateTimeImmutable[]
* @return \DateTime[]
*/
public function getDateTimes(DateTimeZone $timeZone = null)
{
// Does the property have a TZID?
$tzid = $this['TZID'];
if ($tzid) {
$timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root);
}
$dts = [];
foreach ($this->getParts() as $part) {
$dts[] = DateTimeParser::parse($part, $timeZone);
}
return $dts;
}
/**
* Sets the property as a DateTime object.
*
* @param bool isFloating If set to true, timezones will be ignored
*/
public function setDateTime(DateTimeInterface $dt, $isFloating = false)
{
$this->setDateTimes([$dt], $isFloating);
}
/**
* Sets the property as multiple date-time objects.
*
* The first value will be used as a reference for the timezones, and all
* the otehr values will be adjusted for that timezone
*
* @param DateTimeInterface[] $dt
* @param bool isFloating If set to true, timezones will be ignored
*/
public function setDateTimes(array $dt, $isFloating = false)
{
$values = [];
if ($this->hasTime()) {
$tz = null;
$isUtc = false;
foreach ($dt as $d) {
if ($isFloating) {
$values[] = $d->format('Ymd\\THis');
continue;
}
if (is_null($tz)) {
$tz = $d->getTimeZone();
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']);
if (!$isUtc) {
$this->offsetSet('TZID', $tz->getName());
}
} else {
$d = $d->setTimeZone($tz);
}
if ($isUtc) {
$values[] = $d->format('Ymd\\THis\\Z');
} else {
$values[] = $d->format('Ymd\\THis');
}
}
if ($isUtc || $isFloating) {
$this->offsetUnset('TZID');
}
} else {
foreach ($dt as $d) {
$values[] = $d->format('Ymd');
}
$this->offsetUnset('TZID');
}
$this->value = $values;
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return $this->hasTime() ? 'DATE-TIME' : 'DATE';
}
/**
* Returns the value, in the format it should be encoded for JSON.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$dts = $this->getDateTimes();
$hasTime = $this->hasTime();
$isFloating = $this->isFloating();
$tz = $dts[0]->getTimeZone();
$isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
return array_map(
function (DateTimeInterface $dt) use ($hasTime, $isUtc) {
if ($hasTime) {
return $dt->format('Y-m-d\\TH:i:s').($isUtc ? 'Z' : '');
} else {
return $dt->format('Y-m-d');
}
},
$dts
);
}
/**
* Sets the json value, as it would appear in a jCard or jCal object.
*
* The value must always be an array.
*/
public function setJsonValue(array $value)
{
// dates and times in jCal have one difference to dates and times in
// iCalendar. In jCal date-parts are separated by dashes, and
// time-parts are separated by colons. It makes sense to just remove
// those.
$this->setValue(
array_map(
function ($item) {
return strtr($item, [':' => '', '-' => '']);
},
$value
)
);
}
/**
* We need to intercept offsetSet, because it may be used to alter the
* VALUE from DATE-TIME to DATE or vice-versa.
*
* @param string $name
* @param mixed $value
*/
public function offsetSet($name, $value)
{
parent::offsetSet($name, $value);
if ('VALUE' !== strtoupper($name)) {
return;
}
// This will ensure that dates are correctly encoded.
$this->setDateTimes($this->getDateTimes());
}
/**
* Validates the node for correctness.
*
* The following options are supported:
* Node::REPAIR - May attempt to automatically repair the problem.
*
* This method returns an array with detected problems.
* Every element has the following properties:
*
* * level - problem level.
* * message - A human-readable string describing the issue.
* * node - A reference to the problematic node.
*
* The level means:
* 1 - The issue was repaired (only happens if REPAIR was turned on)
* 2 - An inconsequential issue
* 3 - A severe issue.
*
* @param int $options
*
* @return array
*/
public function validate($options = 0)
{
$messages = parent::validate($options);
$valueType = $this->getValueType();
$values = $this->getParts();
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;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property;
/**
* Duration property.
*
* This object represents DURATION values, as defined here:
*
* http://tools.ietf.org/html/rfc5545#section-3.3.6
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Duration extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = ',';
/**
* 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(explode($this->delimiter, $val));
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return implode($this->delimiter, $this->getParts());
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'DURATION';
}
/**
* Returns a DateInterval representation of the Duration property.
*
* If the property has more than one value, only the first is returned.
*
* @return \DateInterval
*/
public function getDateInterval()
{
$parts = $this->getParts();
$value = $parts[0];
return DateTimeParser::parseDuration($value);
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property;
use Sabre\Xml;
/**
* Period property.
*
* This object represents PERIOD values, as defined here:
*
* http://tools.ietf.org/html/rfc5545#section-3.8.2.6
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Period extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = ',';
/**
* 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(explode($this->delimiter, $val));
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return implode($this->delimiter, $this->getParts());
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'PERIOD';
}
/**
* Sets the json value, as it would appear in a jCard or jCal object.
*
* The value must always be an array.
*/
public function setJsonValue(array $value)
{
$value = array_map(
function ($item) {
return strtr(implode('/', $item), [':' => '', '-' => '']);
},
$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 = [];
foreach ($this->getParts() as $item) {
list($start, $end) = explode('/', $item, 2);
$start = DateTimeParser::parseDateTime($start);
// This is a duration value.
if ('P' === $end[0]) {
$return[] = [
$start->format('Y-m-d\\TH:i:s'),
$end,
];
} else {
$end = DateTimeParser::parseDateTime($end);
$return[] = [
$start->format('Y-m-d\\TH:i:s'),
$end->format('Y-m-d\\TH:i:s'),
];
}
}
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();
}
}

View File

@ -0,0 +1,336 @@
<?php
namespace Sabre\VObject\Property\ICalendar;
use Sabre\VObject\Property;
use Sabre\Xml;
/**
* Recur property.
*
* This object represents RECUR properties.
* These values are just used for RRULE and the now deprecated EXRULE.
*
* The RRULE property may look something like this:
*
* RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5.
*
* This property exposes this as a key=>value array that is accessible using
* getParts, and may be set using setParts.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Recur extends Property
{
/**
* Updates the current value.
*
* This may be either a single, or multiple strings in an array.
*
* @param string|array $value
*/
public function setValue($value)
{
// If we're getting the data from json, we'll be receiving an object
if ($value instanceof \StdClass) {
$value = (array) $value;
}
if (is_array($value)) {
$newVal = [];
foreach ($value as $k => $v) {
if (is_string($v)) {
$v = strtoupper($v);
// The value had multiple sub-values
if (false !== strpos($v, ',')) {
$v = explode(',', $v);
}
if (0 === strcmp($k, 'until')) {
$v = strtr($v, [':' => '', '-' => '']);
}
} elseif (is_array($v)) {
$v = array_map('strtoupper', $v);
}
$newVal[strtoupper($k)] = $v;
}
$this->value = $newVal;
} elseif (is_string($value)) {
$this->value = self::stringToArray($value);
} else {
throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
}
}
/**
* Returns the current value.
*
* This method will always return a singular value. If this was a
* multi-value object, some decision will be made first on how to represent
* it as a string.
*
* To get the correct multi-value version, use getParts.
*
* @return string
*/
public function getValue()
{
$out = [];
foreach ($this->value as $key => $value) {
$out[] = $key.'='.(is_array($value) ? implode(',', $value) : $value);
}
return strtoupper(implode(';', $out));
}
/**
* Sets a multi-valued property.
*/
public function setParts(array $parts)
{
$this->setValue($parts);
}
/**
* Returns a multi-valued property.
*
* This method always returns an array, if there was only a single value,
* it will still be wrapped in an array.
*
* @return array
*/
public function getParts()
{
return $this->value;
}
/**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
*
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
* not yet done, but parameters are not included.
*
* @param string $val
*/
public function setRawMimeDirValue($val)
{
$this->setValue($val);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return $this->getValue();
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'RECUR';
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$values = [];
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;
}
}
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;
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace Sabre\VObject\Property;
use
Sabre\VObject\Property;
/**
* Integer property.
*
* This object represents INTEGER values. These are always a single integer.
* They may be preceded by either + or -.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class IntegerValue extends Property
{
/**
* 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((int) $val);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
return $this->value;
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'INTEGER';
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
return [(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);
}
}

View File

@ -0,0 +1,390 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Component;
use Sabre\VObject\Document;
use Sabre\VObject\Parser\MimeDir;
use Sabre\VObject\Property;
use Sabre\Xml;
/**
* Text property.
*
* This object represents TEXT values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Text extends Property
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string
*/
public $delimiter = ',';
/**
* List of properties that are considered 'structured'.
*
* @var array
*/
protected $structuredValues = [
// vCard
'N',
'ADR',
'ORG',
'GENDER',
'CLIENTPIDMAP',
// iCalendar
'REQUEST-STATUS',
];
/**
* Some text components have a minimum number of components.
*
* N must for instance be represented as 5 components, separated by ;, even
* if the last few components are unused.
*
* @var array
*/
protected $minimumPropertyValues = [
'N' => 5,
'ADR' => 7,
];
/**
* Creates the property.
*
* You can specify the parameters either in key=>value syntax, in which case
* parameters will automatically be created, or you can just pass a list of
* Parameter objects.
*
* @param Component $root The root document
* @param string $name
* @param string|array|null $value
* @param array $parameters List of parameters
* @param string $group The vcard property group
*/
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null)
{
// There's two types of multi-valued text properties:
// 1. multivalue properties.
// 2. structured value properties
//
// The former is always separated by a comma, the latter by semi-colon.
if (in_array($name, $this->structuredValues)) {
$this->delimiter = ';';
}
parent::__construct($root, $name, $value, $parameters, $group);
}
/**
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
*
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
* not yet done, but parameters are not included.
*
* @param string $val
*/
public function setRawMimeDirValue($val)
{
$this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
}
/**
* Sets the value as a quoted-printable encoded string.
*
* @param string $val
*/
public function setQuotedPrintableValue($val)
{
$val = quoted_printable_decode($val);
// Quoted printable only appears in vCard 2.1, and the only character
// that may be escaped there is ;. So we are simply splitting on just
// that.
//
// We also don't have to unescape \\, so all we need to look for is a ;
// that's not preceded with a \.
$regex = '# (?<!\\\\) ; #x';
$matches = preg_split($regex, $val);
$this->setValue($matches);
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
$val = $this->getParts();
if (isset($this->minimumPropertyValues[$this->name])) {
$val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
}
foreach ($val as &$item) {
if (!is_array($item)) {
$item = [$item];
}
foreach ($item as &$subItem) {
$subItem = strtr(
$subItem,
[
'\\' => '\\\\',
';' => '\;',
',' => '\,',
"\n" => '\n',
"\r" => '',
]
);
}
$item = implode(',', $item);
}
return implode($this->delimiter, $val);
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
// Structured text values should always be returned as a single
// array-item. Multi-value text should be returned as multiple items in
// the top-array.
if (in_array($this->name, $this->structuredValues)) {
return [$this->getParts()];
}
return $this->getParts();
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'TEXT';
}
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize()
{
// We need to kick in a special type of encoding, if it's a 2.1 vcard.
if (Document::VCARD21 !== $this->root->getDocumentType()) {
return parent::serialize();
}
$val = $this->getParts();
if (isset($this->minimumPropertyValues[$this->name])) {
$val = \array_pad($val, $this->minimumPropertyValues[$this->name], '');
}
// Imploding multiple parts into a single value, and splitting the
// values with ;.
if (\count($val) > 1) {
foreach ($val as $k => $v) {
$val[$k] = \str_replace(';', '\;', $v);
}
$val = \implode(';', $val);
} else {
$val = $val[0];
}
$str = $this->name;
if ($this->group) {
$str = $this->group.'.'.$this->name;
}
foreach ($this->parameters as $param) {
if ('QUOTED-PRINTABLE' === $param->getValue()) {
continue;
}
$str .= ';'.$param->serialize();
}
// If the resulting value contains a \n, we must encode it as
// quoted-printable.
if (false !== \strpos($val, "\n")) {
$str .= ';ENCODING=QUOTED-PRINTABLE:';
$lastLine = $str;
$out = null;
// The PHP built-in quoted-printable-encode does not correctly
// encode newlines for us. Specifically, the \r\n sequence must in
// vcards be encoded as =0D=OA and we must insert soft-newlines
// every 75 bytes.
for ($ii = 0; $ii < \strlen($val); ++$ii) {
$ord = \ord($val[$ii]);
// These characters are encoded as themselves.
if ($ord >= 32 && $ord <= 126) {
$lastLine .= $val[$ii];
} else {
$lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
}
if (\strlen($lastLine) >= 75) {
// Soft line break
$out .= $lastLine."=\r\n ";
$lastLine = null;
}
}
if (!\is_null($lastLine)) {
$out .= $lastLine."\r\n";
}
return $out;
} else {
$str .= ':'.$val;
$str = \preg_replace(
'/(
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
(?![\x80-\xbf]) # prevent splitting multibyte characters
)/x',
"$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;
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);
}
}
/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
*
* @return array
*/
public function validate($options = 0)
{
$warnings = parent::validate($options);
if (isset($this->minimumPropertyValues[$this->name])) {
$minimum = $this->minimumPropertyValues[$this->name];
$parts = $this->getParts();
if (count($parts) < $minimum) {
$warnings[] = [
'level' => $options & self::REPAIR ? 1 : 3,
'message' => 'The '.$this->name.' property must have at least '.$minimum.' values. It only has '.count($parts),
'node' => $this,
];
if ($options & self::REPAIR) {
$parts = array_pad($parts, $minimum, '');
$this->setParts($parts);
}
}
}
return $warnings;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\DateTimeParser;
/**
* Time property.
*
* This object encodes TIME values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Time extends Text
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
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);
}
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$parts = DateTimeParser::parseVCardTime($this->getValue());
$timeStr = '';
// Hour
if (!is_null($parts['hour'])) {
$timeStr .= $parts['hour'];
if (!is_null($parts['minute'])) {
$timeStr .= ':';
}
} else {
// We know either minute or second _must_ be set, so we insert a
// dash for an empty value.
$timeStr .= '-';
}
// Minute
if (!is_null($parts['minute'])) {
$timeStr .= $parts['minute'];
if (!is_null($parts['second'])) {
$timeStr .= ':';
}
} else {
if (isset($parts['second'])) {
// Dash for empty minute
$timeStr .= '-';
}
}
// Second
if (!is_null($parts['second'])) {
$timeStr .= $parts['second'];
}
// Timezone
if (!is_null($parts['timezone'])) {
if ('Z' === $parts['timezone']) {
$timeStr .= 'Z';
} else {
$timeStr .=
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']);
}
}
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);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Sabre\VObject\Property;
/**
* Unknown property.
*
* This object represents any properties not recognized by the parser.
* This type of value has been introduced by the jCal, jCard specs.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Unknown extends Text
{
/**
* 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 [$this->getRawMimeDirValue()];
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'UNKNOWN';
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Parameter;
use Sabre\VObject\Property;
/**
* URI property.
*
* This object encodes URI values. vCard 2.1 calls these URL.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Uri extends Text
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
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;
}
/**
* 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)
{
// 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, ['\,' => ',']);
}
}
/**
* Returns a raw mime-dir representation of the value.
*
* @return string
*/
public function getRawMimeDirValue()
{
if (is_array($this->value)) {
$value = $this->value[0];
} else {
$value = $this->value;
}
return strtr($value, [',' => '\,']);
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Sabre\VObject\Property;
/**
* UtcOffset property.
*
* This object encodes UTC-OFFSET values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class UtcOffset extends Text
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
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()
);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Sabre\VObject\Property\VCard;
/**
* Date property.
*
* This object encodes vCard DATE values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Date extends DateAndOrTime
{
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'DATE';
}
/**
* Sets the property as a DateTime object.
*/
public function setDateTime(\DateTimeInterface $dt)
{
$this->value = $dt->format('Ymd');
}
}

View File

@ -0,0 +1,367 @@
<?php
namespace Sabre\VObject\Property\VCard;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;
use Sabre\Xml;
/**
* DateAndOrTime property.
*
* This object encodes DATE-AND-OR-TIME values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class DateAndOrTime extends Property
{
/**
* Field separator.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
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]");
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
$dateStr = '';
// Year
if (!is_null($parts['year'])) {
$dateStr .= $parts['year'];
if (!is_null($parts['month'])) {
// If a year and a month is set, we need to insert a separator
// dash.
$dateStr .= '-';
}
} else {
if (!is_null($parts['month']) || !is_null($parts['date'])) {
// Inserting two dashes
$dateStr .= '--';
}
}
// Month
if (!is_null($parts['month'])) {
$dateStr .= $parts['month'];
if (isset($parts['date'])) {
// If month and date are set, we need the separator dash.
$dateStr .= '-';
}
} elseif (isset($parts['date'])) {
// If the month is empty, and a date is set, we need a 'empty
// dash'
$dateStr .= '-';
}
// Date
if (!is_null($parts['date'])) {
$dateStr .= $parts['date'];
}
// Early exit if we don't have a time string.
if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
return [$dateStr];
}
$dateStr .= 'T';
// Hour
if (!is_null($parts['hour'])) {
$dateStr .= $parts['hour'];
if (!is_null($parts['minute'])) {
$dateStr .= ':';
}
} else {
// We know either minute or second _must_ be set, so we insert a
// dash for an empty value.
$dateStr .= '-';
}
// Minute
if (!is_null($parts['minute'])) {
$dateStr .= $parts['minute'];
if (!is_null($parts['second'])) {
$dateStr .= ':';
}
} elseif (isset($parts['second'])) {
// Dash for empty minute
$dateStr .= '-';
}
// Second
if (!is_null($parts['second'])) {
$dateStr .= $parts['second'];
}
// Timezone
if (!is_null($parts['timezone'])) {
$dateStr .= $parts['timezone'];
}
return [$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;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Sabre\VObject\Property\VCard;
/**
* DateTime property.
*
* This object encodes DATE-TIME values for vCards.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class DateTime extends DateAndOrTime
{
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'DATE-TIME';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Sabre\VObject\Property\VCard;
use
Sabre\VObject\Property;
/**
* LanguageTag property.
*
* This object represents LANGUAGE-TAG values as used in vCards.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class LanguageTag extends Property
{
/**
* 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 $this->getValue();
}
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'LANGUAGE-TAG';
}
}

View File

@ -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';
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Sabre\VObject\Property\VCard;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property\Text;
use Sabre\Xml;
/**
* TimeStamp property.
*
* This object encodes TIMESTAMP values.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class TimeStamp extends Text
{
/**
* In case this is a multi-value property. This string will be used as a
* delimiter.
*
* @var string|null
*/
public $delimiter = null;
/**
* Returns the type of value.
*
* This corresponds to the VALUE= parameter. Every property also has a
* 'default' valueType.
*
* @return string
*/
public function getValueType()
{
return 'TIMESTAMP';
}
/**
* Returns the value, in the format it should be encoded for json.
*
* This method must always return an array.
*
* @return array
*/
public function getJsonValue()
{
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
$dateStr =
$parts['year'].'-'.
$parts['month'].'-'.
$parts['date'].'T'.
$parts['hour'].':'.
$parts['minute'].':'.
$parts['second'];
// Timezone
if (!is_null($parts['timezone'])) {
$dateStr .= $parts['timezone'];
}
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());
}
}