Completed
Push — master ( ce748c...5833c2 )
by Thomas
11:44
created

BirthdayService::syncUser()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Achim Königs <[email protected]>
4
 * @author Thomas Müller <[email protected]>
5
 *
6
 * @copyright Copyright (c) 2016, ownCloud GmbH.
7
 * @license AGPL-3.0
8
 *
9
 * This code is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License, version 3,
11
 * as published by the Free Software Foundation.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License, version 3,
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
20
 *
21
 */
22
23
namespace OCA\DAV\CalDAV;
24
25
use Exception;
26
use OCA\DAV\CardDAV\CardDavBackend;
27
use OCA\DAV\DAV\GroupPrincipalBackend;
28
use Sabre\VObject\Component\VCalendar;
29
use Sabre\VObject\Reader;
30
31
class BirthdayService {
32
33
	const BIRTHDAY_CALENDAR_URI = 'contact_birthdays';
34
35
	/** @var GroupPrincipalBackend */
36
	private $principalBackend;
37
38
	/**
39
	 * BirthdayService constructor.
40
	 *
41
	 * @param CalDavBackend $calDavBackEnd
42
	 * @param CardDavBackend $cardDavBackEnd
43
	 * @param GroupPrincipalBackend $principalBackend
44
	 */
45
	public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend) {
46
		$this->calDavBackEnd = $calDavBackEnd;
0 ignored issues
show
Bug introduced by
The property calDavBackEnd does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
47
		$this->cardDavBackEnd = $cardDavBackEnd;
0 ignored issues
show
Bug introduced by
The property cardDavBackEnd does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
48
		$this->principalBackend = $principalBackend;
49
	}
50
51
	/**
52
	 * @param int $addressBookId
53
	 * @param string $cardUri
54
	 * @param string $cardData
55
	 */
56
	public function onCardChanged($addressBookId, $cardUri, $cardData) {
57
		$targetPrincipals = $this->getAllAffectedPrincipals($addressBookId);
58
		
59
		$book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
60
		$targetPrincipals[] = $book['principaluri'];
61
		$datesToSync = [
62
			['postfix' => '', 'field' => 'BDAY', 'symbol' => '*'],
63
			['postfix' => '-death', 'field' => 'DEATHDATE', 'symbol' => "†"],
64
			['postfix' => '-anniversary', 'field' => 'ANNIVERSARY', 'symbol' => "⚭"],
65
		];
66
		foreach ($targetPrincipals as $principalUri) {
67
			$calendar = $this->ensureCalendarExists($principalUri);
68
			foreach ($datesToSync as $type) {
69
				$this->updateCalendar($cardUri, $cardData, $book, $calendar['id'], $type);
0 ignored issues
show
Documentation introduced by
$type is of type array<string,string,{"po...ng","symbol":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
70
			}
71
		}
72
	}
73
74
	/**
75
	 * @param int $addressBookId
76
	 * @param string $cardUri
77
	 */
78
	public function onCardDeleted($addressBookId, $cardUri) {
79
		$targetPrincipals = $this->getAllAffectedPrincipals($addressBookId);
80
		$book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
81
		$targetPrincipals[] = $book['principaluri'];
82
		foreach ($targetPrincipals as $principalUri) {
83
			$calendar = $this->ensureCalendarExists($principalUri);
84
			foreach (['', '-death', '-anniversary'] as $tag) {
85
				$objectUri = $book['uri'] . '-' . $cardUri . $tag .'.ics';
86
				$this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri);
87
			}
88
		}
89
	}
90
91
	/**
92
	 * @param string $principal
93
	 * @return array|null
94
	 * @throws \Sabre\DAV\Exception\BadRequest
95
	 */
96
	public function ensureCalendarExists($principal) {
97
		$book = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI);
98
		if (!is_null($book)) {
99
			return $book;
100
		}
101
		$this->calDavBackEnd->createCalendar($principal, self::BIRTHDAY_CALENDAR_URI, [
102
			'{DAV:}displayname' => 'Contact birthdays',
103
			'{http://apple.com/ns/ical/}calendar-color' => '#FFFFCA',
104
			'components'   => 'VEVENT',
105
		]);
106
107
		return $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI);
108
	}
109
110
	/**
111
	 * @param string $cardData
112
	 * @param string $dateField
113
	 * @param string $summarySymbol
114
	 * @return null|VCalendar
115
	 */
