Completed
Push — master ( e7a28e...09b069 )
by
unknown
26:01 queued 15s
created
apps/dav/lib/CardDAV/AddressBookImpl.php 1 patch
Indentation   +318 added lines, -318 removed lines patch added patch discarded remove patch
@@ -18,322 +18,322 @@
 block discarded – undo
18 18
 
19 19
 class AddressBookImpl implements IAddressBookEnabled {
20 20
 
21
-	/**
22
-	 * AddressBookImpl constructor.
23
-	 *
24
-	 * @param AddressBook $addressBook
25
-	 * @param array $addressBookInfo
26
-	 * @param CardDavBackend $backend
27
-	 * @param IUrlGenerator $urlGenerator
28
-	 */
29
-	public function __construct(
30
-		private AddressBook $addressBook,
31
-		private array $addressBookInfo,
32
-		private CardDavBackend $backend,
33
-		private IURLGenerator $urlGenerator,
34
-		private PropertyMapper $propertyMapper,
35
-		private ?string $userId,
36
-	) {
37
-	}
38
-
39
-	/**
40
-	 * @return string defining the technical unique key
41
-	 * @since 5.0.0
42
-	 */
43
-	public function getKey() {
44
-		return $this->addressBookInfo['id'];
45
-	}
46
-
47
-	/**
48
-	 * @return string defining the unique uri
49
-	 * @since 16.0.0
50
-	 */
51
-	public function getUri(): string {
52
-		return $this->addressBookInfo['uri'];
53
-	}
54
-
55
-	/**
56
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
57
-	 *
58
-	 * @return mixed
59
-	 * @since 5.0.0
60
-	 */
61
-	public function getDisplayName() {
62
-		return $this->addressBookInfo['{DAV:}displayname'];
63
-	}
64
-
65
-	/**
66
-	 * @param string $pattern which should match within the $searchProperties
67
-	 * @param array $searchProperties defines the properties within the query pattern should match
68
-	 * @param array $options Options to define the output format and search behavior
69
-	 *                       - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
70
-	 *                       example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
71
-	 *                       - 'escape_like_param' - If set to false wildcards _ and % are not escaped
72
-	 *                       - 'limit' - Set a numeric limit for the search results
73
-	 *                       - 'offset' - Set the offset for the limited search results
74
-	 *                       - 'wildcard' - Whether the search should use wildcards
75
-	 * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options
76
-	 * @return array an array of contacts which are arrays of key-value-pairs
77
-	 *               example result:
78
-	 *               [
79
-	 *               ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
80
-	 *               ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
81
-	 *               ]
82
-	 * @since 5.0.0
83
-	 */
84
-	public function search($pattern, $searchProperties, $options) {
85
-		$results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
86
-
87
-		$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
88
-
89
-		$vCards = [];
90
-		foreach ($results as $result) {
91
-			$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
92
-		}
93
-
94
-		return $vCards;
95
-	}
96
-
97
-	/**
98
-	 * @param array $properties this array if key-value-pairs defines a contact
99
-	 * @return array an array representing the contact just created or updated
100
-	 * @since 5.0.0
101
-	 */
102
-	public function createOrUpdate($properties) {
103
-		$update = false;
104
-		if (!isset($properties['URI'])) { // create a new contact
105
-			$uid = $this->createUid();
106
-			$uri = $uid . '.vcf';
107
-			$vCard = $this->createEmptyVCard($uid);
108
-		} else { // update existing contact
109
-			$uri = $properties['URI'];
110
-			$vCardData = $this->backend->getCard($this->getKey(), $uri);
111
-			$vCard = $this->readCard($vCardData['carddata']);
112
-			$update = true;
113
-		}
114
-
115
-		foreach ($properties as $key => $value) {
116
-			if (is_array($value)) {
117
-				$vCard->remove($key);
118
-				foreach ($value as $entry) {
119
-					if (is_string($entry)) {
120
-						$property = $vCard->createProperty($key, $entry);
121
-					} else {
122
-						if (($key === 'ADR' || $key === 'PHOTO') && is_string($entry['value'])) {
123
-							$entry['value'] = stripslashes($entry['value']);
124
-							$entry['value'] = explode(';', $entry['value']);
125
-						}
126
-						$property = $vCard->createProperty($key, $entry['value']);
127
-						if (isset($entry['type'])) {
128
-							$property->add('TYPE', $entry['type']);
129
-						}
130
-					}
131
-					$vCard->add($property);
132
-				}
133
-			} elseif ($key !== 'URI') {
134
-				$vCard->$key = $vCard->createProperty($key, $value);
135
-			}
136
-		}
137
-
138
-		if ($update) {
139
-			$this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
140
-		} else {
141
-			$this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
142
-		}
143
-
144
-		return $this->vCard2Array($uri, $vCard);
145
-	}
146
-
147
-	/**
148
-	 * @return mixed
149
-	 * @since 5.0.0
150
-	 */
151
-	public function getPermissions() {
152
-		$permissions = $this->addressBook->getACL();
153
-		$result = 0;
154
-		foreach ($permissions as $permission) {
155
-			if ($this->addressBookInfo['principaluri'] !== $permission['principal']) {
156
-				continue;
157
-			}
158
-
159
-			switch ($permission['privilege']) {
160
-				case '{DAV:}read':
161
-					$result |= Constants::PERMISSION_READ;
162
-					break;
163
-				case '{DAV:}write':
164
-					$result |= Constants::PERMISSION_CREATE;
165
-					$result |= Constants::PERMISSION_UPDATE;
166
-					break;
167
-				case '{DAV:}all':
168
-					$result |= Constants::PERMISSION_ALL;
169
-					break;
170
-			}
171
-		}
172
-
173
-		return $result;
174
-	}
175
-
176
-	/**
177
-	 * @param int $id the unique identifier to a contact
178
-	 * @return bool successful or not
179
-	 * @since 5.0.0
180
-	 */
181
-	public function delete($id) {
182
-		$uri = $this->backend->getCardUri($id);
183
-		return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
184
-	}
185
-
186
-	/**
187
-	 * read vCard data into a vCard object
188
-	 *
189
-	 * @param string $cardData
190
-	 * @return VCard
191
-	 */
192
-	protected function readCard($cardData) {
193
-		return  Reader::read($cardData);
194
-	}
195
-
196
-	/**
197
-	 * create UID for contact
198
-	 *
199
-	 * @return string
200
-	 */
201
-	protected function createUid() {
202
-		do {
203
-			$uid = $this->getUid();
204
-			$contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
205
-		} while (!empty($contact));
206
-
207
-		return $uid;
208
-	}
209
-
210
-	/**
211
-	 * getUid is only there for testing, use createUid instead
212
-	 */
213
-	protected function getUid() {
214
-		return UUIDUtil::getUUID();
215
-	}
216
-
217
-	/**
218
-	 * create empty vcard
219
-	 *
220
-	 * @param string $uid
221
-	 * @return VCard
222
-	 */
223
-	protected function createEmptyVCard($uid) {
224
-		$vCard = new VCard();
225
-		$vCard->UID = $uid;
226
-		return $vCard;
227
-	}
228
-
229
-	/**
230
-	 * create array with all vCard properties
231
-	 *
232
-	 * @param string $uri
233
-	 * @param VCard $vCard
234
-	 * @param boolean $withTypes (optional) return the values as arrays of value/type pairs
235
-	 * @return array
236
-	 */
237
-	protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
238
-		$result = [
239
-			'URI' => $uri,
240
-		];
241
-
242
-		foreach ($vCard->children() as $property) {
243
-			if ($property->name === 'PHOTO' && in_array($property->getValueType(), ['BINARY', 'URI'])) {
244
-				$url = $this->urlGenerator->getAbsoluteURL(
245
-					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
246
-				$url .= implode('/', [
247
-					'addressbooks',
248
-					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
249
-					$this->addressBookInfo['uri'],
250
-					$uri
251
-				]) . '?photo';
252
-
253
-				$result['PHOTO'] = 'VALUE=uri:' . $url;
254
-			} elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
255
-				if (!isset($result[$property->name])) {
256
-					$result[$property->name] = [];
257
-				}
258
-
259
-				$type = $this->getTypeFromProperty($property);
260
-				if ($withTypes) {
261
-					$result[$property->name][] = [
262
-						'type' => $type,
263
-						'value' => $property->getValue()
264
-					];
265
-				} else {
266
-					$result[$property->name][] = $property->getValue();
267
-				}
268
-			} else {
269
-				$result[$property->name] = $property->getValue();
270
-			}
271
-		}
272
-
273
-		if ($this->isSystemAddressBook()) {
274
-			$result['isLocalSystemBook'] = true;
275
-		}
276
-		return $result;
277
-	}
278
-
279
-	/**
280
-	 * Get the type of the current property
281
-	 *
282
-	 * @param Property $property
283
-	 * @return null|string
284
-	 */
285
-	protected function getTypeFromProperty(Property $property) {
286
-		$parameters = $property->parameters();
287
-		// Type is the social network, when it's empty we don't need this.
288
-		if (isset($parameters['TYPE'])) {
289
-			/** @var \Sabre\VObject\Parameter $type */
290
-			$type = $parameters['TYPE'];
291
-			return $type->getValue();
292
-		}
293
-
294
-		return null;
295
-	}
296
-
297
-	/**
298
-	 * @inheritDoc
299
-	 */
300
-	public function isShared(): bool {
301
-		if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
302
-			return false;
303
-		}
304
-
305
-		return $this->addressBookInfo['principaluri']
306
-			!== $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
307
-	}
308
-
309
-	/**
310
-	 * @inheritDoc
311
-	 */
312
-	public function isSystemAddressBook(): bool {
313
-		return $this->addressBookInfo['principaluri'] === 'principals/system/system' && (
314
-			$this->addressBookInfo['uri'] === 'system'
315
-			|| $this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
316
-		);
317
-	}
318
-
319
-	public function isEnabled(): bool {
320
-		if (!$this->userId) {
321
-			return true;
322
-		}
323
-
324
-		if ($this->isSystemAddressBook()) {
325
-			$user = $this->userId ;
326
-			$uri = 'z-server-generated--system';
327
-		} else {
328
-			$user = str_replace('principals/users/', '', $this->addressBookInfo['principaluri']);
329
-			$uri = $this->addressBookInfo['uri'];
330
-		}
331
-
332
-		$path = 'addressbooks/users/' . $user . '/' . $uri;
333
-		$properties = $this->propertyMapper->findPropertyByPathAndName($user, $path, '{http://owncloud.org/ns}enabled');
334
-		if (count($properties) > 0) {
335
-			return (bool)$properties[0]->getPropertyvalue();
336
-		}
337
-		return true;
338
-	}
21
+    /**
22
+     * AddressBookImpl constructor.
23
+     *
24
+     * @param AddressBook $addressBook
25
+     * @param array $addressBookInfo
26
+     * @param CardDavBackend $backend
27
+     * @param IUrlGenerator $urlGenerator
28
+     */
29
+    public function __construct(
30
+        private AddressBook $addressBook,
31
+        private array $addressBookInfo,
32
+        private CardDavBackend $backend,
33
+        private IURLGenerator $urlGenerator,
34
+        private PropertyMapper $propertyMapper,
35
+        private ?string $userId,
36
+    ) {
37
+    }
38
+
39
+    /**
40
+     * @return string defining the technical unique key
41
+     * @since 5.0.0
42
+     */
43
+    public function getKey() {
44
+        return $this->addressBookInfo['id'];
45
+    }
46
+
47
+    /**
48
+     * @return string defining the unique uri
49
+     * @since 16.0.0
50
+     */
51
+    public function getUri(): string {
52
+        return $this->addressBookInfo['uri'];
53
+    }
54
+
55
+    /**
56
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
57
+     *
58
+     * @return mixed
59
+     * @since 5.0.0
60
+     */
61
+    public function getDisplayName() {
62
+        return $this->addressBookInfo['{DAV:}displayname'];
63
+    }
64
+
65
+    /**
66
+     * @param string $pattern which should match within the $searchProperties
67
+     * @param array $searchProperties defines the properties within the query pattern should match
68
+     * @param array $options Options to define the output format and search behavior
69
+     *                       - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
70
+     *                       example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
71
+     *                       - 'escape_like_param' - If set to false wildcards _ and % are not escaped
72
+     *                       - 'limit' - Set a numeric limit for the search results
73
+     *                       - 'offset' - Set the offset for the limited search results
74
+     *                       - 'wildcard' - Whether the search should use wildcards
75
+     * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options
76
+     * @return array an array of contacts which are arrays of key-value-pairs
77
+     *               example result:
78
+     *               [
79
+     *               ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
80
+     *               ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
81
+     *               ]
82
+     * @since 5.0.0
83
+     */
84
+    public function search($pattern, $searchProperties, $options) {
85
+        $results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
86
+
87
+        $withTypes = \array_key_exists('types', $options) && $options['types'] === true;
88
+
89
+        $vCards = [];
90
+        foreach ($results as $result) {
91
+            $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
92
+        }
93
+
94
+        return $vCards;
95
+    }
96
+
97
+    /**
98
+     * @param array $properties this array if key-value-pairs defines a contact
99
+     * @return array an array representing the contact just created or updated
100
+     * @since 5.0.0
101
+     */
102
+    public function createOrUpdate($properties) {
103
+        $update = false;
104
+        if (!isset($properties['URI'])) { // create a new contact
105
+            $uid = $this->createUid();
106
+            $uri = $uid . '.vcf';
107
+            $vCard = $this->createEmptyVCard($uid);
108
+        } else { // update existing contact
109
+            $uri = $properties['URI'];
110
+            $vCardData = $this->backend->getCard($this->getKey(), $uri);
111
+            $vCard = $this->readCard($vCardData['carddata']);
112
+            $update = true;
113
+        }
114
+
115
+        foreach ($properties as $key => $value) {
116
+            if (is_array($value)) {
117
+                $vCard->remove($key);
118
+                foreach ($value as $entry) {
119
+                    if (is_string($entry)) {
120
+                        $property = $vCard->createProperty($key, $entry);
121
+                    } else {
122
+                        if (($key === 'ADR' || $key === 'PHOTO') && is_string($entry['value'])) {
123
+                            $entry['value'] = stripslashes($entry['value']);
124
+                            $entry['value'] = explode(';', $entry['value']);
125
+                        }
126
+                        $property = $vCard->createProperty($key, $entry['value']);
127
+                        if (isset($entry['type'])) {
128
+                            $property->add('TYPE', $entry['type']);
129
+                        }
130
+                    }
131
+                    $vCard->add($property);
132
+                }
133
+            } elseif ($key !== 'URI') {
134
+                $vCard->$key = $vCard->createProperty($key, $value);
135
+            }
136
+        }
137
+
138
+        if ($update) {
139
+            $this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
140
+        } else {
141
+            $this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
142
+        }
143
+
144
+        return $this->vCard2Array($uri, $vCard);
145
+    }
146
+
147
+    /**
148
+     * @return mixed
149
+     * @since 5.0.0
150
+     */
151
+    public function getPermissions() {
152
+        $permissions = $this->addressBook->getACL();
153
+        $result = 0;
154
+        foreach ($permissions as $permission) {
155
+            if ($this->addressBookInfo['principaluri'] !== $permission['principal']) {
156
+                continue;
157
+            }
158
+
159
+            switch ($permission['privilege']) {
160
+                case '{DAV:}read':
161
+                    $result |= Constants::PERMISSION_READ;
162
+                    break;
163
+                case '{DAV:}write':
164
+                    $result |= Constants::PERMISSION_CREATE;
165
+                    $result |= Constants::PERMISSION_UPDATE;
166
+                    break;
167
+                case '{DAV:}all':
168
+                    $result |= Constants::PERMISSION_ALL;
169
+                    break;
170
+            }
171
+        }
172
+
173
+        return $result;
174
+    }
175
+
176
+    /**
177
+     * @param int $id the unique identifier to a contact
178
+     * @return bool successful or not
179
+     * @since 5.0.0
180
+     */
181
+    public function delete($id) {
182
+        $uri = $this->backend->getCardUri($id);
183
+        return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
184
+    }
185
+
186
+    /**
187
+     * read vCard data into a vCard object
188
+     *
189
+     * @param string $cardData
190
+     * @return VCard
191
+     */
192
+    protected function readCard($cardData) {
193
+        return  Reader::read($cardData);
194
+    }
195
+
196
+    /**
197
+     * create UID for contact
198
+     *
199
+     * @return string
200
+     */
201
+    protected function createUid() {
202
+        do {
203
+            $uid = $this->getUid();
204
+            $contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
205
+        } while (!empty($contact));
206
+
207
+        return $uid;
208
+    }
209
+
210
+    /**
211
+     * getUid is only there for testing, use createUid instead
212
+     */
213
+    protected function getUid() {
214
+        return UUIDUtil::getUUID();
215
+    }
216
+
217
+    /**
218
+     * create empty vcard
219
+     *
220
+     * @param string $uid
221
+     * @return VCard
222
+     */
223
+    protected function createEmptyVCard($uid) {
224
+        $vCard = new VCard();
225
+        $vCard->UID = $uid;
226
+        return $vCard;
227
+    }
228
+
229
+    /**
230
+     * create array with all vCard properties
231
+     *
232
+     * @param string $uri
233
+     * @param VCard $vCard
234
+     * @param boolean $withTypes (optional) return the values as arrays of value/type pairs
235
+     * @return array
236
+     */
237
+    protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
238
+        $result = [
239
+            'URI' => $uri,
240
+        ];
241
+
242
+        foreach ($vCard->children() as $property) {
243
+            if ($property->name === 'PHOTO' && in_array($property->getValueType(), ['BINARY', 'URI'])) {
244
+                $url = $this->urlGenerator->getAbsoluteURL(
245
+                    $this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
246
+                $url .= implode('/', [
247
+                    'addressbooks',
248
+                    substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
249
+                    $this->addressBookInfo['uri'],
250
+                    $uri
251
+                ]) . '?photo';
252
+
253
+                $result['PHOTO'] = 'VALUE=uri:' . $url;
254
+            } elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
255
+                if (!isset($result[$property->name])) {
256
+                    $result[$property->name] = [];
257
+                }
258
+
259
+                $type = $this->getTypeFromProperty($property);
260
+                if ($withTypes) {
261
+                    $result[$property->name][] = [
262
+                        'type' => $type,
263
+                        'value' => $property->getValue()
264
+                    ];
265
+                } else {
266
+                    $result[$property->name][] = $property->getValue();
267
+                }
268
+            } else {
269
+                $result[$property->name] = $property->getValue();
270
+            }
271
+        }
272
+
273
+        if ($this->isSystemAddressBook()) {
274
+            $result['isLocalSystemBook'] = true;
275
+        }
276
+        return $result;
277
+    }
278
+
279
+    /**
280
+     * Get the type of the current property
281
+     *
282
+     * @param Property $property
283
+     * @return null|string
284
+     */
285
+    protected function getTypeFromProperty(Property $property) {
286
+        $parameters = $property->parameters();
287
+        // Type is the social network, when it's empty we don't need this.
288
+        if (isset($parameters['TYPE'])) {
289
+            /** @var \Sabre\VObject\Parameter $type */
290
+            $type = $parameters['TYPE'];
291
+            return $type->getValue();
292
+        }
293
+
294
+        return null;
295
+    }
296
+
297
+    /**
298
+     * @inheritDoc
299
+     */
300
+    public function isShared(): bool {
301
+        if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
302
+            return false;
303
+        }
304
+
305
+        return $this->addressBookInfo['principaluri']
306
+            !== $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
307
+    }
308
+
309
+    /**
310
+     * @inheritDoc
311
+     */
312
+    public function isSystemAddressBook(): bool {
313
+        return $this->addressBookInfo['principaluri'] === 'principals/system/system' && (
314
+            $this->addressBookInfo['uri'] === 'system'
315
+            || $this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
316
+        );
317
+    }
318
+
319
+    public function isEnabled(): bool {
320
+        if (!$this->userId) {
321
+            return true;
322
+        }
323
+
324
+        if ($this->isSystemAddressBook()) {
325
+            $user = $this->userId ;
326
+            $uri = 'z-server-generated--system';
327
+        } else {
328
+            $user = str_replace('principals/users/', '', $this->addressBookInfo['principaluri']);
329
+            $uri = $this->addressBookInfo['uri'];
330
+        }
331
+
332
+        $path = 'addressbooks/users/' . $user . '/' . $uri;
333
+        $properties = $this->propertyMapper->findPropertyByPathAndName($user, $path, '{http://owncloud.org/ns}enabled');
334
+        if (count($properties) > 0) {
335
+            return (bool)$properties[0]->getPropertyvalue();
336
+        }
337
+        return true;
338
+    }
339 339
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CardDAV/AddressBookImplTest.php 1 patch
Indentation   +515 added lines, -515 removed lines patch added patch discarded remove patch
@@ -19,519 +19,519 @@
 block discarded – undo
