Passed
Push — master ( 34463b...78da95 )
by Morris
13:56 queued 11s
created

AddressBookImpl::vCard2Array()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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

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

207
		$uri = $this->backend->getCardUri(/** @scrutinizer ignore-type */ $id);
Loading history...
208
		return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
209
	}
210
211
	/**
212
	 * read vCard data into a vCard object
213
	 *
214
	 * @param string $cardData
215
	 * @return VCard
216
	 */
217
	protected function readCard($cardData) {
218
		return  Reader::read($cardData);
219
	}
220
221
	/**
222
	 * create UID for contact
223
	 *
224
	 * @return string
225
	 */
226
	protected function createUid() {
227
		do {
228
			$uid = $this->getUid();
229
			$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

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