116
	public function buildDateFromContact($cardData, $dateField, $summarySymbol) {
117
		if (empty($cardData)) {
118
			return null;
119
		}
120
		try {
121
			$doc = Reader::read($cardData);
122
		} catch (Exception $e) {
123
			return null;
124
		}
125
126
		if (!isset($doc->{$dateField})) {
127
			return null;
128
		}
129
		$birthday = $doc->{$dateField};
130
		if (!(string)$birthday) {
131
			return null;
132
		}
133
		$title = str_replace('{name}',
134
			strtr((string)$doc->FN, ['\,' => ',', '\;' => ';']),
135
			'{name}'
136
		);
137
		try {
138
			$date = new \DateTime($birthday);
139
		} catch (Exception $e) {
140
			return null;
141
		}
142
		$vCal = new VCalendar();
143
		$vCal->VERSION = '2.0';
144
		$vEvent = $vCal->createComponent('VEVENT');
145
		$vEvent->add('DTSTART');
146
		$vEvent->DTSTART->setDateTime(
147
			$date
148
		);
149
		$vEvent->DTSTART['VALUE'] = 'DATE';
150
		$vEvent->add('DTEND');
151
		$date->add(new \DateInterval('P1D'));
152
		$vEvent->DTEND->setDateTime(
153
			$date
154
		);
155
		$vEvent->DTEND['VALUE'] = 'DATE';
156
		$vEvent->{'UID'} = $doc->UID;
157
		$vEvent->{'RRULE'} = 'FREQ=YEARLY';
158
		$vEvent->{'SUMMARY'} = $title . ' (' . $summarySymbol . $date->format('Y') . ')';
159
		$vEvent->{'TRANSP'} = 'TRANSPARENT';
160
		$alarm = $vCal->createComponent('VALARM');
161
		$alarm->add($vCal->createProperty('TRIGGER', '-PT0M', ['VALUE' => 'DURATION']));
162
		$alarm->add($vCal->createProperty('ACTION', 'DISPLAY'));
163
		$alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'}));
164
		$vEvent->add($alarm);
165
		$vCal->add($vEvent);
166
		return $vCal;
167
	}
168
169
	/**
170
	 * @param string $user
171
	 */
172
	public function syncUser($user) {
173
		$principal = 'principals/users/'.$user;
174
		$this->ensureCalendarExists($principal);
175
		$books = $this->cardDavBackEnd->getAddressBooksForUser($principal);
176
		foreach($books as $book) {
177
			$cards = $this->cardDavBackEnd->getCards($book['id']);
178
			foreach($cards as $card) {
179
				$this->onCardChanged($book['id'], $card['uri'], $card['carddata']);
180
			}
181
		}
182
	}
183
184
	/**
185
	 * @param string $existingCalendarData
186
	 * @param VCalendar $newCalendarData
187
	 * @return bool
188
	 */
189
	public function birthdayEvenChanged($existingCalendarData, $newCalendarData) {
190
		try {
191
			$existingBirthday = Reader::read($existingCalendarData);
192
		} catch (Exception $ex) {
193
			return true;
194
		}
195
		if ($newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() ||
196
			$newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue()
197
		) {
198
			return true;
199
		}
200
		return false;
201
	}
202
203
	/**
204
	 * @param integer $addressBookId
205
	 * @return mixed
206
	 */
207
	protected function getAllAffectedPrincipals($addressBookId) {
208
		$targetPrincipals = [];
209
		$shares = $this->cardDavBackEnd->getShares($addressBookId);
210
		foreach ($shares as $share) {
211
			if ($share['{http://owncloud.org/ns}group-share']) {
212
				$users = $this->principalBackend->getGroupMemberSet($share['{http://owncloud.org/ns}principal']);
213
				foreach ($users as $user) {
214
					$targetPrincipals[] = $user['uri'];
215
				}
216
			} else {
217
				$targetPrincipals[] = $share['{http://owncloud.org/ns}principal'];
218
			}
219
		}
220
		return array_values(array_unique($targetPrincipals, SORT_STRING));
221
	}
222
223
	/**
224
	 * @param string $cardUri
225
	 * @param string  $cardData
226
	 * @param array $book
227
	 * @param int $calendarId
228
	 * @param string $type
229
	 */
230
	private function updateCalendar($cardUri, $cardData, $book, $calendarId, $type) {
231
		$objectUri = $book['uri'] . '-' . $cardUri . $type['postfix'] . '.ics';
232
		$calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['symbol']);
233
		$existing = $this->calDavBackEnd->getCalendarObject($calendarId, $objectUri);
234
		if (is_null($calendarData)) {
235
			if (!is_null($existing)) {
236
				$this->calDavBackEnd->deleteCalendarObject($calendarId, $objectUri);
237
			}
238
		} else {
239
			if (is_null($existing)) {
240
				$this->calDavBackEnd->createCalendarObject($calendarId, $objectUri, $calendarData->serialize());
241
			} else {
242
				if ($this->birthdayEvenChanged($existing['calendardata'], $calendarData)) {
243
					$this->calDavBackEnd->updateCalendarObject($calendarId, $objectUri, $calendarData->serialize());
244
				}
245
			}
246
		}
247
	}
248
249
}
250