19 19
 use Test\TestCase;
20 20
 
21 21
 class AddressBookImplTest extends TestCase {
22
-	private array $addressBookInfo;
23
-	private AddressBook&MockObject $addressBook;
24
-	private IURLGenerator&MockObject $urlGenerator;
25
-	private CardDavBackend&MockObject $backend;
26
-	private PropertyMapper&MockObject $propertyMapper;
27
-	private VCard&MockObject $vCard;
28
-	private AddressBookImpl $addressBookImpl;
29
-
30
-	protected function setUp(): void {
31
-		parent::setUp();
32
-
33
-		$this->addressBookInfo = [
34
-			'id' => 42,
35
-			'uri' => 'system',
36
-			'principaluri' => 'principals/system/system',
37
-			'{DAV:}displayname' => 'display name',
38
-		];
39
-		$this->addressBook = $this->createMock(AddressBook::class);
40
-		$this->backend = $this->createMock(CardDavBackend::class);
41
-		$this->vCard = $this->createMock(VCard::class);
42
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
43
-		$this->propertyMapper = $this->createMock(PropertyMapper::class);
44
-
45
-		$this->addressBookImpl = new AddressBookImpl(
46
-			$this->addressBook,
47
-			$this->addressBookInfo,
48
-			$this->backend,
49
-			$this->urlGenerator,
50
-			$this->propertyMapper,
51
-			null
52
-		);
53
-	}
54
-
55
-	public function testGetKey(): void {
56
-		$this->assertSame($this->addressBookInfo['id'],
57
-			$this->addressBookImpl->getKey());
58
-	}
59
-
60
-	public function testGetDisplayName(): void {
61
-		$this->assertSame($this->addressBookInfo['{DAV:}displayname'],
62
-			$this->addressBookImpl->getDisplayName());
63
-	}
64
-
65
-	public function testSearch(): void {
66
-		/** @var MockObject&AddressBookImpl $addressBookImpl */
67
-		$addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
68
-			->setConstructorArgs(
69
-				[
70
-					$this->addressBook,
71
-					$this->addressBookInfo,
72
-					$this->backend,
73
-					$this->urlGenerator,
74
-					$this->propertyMapper,
75
-					null
76
-				]
77
-			)
78
-			->onlyMethods(['vCard2Array', 'readCard'])
79
-			->getMock();
80
-
81
-		$pattern = 'pattern';
82
-		$searchProperties = 'properties';
83
-
84
-		$this->backend->expects($this->once())->method('search')
85
-			->with($this->addressBookInfo['id'], $pattern, $searchProperties)
86
-			->willReturn(
87
-				[
88
-					['uri' => 'foo.vcf', 'carddata' => 'cardData1'],
89
-					['uri' => 'bar.vcf', 'carddata' => 'cardData2']
90
-				]
91
-			);
92
-
93
-		$addressBookImpl->expects($this->exactly(2))->method('readCard')
94
-			->willReturn($this->vCard);
95
-		$addressBookImpl->expects($this->exactly(2))->method('vCard2Array')
96
-			->willReturnMap([
97
-				['foo.vcf', $this->vCard, 'vCard'],
98
-				['bar.vcf', $this->vCard, 'vCard'],
99
-			]);
100
-
101
-		$result = $addressBookImpl->search($pattern, $searchProperties, []);
102
-		$this->assertTrue((is_array($result)));
103
-		$this->assertSame(2, count($result));
104
-	}
105
-
106
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestCreate')]
107
-	public function testCreate(array $properties): void {
108
-		$uid = 'uid';
109
-
110
-		/** @var MockObject&AddressBookImpl $addressBookImpl */
111
-		$addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
112
-			->setConstructorArgs(
113
-				[
114
-					$this->addressBook,
115
-					$this->addressBookInfo,
116
-					$this->backend,
117
-					$this->urlGenerator,
118
-					$this->propertyMapper,
119
-					null
120
-				]
121
-			)
122
-			->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard'])
123
-			->getMock();
124
-
125
-		$expectedProperties = 0;
126
-		foreach ($properties as $data) {
127
-			if (is_string($data)) {
128
-				$expectedProperties++;
129
-			} else {
130
-				$expectedProperties += count($data);
131
-			}
132
-		}
133
-
134
-		$addressBookImpl->expects($this->once())->method('createUid')
135
-			->willReturn($uid);
136
-		$addressBookImpl->expects($this->once())->method('createEmptyVCard')
137
-			->with($uid)->willReturn($this->vCard);
138
-		$this->vCard->expects($this->exactly($expectedProperties))
139
-			->method('createProperty');
140
-		$this->backend->expects($this->once())->method('createCard');
141
-		$this->backend->expects($this->never())->method('updateCard');
142
-		$this->backend->expects($this->never())->method('getCard');
143
-		$addressBookImpl->expects($this->once())->method('vCard2Array')
144
-			->with('uid.vcf', $this->vCard)->willReturn(true);
145
-
146
-		$this->assertTrue($addressBookImpl->createOrUpdate($properties));
147
-	}
148
-
149
-	public static function dataTestCreate(): array {
150
-		return [
151
-			[[]],
152
-			[['FN' => 'John Doe']],
153
-			[['FN' => 'John Doe', 'EMAIL' => ['[email protected]', '[email protected]']]],
154
-		];
155
-	}
156
-
157
-	public function testUpdate(): void {
158
-		$uid = 'uid';
159
-		$uri = 'bla.vcf';
160
-		$properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe'];
161
-
162
-		/** @var MockObject&AddressBookImpl $addressBookImpl */
163
-		$addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
164
-			->setConstructorArgs(
165
-				[
166
-					$this->addressBook,
167
-					$this->addressBookInfo,
168
-					$this->backend,
169
-					$this->urlGenerator,
170
-					$this->propertyMapper,
171
-					null
172
-				]
173
-			)
174
-			->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
175
-			->getMock();
176
-
177
-		$addressBookImpl->expects($this->never())->method('createUid');
178
-		$addressBookImpl->expects($this->never())->method('createEmptyVCard');
179
-		$this->backend->expects($this->once())->method('getCard')
180
-			->with($this->addressBookInfo['id'], $uri)
181
-			->willReturn(['carddata' => 'data']);
182
-		$addressBookImpl->expects($this->once())->method('readCard')
183
-			->with('data')->willReturn($this->vCard);
184
-		$this->vCard->expects($this->exactly(count($properties) - 1))
185
-			->method('createProperty');
186
-		$this->backend->expects($this->never())->method('createCard');
187
-		$this->backend->expects($this->once())->method('updateCard');
188
-		$addressBookImpl->expects($this->once())->method('vCard2Array')
189
-			->with($uri, $this->vCard)->willReturn(true);
190
-
191
-		$this->assertTrue($addressBookImpl->createOrUpdate($properties));
192
-	}
193
-
194
-	public function testUpdateWithTypes(): void {
195
-		$uid = 'uid';
196
-		$uri = 'bla.vcf';
197
-		$properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe', 'ADR' => [['type' => 'HOME', 'value' => ';;street;city;;;country']]];
198
-		$vCard = new vCard;
199
-		$textProperty = $vCard->createProperty('KEY', 'value');
200
-
201
-		/** @var MockObject&AddressBookImpl $addressBookImpl */
202
-		$addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
203
-			->setConstructorArgs(
204
-				[
205
-					$this->addressBook,
206
-					$this->addressBookInfo,
207
-					$this->backend,
208
-					$this->urlGenerator,
209
-					$this->propertyMapper,
210
-					null
211
-				]
212
-			)
213
-			->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
214
-			->getMock();
215
-
216
-		$this->backend->expects($this->once())->method('getCard')
217
-			->with($this->addressBookInfo['id'], $uri)
218
-			->willReturn(['carddata' => 'data']);
219
-		$addressBookImpl->expects($this->once())->method('readCard')
220
-			->with('data')->willReturn($this->vCard);
221
-		$this->vCard->method('createProperty')->willReturn($textProperty);
222
-		$this->vCard->expects($this->exactly(count($properties) - 1))
223
-			->method('createProperty');
224
-		$this->vCard->expects($this->once())->method('remove')
225
-			->with('ADR');
226
-		$this->vCard->expects($this->once())->method('add');
227
-
228
-		$addressBookImpl->createOrUpdate($properties);
229
-	}
230
-
231
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetPermissions')]
232
-	public function testGetPermissions(array $permissions, int $expected): void {
233
-		$this->addressBook->expects($this->once())->method('getACL')
234
-			->willReturn($permissions);
235
-
236
-		$this->assertSame($expected,
237
-			$this->addressBookImpl->getPermissions()
238
-		);
239
-	}
240
-
241
-	public static function dataTestGetPermissions(): array {
242
-		return [
243
-			[[], 0],
244
-			[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system']], 1],
245
-			[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'], ['privilege' => '{DAV:}write', 'principal' => 'principals/someone/else']], 1],
246
-			[[['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 6],
247
-			[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
248
-			[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 7],
249
-			[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
250
-			[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
251
-			[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
252
-			[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
253
-		];
254
-	}
255
-
256
-	public function testDelete(): void {
257
-		$cardId = 1;
258
-		$cardUri = 'cardUri';
259
-		$this->backend->expects($this->once())->method('getCardUri')
260
-			->with($cardId)->willReturn($cardUri);
261
-		$this->backend->expects($this->once())->method('deleteCard')
262
-			->with($this->addressBookInfo['id'], $cardUri)
263
-			->willReturn(true);
264
-
265
-		$this->assertTrue($this->addressBookImpl->delete($cardId));
266
-	}
267
-
268
-	public function testReadCard(): void {
269
-		$vCard = new VCard();
270
-		$vCard->add(new Text($vCard, 'UID', 'uid'));
271
-		$vCardSerialized = $vCard->serialize();
272
-
273
-		$result = $this->invokePrivate($this->addressBookImpl, 'readCard', [$vCardSerialized]);
274
-		$resultSerialized = $result->serialize();
275
-
276
-		$this->assertSame($vCardSerialized, $resultSerialized);
277
-	}
278
-
279
-	public function testCreateUid(): void {
280
-		/** @var MockObject&AddressBookImpl $addressBookImpl */
281
-		$addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
282
-			->setConstructorArgs(
283
-				[
284
-					$this->addressBook,
285
-					$this->addressBookInfo,
286
-					$this->backend,
287
-					$this->urlGenerator,
288
-					$this->propertyMapper,
289
-					null
290
-				]
291
-			)
292
-			->onlyMethods(['getUid'])
293
-			->getMock();
294
-
295
-		$addressBookImpl->expects($this->exactly(2))
296
-			->method('getUid')
297
-			->willReturnOnConsecutiveCalls(
298
-				'uid0',
299
-				'uid1',
300
-			);
301
-
302
-		// simulate that 'uid0' already exists, so the second uid will be returned
303
-		$this->backend->expects($this->exactly(2))->method('getContact')
304
-			->willReturnCallback(
305
-				function ($id, $uid) {
306
-					return ($uid === 'uid0.vcf');
307
-				}
308
-			);
309
-
310
-		$this->assertSame('uid1',
311
-			$this->invokePrivate($addressBookImpl, 'createUid', [])
312
-		);
313
-	}
314
-
315
-	public function testCreateEmptyVCard(): void {
316
-		$uid = 'uid';
317
-		$expectedVCard = new VCard();
318
-		$expectedVCard->UID = $uid;
319
-		$expectedVCardSerialized = $expectedVCard->serialize();
320
-
321
-		$result = $this->invokePrivate($this->addressBookImpl, 'createEmptyVCard', [$uid]);
322
-		$resultSerialized = $result->serialize();
323
-
324
-		$this->assertSame($expectedVCardSerialized, $resultSerialized);
325
-	}
326
-
327
-	public function testVCard2Array(): void {
328
-		$vCard = new VCard();
329
-
330
-		$vCard->add($vCard->createProperty('FN', 'Full Name'));
331
-
332
-		// Multi-value properties
333
-		$vCard->add($vCard->createProperty('CLOUD', 'cloud-user1@localhost'));
334
-		$vCard->add($vCard->createProperty('CLOUD', '[email protected]'));
335
-		$vCard->add($vCard->createProperty('EMAIL', 'email-user1@localhost'));
336
-		$vCard->add($vCard->createProperty('EMAIL', '[email protected]'));
337
-		$vCard->add($vCard->createProperty('IMPP', 'impp-user1@localhost'));
338
-		$vCard->add($vCard->createProperty('IMPP', '[email protected]'));
339
-		$vCard->add($vCard->createProperty('TEL', '+49 123456789'));
340
-		$vCard->add($vCard->createProperty('TEL', '+1 555 123456789'));
341
-		$vCard->add($vCard->createProperty('URL', 'https://localhost'));
342
-		$vCard->add($vCard->createProperty('URL', 'https://example.tld'));
343
-
344
-		// Type depending properties
345
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example');
346
-		$property->add('TYPE', 'twitter');
347
-		$vCard->add($property);
348
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example-2');
349
-		$property->add('TYPE', 'twitter');
350
-		$vCard->add($property);
351
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'fb-example');
352
-		$property->add('TYPE', 'facebook');
353
-		$vCard->add($property);
354
-
355
-		$array = $this->invokePrivate($this->addressBookImpl, 'vCard2Array', ['uri', $vCard]);
356
-		unset($array['PRODID']);
357
-		unset($array['UID']);
358
-
359
-		$this->assertEquals([
360
-			'URI' => 'uri',
361
-			'VERSION' => '4.0',
362
-			'FN' => 'Full Name',
363
-			'CLOUD' => [
364
-				'cloud-user1@localhost',
365
-				'[email protected]',
366
-			],
367
-			'EMAIL' => [
368
-				'email-user1@localhost',
369
-				'[email protected]',
370
-			],
371
-			'IMPP' => [
372
-				'impp-user1@localhost',
373
-				'[email protected]',
374
-			],
375
-			'TEL' => [
376
-				'+49 123456789',
377
-				'+1 555 123456789',
378
-			],
379
-			'URL' => [
380
-				'https://localhost',
381
-				'https://example.tld',
382
-			],
383
-
384
-			'X-SOCIALPROFILE' => [
385
-				'tw-example',
386
-				'tw-example-2',
387
-				'fb-example',
388
-			],
389
-
390
-			'isLocalSystemBook' => true,
391
-		], $array);
392
-	}
393
-
394
-	public function testVCard2ArrayWithTypes(): void {
395
-		$vCard = new VCard();
396
-
397
-		$vCard->add($vCard->createProperty('FN', 'Full Name'));
398
-
399
-		// Multi-value properties
400
-		$vCard->add($vCard->createProperty('CLOUD', 'cloud-user1@localhost'));
401
-		$vCard->add($vCard->createProperty('CLOUD', '[email protected]'));
402
-
403
-		$property = $vCard->createProperty('EMAIL', 'email-user1@localhost');
404
-		$property->add('TYPE', 'HOME');
405
-		$vCard->add($property);
406
-		$property = $vCard->createProperty('EMAIL', '[email protected]');
407
-		$property->add('TYPE', 'WORK');
408
-		$vCard->add($property);
409
-
410
-		$vCard->add($vCard->createProperty('IMPP', 'impp-user1@localhost'));
411
-		$vCard->add($vCard->createProperty('IMPP', '[email protected]'));
412
-
413
-		$property = $vCard->createProperty('TEL', '+49 123456789');
414
-		$property->add('TYPE', 'HOME,VOICE');
415
-		$vCard->add($property);
416
-		$property = $vCard->createProperty('TEL', '+1 555 123456789');
417
-		$property->add('TYPE', 'WORK');
418
-		$vCard->add($property);
419
-
420
-		$vCard->add($vCard->createProperty('URL', 'https://localhost'));
421
-		$vCard->add($vCard->createProperty('URL', 'https://example.tld'));
422
-
423
-		// Type depending properties
424
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example');
425
-		$property->add('TYPE', 'twitter');
426
-		$vCard->add($property);
427
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example-2');
428
-		$property->add('TYPE', 'twitter');
429
-		$vCard->add($property);
430
-		$property = $vCard->createProperty('X-SOCIALPROFILE', 'fb-example');
431
-		$property->add('TYPE', 'facebook');
432
-		$vCard->add($property);
433
-
434
-		$array = $this->invokePrivate($this->addressBookImpl, 'vCard2Array', ['uri', $vCard, true]);
435
-		unset($array['PRODID']);
436
-		unset($array['UID']);
437
-
438
-		$this->assertEquals([
439
-			'URI' => 'uri',
440
-			'VERSION' => '4.0',
441
-			'FN' => 'Full Name',
442
-			'CLOUD' => [
443
-				['type' => '', 'value' => 'cloud-user1@localhost'],
444
-				['type' => '', 'value' => '[email protected]'],
445
-			],
446
-			'EMAIL' => [
447
-				['type' => 'HOME', 'value' => 'email-user1@localhost'],
448
-				['type' => 'WORK', 'value' => '[email protected]'],
449
-			],
450
-			'IMPP' => [
451
-				['type' => '', 'value' => 'impp-user1@localhost'],
452
-				['type' => '', 'value' => '[email protected]'],
453
-			],
454
-			'TEL' => [
455
-				['type' => 'HOME,VOICE', 'value' => '+49 123456789'],
456
-				['type' => 'WORK', 'value' => '+1 555 123456789'],
457
-			],
458
-			'URL' => [
459
-				['type' => '', 'value' => 'https://localhost'],
460
-				['type' => '', 'value' => 'https://example.tld'],
461
-			],
462
-
463
-			'X-SOCIALPROFILE' => [
464
-				['type' => 'twitter', 'value' => 'tw-example'],
465
-				['type' => 'twitter', 'value' => 'tw-example-2'],
466
-				['type' => 'facebook', 'value' => 'fb-example'],
467
-			],
468
-
469
-			'isLocalSystemBook' => true,
470
-		], $array);
471
-	}
472
-
473
-	public function testIsSystemAddressBook(): void {
474
-		$addressBookInfo = [
475
-			'{http://owncloud.org/ns}owner-principal' => 'principals/system/system',
476
-			'principaluri' => 'principals/system/system',
477
-			'{DAV:}displayname' => 'display name',
478
-			'id' => 666,
479
-			'uri' => 'system',
480
-		];
481
-
482
-		$addressBookImpl = new AddressBookImpl(
483
-			$this->addressBook,
484
-			$addressBookInfo,
485
-			$this->backend,
486
-			$this->urlGenerator,
487
-			$this->propertyMapper,
488
-			null
489
-		);
490
-
491
-		$this->assertTrue($addressBookImpl->isSystemAddressBook());
492
-	}
493
-
494
-	public function testIsShared(): void {
495
-		$addressBookInfo = [
496
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
497
-			'{DAV:}displayname' => 'Test address book',
498
-			'principaluri' => 'user2',
499
-			'id' => 666,
500
-			'uri' => 'default',
501
-		];
502
-
503
-		$addressBookImpl = new AddressBookImpl(
504
-			$this->addressBook,
505
-			$addressBookInfo,
506
-			$this->backend,
507
-			$this->urlGenerator,
508
-			$this->propertyMapper,
509
-			'user2'
510
-		);
511
-
512
-		$this->assertFalse($addressBookImpl->isSystemAddressBook());
513
-		$this->assertTrue($addressBookImpl->isShared());
514
-	}
515
-
516
-	public function testIsNotShared(): void {
517
-		$addressBookInfo = [
518
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
519
-			'{DAV:}displayname' => 'Test address book',
520
-			'principaluri' => 'user1',
521
-			'id' => 666,
522
-			'uri' => 'default',
523
-		];
524
-
525
-		$addressBookImpl = new AddressBookImpl(
526
-			$this->addressBook,
527
-			$addressBookInfo,
528
-			$this->backend,
529
-			$this->urlGenerator,
530
-			$this->propertyMapper,
531
-			'user2'
532
-		);
533
-
534
-		$this->assertFalse($addressBookImpl->isSystemAddressBook());
535
-		$this->assertFalse($addressBookImpl->isShared());
536
-	}
22
+    private array $addressBookInfo;
23
+    private AddressBook&MockObject $addressBook;
24
+    private IURLGenerator&MockObject $urlGenerator;
25
+    private CardDavBackend&MockObject $backend;
26
+    private PropertyMapper&MockObject $propertyMapper;
27
+    private VCard&MockObject $vCard;
28
+    private AddressBookImpl $addressBookImpl;
29
+
30
+    protected function setUp(): void {
31
+        parent::setUp();
32
+
33
+        $this->addressBookInfo = [
34
+            'id' => 42,
35
+            'uri' => 'system',
36
+            'principaluri' => 'principals/system/system',
37
+            '{DAV:}displayname' => 'display name',
38
+        ];
39
+        $this->addressBook = $this->createMock(AddressBook::class);
40
+        $this->backend = $this->createMock(CardDavBackend::class);
41
+        $this->vCard = $this->createMock(VCard::class);
42
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
43
+        $this->propertyMapper = $this->createMock(PropertyMapper::class);
44
+
45
+        $this->addressBookImpl = new AddressBookImpl(
46
+            $this->addressBook,
47
+            $this->addressBookInfo,
48
+            $this->backend,
49
+            $this->urlGenerator,
50
+            $this->propertyMapper,
51
+            null
52
+        );
53
+    }
54
+
55
+    public function testGetKey(): void {
56
+        $this->assertSame($this->addressBookInfo['id'],
57
+            $this->addressBookImpl->getKey());
58
+    }
59
+
60
+    public function testGetDisplayName(): void {
61
+        $this->assertSame($this->addressBookInfo['{DAV:}displayname'],
62
+            $this->addressBookImpl->getDisplayName());
63
+    }
64
+
65
+    public function testSearch(): void {
66
+        /** @var MockObject&AddressBookImpl $addressBookImpl */
67
+        $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
68
+            ->setConstructorArgs(
69
+                [
70
+                    $this->addressBook,
71
+                    $this->addressBookInfo,
72
+                    $this->backend,
73
+                    $this->urlGenerator,
74
+                    $this->propertyMapper,
75
+                    null
76
+                ]
77
+            )
78
+            ->onlyMethods(['vCard2Array', 'readCard'])
79
+            ->getMock();
80
+
81
+        $pattern = 'pattern';
82
+        $searchProperties = 'properties';
83
+
84
+        $this->backend->expects($this->once())->method('search')
85
+            ->with($this->addressBookInfo['id'], $pattern, $searchProperties)
86
+            ->willReturn(
87
+                [
88
+                    ['uri' => 'foo.vcf', 'carddata' => 'cardData1'],
89
+                    ['uri' => 'bar.vcf', 'carddata' => 'cardData2']
90
+                ]
91
+            );
92
+
93
+        $addressBookImpl->expects($this->exactly(2))->method('readCard')
94
+            ->willReturn($this->vCard);
95
+        $addressBookImpl->expects($this->exactly(2))->method('vCard2Array')
96
+            ->willReturnMap([
97
+                ['foo.vcf', $this->vCard, 'vCard'],
98
+                ['bar.vcf', $this->vCard, 'vCard'],
99
+            ]);
100
+
101
+        $result = $addressBookImpl->search($pattern, $searchProperties, []);
102
+        $this->assertTrue((is_array($result)));
103
+        $this->assertSame(2, count($result));
104
+    }
105
+
106
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCreate')]
107
+    public function testCreate(array $properties): void {
108
+        $uid = 'uid';
109
+
110
+        /** @var MockObject&AddressBookImpl $addressBookImpl */
111
+        $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
112
+            ->setConstructorArgs(
113
+                [
114
+                    $this->addressBook,
115
+                    $this->addressBookInfo,
116
+                    $this->backend,
117
+                    $this->urlGenerator,
118
+                    $this->propertyMapper,
119
+                    null
120
+                ]
121
+            )
122
+            ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard'])
123
+            ->getMock();
124
+
125
+        $expectedProperties = 0;
126
+        foreach ($properties as $data) {
127
+            if (is_string($data)) {
128
+                $expectedProperties++;
129
+            } else {
130
+                $expectedProperties += count($data);
131
+            }
132
+        }
133
+
134
+        $addressBookImpl->expects($this->once())->method('createUid')
135
+            ->willReturn($uid);
136
+        $addressBookImpl->expects($this->once())->method('createEmptyVCard')
137
+            ->with($uid)->willReturn($this->vCard);
138
+        $this->vCard->expects($this->exactly($expectedProperties))
139
+            ->method('createProperty');
140
+        $this->backend->expects($this->once())->method('createCard');
141
+        $this->backend->expects($this->never())->method('updateCard');
142
+        $this->backend->expects($this->never())->method('getCard');
143
+        $addressBookImpl->expects($this->once())->method('vCard2Array')
144
+            ->with('uid.vcf', $this->vCard)->willReturn(true);
145
+
146
+        $this->assertTrue($addressBookImpl->createOrUpdate($properties));
147
+    }
148
+
149
+    public static function dataTestCreate(): array {
150
+        return [
151
+            [[]],
152
+            [['FN' => 'John Doe']],
153
+            [['FN' => 'John Doe', 'EMAIL' => ['[email protected]', '[email protected]']]],
154
+        ];
155
+    }
156
+
157
+    public function testUpdate(): void {
158
+        $uid = 'uid';
159
+        $uri = 'bla.vcf';
160
+        $properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe'];
161
+
162
+        /** @var MockObject&AddressBookImpl $addressBookImpl */
163
+        $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
164
+            ->setConstructorArgs(
165
+                [
166
+                    $this->addressBook,
167
+                    $this->addressBookInfo,
168
+                    $this->backend,
169
+                    $this->urlGenerator,
170
+                    $this->propertyMapper,
171
+                    null
172
+                ]
173
+            )
174
+            ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
175
+            ->getMock();
176
+
177
+        $addressBookImpl->expects($this->never())->method('createUid');
178
+        $addressBookImpl->expects($this->never())->method('createEmptyVCard');
179
+        $this->backend->expects($this->once())->method('getCard')
180
+            ->with($this->addressBookInfo['id'], $uri)
181
+            ->willReturn(['carddata' => 'data']);
182
+        $addressBookImpl->expects($this->once())->method('readCard')
183
+            ->with('data')->willReturn($this->vCard);
184
+        $this->vCard->expects($this->exactly(count($properties) - 1))
185
+            ->method('createProperty');
186
+        $this->backend->expects($this->never())->method('createCard');
187
+        $this->backend->expects($this->once())->method('updateCard');
188
+        $addressBookImpl->expects($this->once())->method('vCard2Array')
189
+            ->with($uri, $this->vCard)->willReturn(true);
190
+
191
+        $this->assertTrue($addressBookImpl->createOrUpdate($properties));
192
+    }
193
+
194
+    public function testUpdateWithTypes(): void {
195
+        $uid = 'uid';
196
+        $uri = 'bla.vcf';
197
+        $properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe', 'ADR' => [['type' => 'HOME', 'value' => ';;street;city;;;country']]];
198
+        $vCard = new vCard;
199
+        $textProperty = $vCard->createProperty('KEY', 'value');
200
+
201
+        /** @var MockObject&AddressBookImpl $addressBookImpl */
202
+        $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
203
+            ->setConstructorArgs(
204
+                [
205
+                    $this->addressBook,
206
+                    $this->addressBookInfo,
207
+                    $this->backend,
208
+                    $this->urlGenerator,
209
+                    $this->propertyMapper,
210
+                    null
211
+                ]
212
+            )
213
+            ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
214
+            ->getMock();
215
+
216
+        $this->backend->expects($this->once())->method('getCard')
217
+            ->with($this->addressBookInfo['id'], $uri)
218
+            ->willReturn(['carddata' => 'data']);
219
+        $addressBookImpl->expects($this->once())->method('readCard')
220
+            ->with('data')->willReturn($this->vCard);
221
+        $this->vCard->method('createProperty')->willReturn($textProperty);
222
+        $this->vCard->expects($this->exactly(count($properties) - 1))
223
+            ->method('createProperty');
224
+        $this->vCard->expects($this->once())->method('remove')
225
+            ->with('ADR');
226
+        $this->vCard->expects($this->once())->method('add');
227
+
228
+        $addressBookImpl->createOrUpdate($properties);
229
+    }
230
+
231
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetPermissions')]
232
+    public function testGetPermissions(array $permissions, int $expected): void {
233
+        $this->addressBook->expects($this->once())->method('getACL')
234
+            ->willReturn($permissions);
235
+
236
+        $this->assertSame($expected,
237
+            $this->addressBookImpl->getPermissions()
238
+        );
239
+    }
240
+
241
+    public static function dataTestGetPermissions(): array {
242
+        return [
243
+            [[], 0],
244
+            [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system']], 1],
245
+            [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'], ['privilege' => '{DAV:}write', 'principal' => 'principals/someone/else']], 1],
246
+            [[['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 6],
247
+            [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
248
+            [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 7],
249
+            [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
250
+            [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
251
+            [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
252
+            [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
253
+        ];
254
+    }
255
+
256
+    public function testDelete(): void {
257
+        $cardId = 1;
258
+        $cardUri = 'cardUri';
259
+        $this->backend->expects($this->once())->method('getCardUri')
260
+            ->with($cardId)->willReturn($cardUri);
261
+        $this->backend->expects($this->once())->method('deleteCard')
262
+            ->with($this->addressBookInfo['id'], $cardUri)
263
+            ->willReturn(true);
264
+
265
+        $this->assertTrue($this->addressBookImpl->delete($cardId));
266
+    }
267
+
268
+    public function testReadCard(): void {
269
+        $vCard = new VCard();
270
+        $vCard->add(new Text($vCard, 'UID', 'uid'));
271
+        $vCardSerialized = $vCard->serialize();
272
+
273
+        $result = $this->invokePrivate($this->addressBookImpl, 'readCard', [$vCardSerialized]);
274
+        $resultSerialized = $result->serialize();
275
+
276
+        $this->assertSame($vCardSerialized, $resultSerialized);
277
+    }
278
+
279
+    public function testCreateUid(): void {
280
+        /** @var MockObject&AddressBookImpl $addressBookImpl */
281
+        $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class)
282
+            ->setConstructorArgs(
283
+                [
284
+                    $this->addressBook,
285
+                    $this->addressBookInfo,
286
+                    $this->backend,
287
+                    $this->urlGenerator,
288
+                    $this->propertyMapper,
289
+                    null
290
+                ]
291
+            )
292
+            ->onlyMethods(['getUid'])
293
+            ->getMock();
294
+
295
+        $addressBookImpl->expects($this->exactly(2))
296
+            ->method('getUid')
297
+            ->willReturnOnConsecutiveCalls(
298
+                'uid0',
299
+                'uid1',
300
+            );
301
+
302
+        // simulate that 'uid0' already exists, so the second uid will be returned
303
+        $this->backend->expects($this->exactly(2))->method('getContact')
304
+            ->willReturnCallback(
305
+                function ($id, $uid) {
306
+                    return ($uid === 'uid0.vcf');
307
+                }
308
+            );
309
+
310
+        $this->assertSame('uid1',
311
+            $this->invokePrivate($addressBookImpl, 'createUid', [])
312
+        );
313
+    }
314
+
315
+    public function testCreateEmptyVCard(): void {
316
+        $uid = 'uid';
317
+        $expectedVCard = new VCard();
318
+        $expectedVCard->UID = $uid;
319
+        $expectedVCardSerialized = $expectedVCard->serialize();
320
+
321
+        $result = $this->invokePrivate($this->addressBookImpl, 'createEmptyVCard', [$uid]);
322
+        $resultSerialized = $result->serialize();
323
+
324
+        $this->assertSame($expectedVCardSerialized, $resultSerialized);
325
+    }
326
+
327
+    public function testVCard2Array(): void {
328
+        $vCard = new VCard();
329
+
330
+        $vCard->add($vCard->createProperty('FN', 'Full Name'));
331
+
332
+        // Multi-value properties
333
+        $vCard->add($vCard->createProperty('CLOUD', 'cloud-user1@localhost'));
334
+        $vCard->add($vCard->createProperty('CLOUD', '[email protected]'));
335
+        $vCard->add($vCard->createProperty('EMAIL', 'email-user1@localhost'));
336
+        $vCard->add($vCard->createProperty('EMAIL', '[email protected]'));
337
+        $vCard->add($vCard->createProperty('IMPP', 'impp-user1@localhost'));
338
+        $vCard->add($vCard->createProperty('IMPP', '[email protected]'));
339
+        $vCard->add($vCard->createProperty('TEL', '+49 123456789'));
340
+        $vCard->add($vCard->createProperty('TEL', '+1 555 123456789'));
341
+        $vCard->add($vCard->createProperty('URL', 'https://localhost'));
342
+        $vCard->add($vCard->createProperty('URL', 'https://example.tld'));
343
+
344
+        // Type depending properties
345
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example');
346
+        $property->add('TYPE', 'twitter');
347
+        $vCard->add($property);
348
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example-2');
349
+        $property->add('TYPE', 'twitter');
350
+        $vCard->add($property);
351
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'fb-example');
352
+        $property->add('TYPE', 'facebook');
353
+        $vCard->add($property);
354
+
355
+        $array = $this->invokePrivate($this->addressBookImpl, 'vCard2Array', ['uri', $vCard]);
356
+        unset($array['PRODID']);
357
+        unset($array['UID']);
358
+
359
+        $this->assertEquals([
360
+            'URI' => 'uri',
361
+            'VERSION' => '4.0',
362
+            'FN' => 'Full Name',
363
+            'CLOUD' => [
364
+                'cloud-user1@localhost',
365
+                '[email protected]',
366
+            ],
367
+            'EMAIL' => [
368
+                'email-user1@localhost',
369
+                '[email protected]',
370
+            ],
371
+            'IMPP' => [
372
+                'impp-user1@localhost',
373
+                '[email protected]',
374
+            ],
375
+            'TEL' => [
376
+                '+49 123456789',
377
+                '+1 555 123456789',
378
+            ],
379
+            'URL' => [
380
+                'https://localhost',
381
+                'https://example.tld',
382
+            ],
383
+
384
+            'X-SOCIALPROFILE' => [
385
+                'tw-example',
386
+                'tw-example-2',
387
+                'fb-example',
388
+            ],
389
+
390
+            'isLocalSystemBook' => true,
391
+        ], $array);
392
+    }
393
+
394
+    public function testVCard2ArrayWithTypes(): void {
395
+        $vCard = new VCard();
396
+
397
+        $vCard->add($vCard->createProperty('FN', 'Full Name'));
398
+
399
+        // Multi-value properties
400
+        $vCard->add($vCard->createProperty('CLOUD', 'cloud-user1@localhost'));
401
+        $vCard->add($vCard->createProperty('CLOUD', '[email protected]'));
402
+
403
+        $property = $vCard->createProperty('EMAIL', 'email-user1@localhost');
404
+        $property->add('TYPE', 'HOME');
405
+        $vCard->add($property);
406
+        $property = $vCard->createProperty('EMAIL', '[email protected]');
407
+        $property->add('TYPE', 'WORK');
408
+        $vCard->add($property);
409
+
410
+        $vCard->add($vCard->createProperty('IMPP', 'impp-user1@localhost'));
411
+        $vCard->add($vCard->createProperty('IMPP', '[email protected]'));
412
+
413
+        $property = $vCard->createProperty('TEL', '+49 123456789');
414
+        $property->add('TYPE', 'HOME,VOICE');
415
+        $vCard->add($property);
416
+        $property = $vCard->createProperty('TEL', '+1 555 123456789');
417
+        $property->add('TYPE', 'WORK');
418
+        $vCard->add($property);
419
+
420
+        $vCard->add($vCard->createProperty('URL', 'https://localhost'));
421
+        $vCard->add($vCard->createProperty('URL', 'https://example.tld'));
422
+
423
+        // Type depending properties
424
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example');
425
+        $property->add('TYPE', 'twitter');
426
+        $vCard->add($property);
427
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example-2');
428
+        $property->add('TYPE', 'twitter');
429
+        $vCard->add($property);
430
+        $property = $vCard->createProperty('X-SOCIALPROFILE', 'fb-example');
431
+        $property->add('TYPE', 'facebook');
432
+        $vCard->add($property);
433
+
434
+        $array = $this->invokePrivate($this->addressBookImpl, 'vCard2Array', ['uri', $vCard, true]);
435
+        unset($array['PRODID']);
436
+        unset($array['UID']);
437
+
438
+        $this->assertEquals([
439
+            'URI' => 'uri',
440
+            'VERSION' => '4.0',
441
+            'FN' => 'Full Name',
442
+            'CLOUD' => [
443
+                ['type' => '', 'value' => 'cloud-user1@localhost'],
444
+                ['type' => '', 'value' => '[email protected]'],
445
+            ],
446
+            'EMAIL' => [
447
+                ['type' => 'HOME', 'value' => 'email-user1@localhost'],
448
+                ['type' => 'WORK', 'value' => '[email protected]'],
449
+            ],
450
+            'IMPP' => [
451
+                ['type' => '', 'value' => 'impp-user1@localhost'],
452
+                ['type' => '', 'value' => '[email protected]'],
453
+            ],
454
+            'TEL' => [
455
+                ['type' => 'HOME,VOICE', 'value' => '+49 123456789'],
456
+                ['type' => 'WORK', 'value' => '+1 555 123456789'],
457
+            ],
458
+            'URL' => [
459
+                ['type' => '', 'value' => 'https://localhost'],
460
+                ['type' => '', 'value' => 'https://example.tld'],
461
+            ],
462
+
463
+            'X-SOCIALPROFILE' => [
464
+                ['type' => 'twitter', 'value' => 'tw-example'],
465
+                ['type' => 'twitter', 'value' => 'tw-example-2'],
466
+                ['type' => 'facebook', 'value' => 'fb-example'],
467
+            ],
468
+
469
+            'isLocalSystemBook' => true,
470
+        ], $array);
471
+    }
472
+
473
+    public function testIsSystemAddressBook(): void {
474
+        $addressBookInfo = [
475
+            '{http://owncloud.org/ns}owner-principal' => 'principals/system/system',
476
+            'principaluri' => 'principals/system/system',
477
+            '{DAV:}displayname' => 'display name',
478
+            'id' => 666,
479
+            'uri' => 'system',
480
+        ];
481
+
482
+        $addressBookImpl = new AddressBookImpl(
483
+            $this->addressBook,
484
+            $addressBookInfo,
485
+            $this->backend,
486
+            $this->urlGenerator,
487
+            $this->propertyMapper,
488
+            null
489
+        );
490
+
491
+        $this->assertTrue($addressBookImpl->isSystemAddressBook());
492
+    }
493
+
494
+    public function testIsShared(): void {
495
+        $addressBookInfo = [
496
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
497
+            '{DAV:}displayname' => 'Test address book',
498
+            'principaluri' => 'user2',
499
+            'id' => 666,
500
+            'uri' => 'default',
501
+        ];
502
+
503
+        $addressBookImpl = new AddressBookImpl(
504
+            $this->addressBook,
505
+            $addressBookInfo,
506
+            $this->backend,
507
+            $this->urlGenerator,
508
+            $this->propertyMapper,
509
+            'user2'
510
+        );
511
+
512
+        $this->assertFalse($addressBookImpl->isSystemAddressBook());
513
+        $this->assertTrue($addressBookImpl->isShared());
514
+    }
515
+
516
+    public function testIsNotShared(): void {
517
+        $addressBookInfo = [
518
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
519
+            '{DAV:}displayname' => 'Test address book',
520
+            'principaluri' => 'user1',
521
+            'id' => 666,
522
+            'uri' => 'default',
523
+        ];
524
+
525
+        $addressBookImpl = new AddressBookImpl(
526
+            $this->addressBook,
527
+            $addressBookInfo,
528
+            $this->backend,
529
+            $this->urlGenerator,
530
+            $this->propertyMapper,
531
+            'user2'
532
+        );
533
+
534
+        $this->assertFalse($addressBookImpl->isSystemAddressBook());
535
+        $this->assertFalse($addressBookImpl->isShared());
536
+    }
537 537
 }
Please login to merge, or discard this patch.