Fix generation of birthday, deathdate and anniversary in case where no year is set - which is allowed as per https://tools.ietf.org/html/rfc6350#section-6.2.5 (#26756)
Signed-off-by: Lukas Reschke <lukas@statuscode.ch> Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
parent
6a0f0403d0
commit
d5d726fc24
|
@ -30,6 +30,11 @@ use Exception;
|
||||||
use OCA\DAV\CardDAV\CardDavBackend;
|
use OCA\DAV\CardDAV\CardDavBackend;
|
||||||
use OCA\DAV\DAV\GroupPrincipalBackend;
|
use OCA\DAV\DAV\GroupPrincipalBackend;
|
||||||
use Sabre\VObject\Component\VCalendar;
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
use Sabre\VObject\Component\VCard;
|
||||||
|
use Sabre\VObject\DateTimeParser;
|
||||||
|
use Sabre\VObject\Document;
|
||||||
|
use Sabre\VObject\InvalidDataException;
|
||||||
|
use Sabre\VObject\Property\VCard\DateAndOrTime;
|
||||||
use Sabre\VObject\Reader;
|
use Sabre\VObject\Reader;
|
||||||
|
|
||||||
class BirthdayService {
|
class BirthdayService {
|
||||||
|
@ -129,6 +134,12 @@ class BirthdayService {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$doc = Reader::read($cardData);
|
$doc = Reader::read($cardData);
|
||||||
|
// We're always converting to vCard 4.0 so we can rely on the
|
||||||
|
// VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
|
||||||
|
if (!$doc instanceof VCard) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$doc = $doc->convert(Document::VCARD40);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -136,21 +147,43 @@ class BirthdayService {
|
||||||
if (!isset($doc->{$dateField})) {
|
if (!isset($doc->{$dateField})) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!isset($doc->FN)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$birthday = $doc->{$dateField};
|
$birthday = $doc->{$dateField};
|
||||||
if (!(string)$birthday) {
|
if (!(string)$birthday) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$title = str_replace('{name}',
|
// Skip if the BDAY property is not of the right type.
|
||||||
strtr((string)$doc->FN, array('\,' => ',', '\;' => ';')),
|
if (!$birthday instanceof DateAndOrTime) {
|
||||||
'{name}'
|
return null;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// Skip if we can't parse the BDAY value.
|
||||||
|
try {
|
||||||
|
$dateParts = DateTimeParser::parseVCardDateTime($birthday->getValue());
|
||||||
|
} catch (InvalidDataException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unknownYear = false;
|
||||||
|
if (!$dateParts['year']) {
|
||||||
|
$birthday = '1900-' . $dateParts['month'] . '-' . $dateParts['date'];
|
||||||
|
|
||||||
|
$unknownYear = true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$date = new \DateTime($birthday);
|
$date = new \DateTime($birthday);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if ($unknownYear) {
|
||||||
$summary = $title . ' (' . $summarySymbol . $date->format('Y') . ')';
|
$summary = $doc->FN->getValue() . ' ' . $summarySymbol;
|
||||||
|
} else {
|
||||||
|
$year = (int)$date->format('Y');
|
||||||
|
$summary = $doc->FN->getValue() . " ($summarySymbol$year)";
|
||||||
|
}
|
||||||
$vCal = new VCalendar();
|
$vCal = new VCalendar();
|
||||||
$vCal->VERSION = '2.0';
|
$vCal->VERSION = '2.0';
|
||||||
$vEvent = $vCal->createComponent('VEVENT');
|
$vEvent = $vCal->createComponent('VEVENT');
|
||||||
|
|
|
@ -55,18 +55,18 @@ class BirthdayServiceTest extends TestCase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider providesVCards
|
* @dataProvider providesVCards
|
||||||
* @param boolean $nullExpected
|
* @param boolean $expectedSummary
|
||||||
* @param string | null $data
|
* @param string | null $data
|
||||||
*/
|
*/
|
||||||
public function testBuildBirthdayFromContact($nullExpected, $data) {
|
public function testBuildBirthdayFromContact($expectedSummary, $data) {
|
||||||
$cal = $this->service->buildDateFromContact($data, 'BDAY', '*');
|
$cal = $this->service->buildDateFromContact($data, 'BDAY', '*');
|
||||||
if ($nullExpected) {
|
if ($expectedSummary === null) {
|
||||||
$this->assertNull($cal);
|
$this->assertNull($cal);
|
||||||
} else {
|
} else {
|
||||||
$this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal);
|
$this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal);
|
||||||
$this->assertTrue(isset($cal->VEVENT));
|
$this->assertTrue(isset($cal->VEVENT));
|
||||||
$this->assertEquals('FREQ=YEARLY', $cal->VEVENT->RRULE->getValue());
|
$this->assertEquals('FREQ=YEARLY', $cal->VEVENT->RRULE->getValue());
|
||||||
$this->assertEquals('12345 (*1900)', $cal->VEVENT->SUMMARY->getValue());
|
$this->assertEquals($expectedSummary, $cal->VEVENT->SUMMARY->getValue());
|
||||||
$this->assertEquals('TRANSPARENT', $cal->VEVENT->TRANSP->getValue());
|
$this->assertEquals('TRANSPARENT', $cal->VEVENT->TRANSP->getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,14 +233,19 @@ class BirthdayServiceTest extends TestCase {
|
||||||
|
|
||||||
public function providesVCards() {
|
public function providesVCards() {
|
||||||
return [
|
return [
|
||||||
[true, null],
|
[null, null],
|
||||||
[true, ''],
|
[null, ''],
|
||||||
[true, 'yasfewf'],
|
[null, 'yasfewf'],
|
||||||
[true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
[null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
[true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
[null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
[true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
[null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
[false, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:1900-01-01\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
['12345 (*1900)', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
[false, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:1900-12-31\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
['12345 (*1900)', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
|
['12345 *', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
|
['12345 *', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
|
[null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
|
[null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
|
['12345 (*900)', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue