Passed
Push — master ( 7c3f83...68c8fa )
by Morris
19:13 queued 12s
created

AddressBookImpl::createEmptyVCard()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arne Hamann <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author call-me-matt <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Kesselberg <[email protected]>
10
 * @author Georg Ehrke <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author John Molakvoæ (skjnldsv) <[email protected]>
13
 * @author Julius Härtl <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\DAV\CardDAV;
33
34
use OCP\Constants;
35
use OCP\IAddressBook;
36
use OCP\IURLGenerator;
37
use Sabre\VObject\Component\VCard;
38
use Sabre\VObject\Property;
39
use Sabre\VObject\Reader;
40
use Sabre\VObject\UUIDUtil;
41
42
class AddressBookImpl implements IAddressBook {
43
44
	/** @var CardDavBackend */
45
	private $backend;
46
47
	/** @var array */
48
	private $addressBookInfo;
49
50
	/** @var AddressBook */
51
	private $addressBook;
52
53
	/** @var IURLGenerator */
54
	private $urlGenerator;
55
56
	/**
57
	 * AddressBookImpl constructor.
58
	 *
59
	 * @param AddressBook $addressBook
60
	 * @param array $addressBookInfo
61
	 * @param CardDavBackend $backend
62
	 * @param IUrlGenerator $urlGenerator
63
	 */
64
	public function __construct(
65
			AddressBook $addressBook,
66
			array $addressBookInfo,
67
			CardDavBackend $backend,
68
			IURLGenerator $urlGenerator) {
69
		$this->addressBook = $addressBook;
70
		$this->addressBookInfo = $addressBookInfo;
71
		$this->backend = $backend;
72
		$this->urlGenerator = $urlGenerator;
73
	}
74
75
	/**
76
	 * @return string defining the technical unique key
77
	 * @since 5.0.0
78
	 */
79
	public function getKey() {
80
		return $this->addressBookInfo['id'];
81
	}
82
83
	/**
84
	 * @return string defining the unique uri
85
	 * @since 16.0.0
86
	 */
87
	public function getUri(): string {
88
		return $this->addressBookInfo['uri'];
89
	}
90
91
	/**
92
	 * In comparison to getKey() this function returns a human readable (maybe translated) name
93
	 *
94
	 * @return mixed
95
	 * @since 5.0.0
96
	 */
97
	public function getDisplayName() {
98
		return $this->addressBookInfo['{DAV:}displayname'];
99
	}
100
101
	/**
102
	 * @param string $pattern which should match within the $searchProperties
103
	 * @param array $searchProperties defines the properties within the query pattern should match
104
	 * @param array $options Options to define the output format and search behavior
105
	 * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
106
	 *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
107
	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
108
	 * 	- 'limit' - Set a numeric limit for the search results
109
	 * 	- 'offset' - Set the offset for the limited search results
110
	 * @return array an array of contacts which are arrays of key-value-pairs
111
	 *  example result:
112
	 *  [
113
	 *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
114
	 *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
115
	 *	]
116
	 * @since 5.0.0
117
	 */
118
	public function search($pattern, $searchProperties, $options) {
119
		$results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
0 ignored issues
show
Bug introduced by
$this->getKey() of type string is incompatible with the type integer expected by parameter $addressBookId of OCA\DAV\CardDAV\CardDavBackend::search(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
		$results = $this->backend->search(/** @scrutinizer ignore-type */ $this->getKey(), $pattern, $searchProperties, $options);
Loading history...
120
121
		$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
122
123
		$vCards = [];
124
		foreach ($results as $result) {
125
			$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
126
		}
127
128
		return $vCards;
129
	}
130
131
	/**
132
	 * @param array $properties this array if key-value-pairs defines a contact
133
	 * @return array an array representing the contact just created or updated
134
	 * @since 5.0.0
135
	 */
136
	public function createOrUpdate($properties) {
137
		$update = false;
138
		if (!isset($properties['URI'])) { // create a new contact
139
			$uid = $this->createUid();
140
			$uri = $uid . '.vcf';
141
			$vCard = $this->createEmptyVCard($uid);
142
		} else { // update existing contact
143
			$uri = $properties['URI'];
144
			$vCardData = $this->backend->getCard($this->getKey(), $uri);
145
			$vCard = $this->readCard($vCardData['carddata']);
146
			$update = true;
147
		}
148
149
		foreach ($properties as $key => $value) {
150
			if (is_array($value)) {
151
				$vCard->remove($key);
152
				foreach ($value as $entry) {
153
					if (is_string($entry)) {
154
						$property = $vCard->createProperty($key, $entry);
155
					} else {
156
						if (($key === "ADR" || $key === "PHOTO") && is_string($entry["value"])) {
157
							$entry["value"] = stripslashes($entry["value"]);
158
							$entry["value"] = explode(';', $entry["value"]);
159
						}
160
						$property = $vCard->createProperty($key, $entry["value"]);
161
						if (isset($entry["type"])) {
162
							$property->add('TYPE', $entry["type"]);
163
						}
164
					}
165
					$vCard->add($property);
166
				}
167
			} elseif ($key !== 'URI') {
168
				$vCard->$key = $vCard->createProperty($key, $value);
169
			}
170
		}
171
172
		if ($update) {
173
			$this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
174
		} else {
175
			$this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
176
		}
177
178
		return $this->vCard2Array($uri, $vCard);
179
	}
180
181
	/**
182
	 * @return mixed
183
	 * @since 5.0.0
184
	 */
185
	public function getPermissions() {
186
		$permissions = $this->addressBook->getACL();
187
		$result = 0;
188
		foreach ($permissions as $permission) {
189
			switch ($permission['privilege']) {
190
				case '{DAV:}read':
191
					$result |= Constants::PERMISSION_READ;
192
					break;
193
				case '{DAV:}write':
194
					$result |= Constants::PERMISSION_CREATE;
195
					$result |= Constants::PERMISSION_UPDATE;
196
					break;
197
				case '{DAV:}all':
198
					$result |= Constants::PERMISSION_ALL;
199
					break;
200
			}
201
		}
202
203
		return $result;
204
	}
205
206
	/**
207
	 * @param object $id the unique identifier to a contact
208
	 * @return bool successful or not
209
	 * @since 5.0.0
210
	 */
211
	public function delete($id) {
212
		$uri = $this->backend->getCardUri($id);
0 ignored issues
show
Bug introduced by
$id of type object is incompatible with the type integer expected by parameter $id of OCA\DAV\CardDAV\CardDavBackend::getCardUri(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

212
		$uri = $this->backend->getCardUri(/** @scrutinizer ignore-type */ $id);
Loading history...
213
		return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
214
	}
215
216
	/**
217
	 * read vCard data into a vCard object
218
	 *
219
	 * @param string $cardData
220
	 * @return VCard
221
	 */
222
	protected function readCard($cardData) {
223
		return  Reader::read($cardData);
224
	}
225
226
	/**
227
	 * create UID for contact
228
	 *
229
	 * @return string
230
	 */
231
	protected function createUid() {
232
		do {
233
			$uid = $this->getUid();
234
			$contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
0 ignored issues
show
Bug introduced by
$this->getKey() of type string is incompatible with the type integer expected by parameter $addressBookId of OCA\DAV\CardDAV\CardDavBackend::getContact(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
			$contact = $this->backend->getContact(/** @scrutinizer ignore-type */ $this->getKey(), $uid . '.vcf');
Loading history...
235
		} while (!empty($contact));
236
237
		return $uid;
238
	}
239
240
	/**
241
	 * getUid is only there for testing, use createUid instead
242
	 */
243
	protected function getUid() {
244
		return UUIDUtil::getUUID();
245
	}
246
247
	/**
248
	 * create empty vcard
249
	 *
250
	 * @param string $uid
251
	 * @return VCard
252
	 */
253
	protected function createEmptyVCard($uid) {
254
		$vCard = new VCard();
255
		$vCard->UID = $uid;
0 ignored issues
show
Bug Best Practice introduced by
The property UID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
256
		return $vCard;
257
	}
258
259
	/**
260
	 * create array with all vCard properties
261
	 *
262
	 * @param string $uri
263
	 * @param VCard $vCard
264
	 * @param boolean $withTypes (optional) return the values as arrays of value/type pairs
265
	 * @return array
266
	 */
267
	protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
268
		$result = [
269
			'URI' => $uri,
270
		];
271
272
		foreach ($vCard->children() as $property) {
273
			if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
274
				$url = $this->urlGenerator->getAbsoluteURL(
275
					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
276
				$url .= implode('/', [
277
					'addressbooks',
278
					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
279
					$this->addressBookInfo['uri'],
280
					$uri
281
				]) . '?photo';
282
283
				$result['PHOTO'] = 'VALUE=uri:' . $url;
284
			} elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
285
				if (!isset($result[$property->name])) {
286
					$result[$property->name] = [];
287
				}
288
289
				$type = $this->getTypeFromProperty($property);
290
				if ($withTypes) {
291
					$result[$property->name][] = [
292
						'type' => $type,
293
						'value' => $property->getValue()
294
					];
295
				} else {
296
					$result[$property->name][] = $property->getValue();
297
				}
298
			} else {
299
				$result[$property->name] = $property->getValue();
300
			}
301
		}
302
303
		if ($this->isSystemAddressBook()) {
304
			$result['isLocalSystemBook'] = true;
305
		}
306
		return $result;
307
	}
308
309
	/**
310
	 * Get the type of the current property
311
	 *
312
	 * @param Property $property
313
	 * @return null|string
314
	 */
315
	protected function getTypeFromProperty(Property $property) {
316
		$parameters = $property->parameters();
317
		// Type is the social network, when it's empty we don't need this.
318
		if (isset($parameters['TYPE'])) {
319
			/** @var \Sabre\VObject\Parameter $type */
320
			$type = $parameters['TYPE'];
321
			return $type->getValue();
322
		}
323
324
		return null;
325
	}
326
327
	/**
328
	 * @inheritDoc
329
	 */
330
	public function isShared(): bool {
331
		if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
332
			return false;
333
		}
334
335
		return $this->addressBookInfo['principaluri']
336
			!== $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
337
	}
338
339
	/**
340
	 * @inheritDoc
341
	 */
342
	public function isSystemAddressBook(): bool {
343
		return $this->addressBookInfo['principaluri'] === 'principals/system/system' && (
344
			$this->addressBookInfo['uri'] === 'system' ||
345
			$this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
346
		);
347
	}
348
}
349