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:
Thomas Müller 2016-12-07 23:10:11 +01:00 committed by Roeland Jago Douma
parent 6a0f0403d0
commit d5d726fc24
No known key found for this signature in database
GPG Key ID: F941078878347C0C
2 changed files with 56 additions and 18 deletions

View File

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

View File

@ -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"],
]; ];
} }
} }