Passed
Push — master ( adef2b...4cda13 )
by John
17:12 queued 12s
created
apps/dav/lib/CardDAV/SyncService.php 1 patch
Indentation   +241 added lines, -241 removed lines patch added patch discarded remove patch
@@ -40,245 +40,245 @@
 block discarded – undo
40 40
 use Sabre\VObject\Reader;
41 41
 
42 42
 class SyncService {
43
-	private CardDavBackend $backend;
44
-	private IUserManager $userManager;
45
-	private LoggerInterface $logger;
46
-	private ?array $localSystemAddressBook = null;
47
-	private Converter $converter;
48
-	protected string $certPath;
49
-
50
-	public function __construct(CardDavBackend $backend,
51
-								IUserManager $userManager,
52
-								LoggerInterface $logger,
53
-								Converter $converter) {
54
-		$this->backend = $backend;
55
-		$this->userManager = $userManager;
56
-		$this->logger = $logger;
57
-		$this->converter = $converter;
58
-		$this->certPath = '';
59
-	}
60
-
61
-	/**
62
-	 * @throws \Exception
63
-	 */
64
-	public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string {
65
-		// 1. create addressbook
66
-		$book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties);
67
-		$addressBookId = $book['id'];
68
-
69
-		// 2. query changes
70
-		try {
71
-			$response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken);
72
-		} catch (ClientHttpException $ex) {
73
-			if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
74
-				// remote server revoked access to the address book, remove it
75
-				$this->backend->deleteAddressBook($addressBookId);
76
-				$this->logger->error('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
77
-				throw $ex;
78
-			}
79
-			$this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
80
-			throw $ex;
81
-		}
82
-
83
-		// 3. apply changes
84
-		// TODO: use multi-get for download
85
-		foreach ($response['response'] as $resource => $status) {
86
-			$cardUri = basename($resource);
87
-			if (isset($status[200])) {
88
-				$vCard = $this->download($url, $userName, $sharedSecret, $resource);
89
-				$existingCard = $this->backend->getCard($addressBookId, $cardUri);
90
-				if ($existingCard === false) {
91
-					$this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
92
-				} else {
93
-					$this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
94
-				}
95
-			} else {
96
-				$this->backend->deleteCard($addressBookId, $cardUri);
97
-			}
98
-		}
99
-
100
-		return $response['token'];
101
-	}
102
-
103
-	/**
104
-	 * @throws \Sabre\DAV\Exception\BadRequest
105
-	 */
106
-	public function ensureSystemAddressBookExists(string $principal, string $uri, array $properties): ?array {
107
-		$book = $this->backend->getAddressBooksByUri($principal, $uri);
108
-		if (!is_null($book)) {
109
-			return $book;
110
-		}
111
-		// FIXME This might break in clustered DB setup
112
-		$this->backend->createAddressBook($principal, $uri, $properties);
113
-
114
-		return $this->backend->getAddressBooksByUri($principal, $uri);
115
-	}
116
-
117
-	/**
118
-	 * Check if there is a valid certPath we should use
119
-	 */
120
-	protected function getCertPath(): string {
121
-
122
-		// we already have a valid certPath
123
-		if ($this->certPath !== '') {
124
-			return $this->certPath;
125
-		}
126
-
127
-		$certManager = \OC::$server->getCertificateManager();
128
-		$certPath = $certManager->getAbsoluteBundlePath();
129
-		if (file_exists($certPath)) {
130
-			$this->certPath = $certPath;
131
-		}
132
-
133
-		return $this->certPath;
134
-	}
135
-
136
-	protected function getClient(string $url, string $userName, string $sharedSecret): Client {
137
-		$settings = [
138
-			'baseUri' => $url . '/',
139
-			'userName' => $userName,
140
-			'password' => $sharedSecret,
141
-		];
142
-		$client = new Client($settings);
143
-		$certPath = $this->getCertPath();
144
-		$client->setThrowExceptions(true);
145
-
146
-		if ($certPath !== '' && strpos($url, 'http://') !== 0) {
147
-			$client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
148
-		}
149
-
150
-		return $client;
151
-	}
152
-
153
-	protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
154
-		$client = $this->getClient($url, $userName, $sharedSecret);
155
-
156
-		$body = $this->buildSyncCollectionRequestBody($syncToken);
157
-
158
-		$response = $client->request('REPORT', $addressBookUrl, $body, [
159
-			'Content-Type' => 'application/xml'
160
-		]);
161
-
162
-		return $this->parseMultiStatus($response['body']);
163
-	}
164
-
165
-	protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): array {
166
-		$client = $this->getClient($url, $userName, $sharedSecret);
167
-		return $client->request('GET', $resourcePath);
168
-	}
169
-
170
-	private function buildSyncCollectionRequestBody(?string $syncToken): string {
171
-		$dom = new \DOMDocument('1.0', 'UTF-8');
172
-		$dom->formatOutput = true;
173
-		$root = $dom->createElementNS('DAV:', 'd:sync-collection');
174
-		$sync = $dom->createElement('d:sync-token', $syncToken);
175
-		$prop = $dom->createElement('d:prop');
176
-		$cont = $dom->createElement('d:getcontenttype');
177
-		$etag = $dom->createElement('d:getetag');
178
-
179
-		$prop->appendChild($cont);
180
-		$prop->appendChild($etag);
181
-		$root->appendChild($sync);
182
-		$root->appendChild($prop);
183
-		$dom->appendChild($root);
184
-		return $dom->saveXML();
185
-	}
186
-
187
-	/**
188
-	 * @param string $body
189
-	 * @return array
190
-	 * @throws \Sabre\Xml\ParseException
191
-	 */
192
-	private function parseMultiStatus($body) {
193
-		$xml = new Service();
194
-
195
-		/** @var MultiStatus $multiStatus */
196
-		$multiStatus = $xml->expect('{DAV:}multistatus', $body);
197
-
198
-		$result = [];
199
-		foreach ($multiStatus->getResponses() as $response) {
200
-			$result[$response->getHref()] = $response->getResponseProperties();
201
-		}
202
-
203
-		return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
204
-	}
205
-
206
-	/**
207
-	 * @param IUser $user
208
-	 */
209
-	public function updateUser(IUser $user) {
210
-		$systemAddressBook = $this->getLocalSystemAddressBook();
211
-		$addressBookId = $systemAddressBook['id'];
212
-		$name = $user->getBackendClassName();
213
-		$userId = $user->getUID();
214
-
215
-		$cardId = "$name:$userId.vcf";
216
-		if ($user->isEnabled()) {
217
-			$card = $this->backend->getCard($addressBookId, $cardId);
218
-			if ($card === false) {
219
-				$vCard = $this->converter->createCardFromUser($user);
220
-				if ($vCard !== null) {
221
-					$this->backend->createCard($addressBookId, $cardId, $vCard->serialize(), false);
222
-				}
223
-			} else {
224
-				$vCard = $this->converter->createCardFromUser($user);
225
-				if (is_null($vCard)) {
226
-					$this->backend->deleteCard($addressBookId, $cardId);
227
-				} else {
228
-					$this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
229
-				}
230
-			}
231
-		} else {
232
-			$this->backend->deleteCard($addressBookId, $cardId);
233
-		}
234
-	}
235
-
236
-	/**
237
-	 * @param IUser|string $userOrCardId
238
-	 */
239
-	public function deleteUser($userOrCardId) {
240
-		$systemAddressBook = $this->getLocalSystemAddressBook();
241
-		if ($userOrCardId instanceof IUser) {
242
-			$name = $userOrCardId->getBackendClassName();
243
-			$userId = $userOrCardId->getUID();
244
-
245
-			$userOrCardId = "$name:$userId.vcf";
246
-		}
247
-		$this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
248
-	}
249
-
250
-	/**
251
-	 * @return array|null
252
-	 */
253
-	public function getLocalSystemAddressBook() {
254
-		if (is_null($this->localSystemAddressBook)) {
255
-			$systemPrincipal = "principals/system/system";
256
-			$this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
257
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
258
-			]);
259
-		}
260
-
261
-		return $this->localSystemAddressBook;
262
-	}
263
-
264
-	public function syncInstance(\Closure $progressCallback = null) {
265
-		$systemAddressBook = $this->getLocalSystemAddressBook();
266
-		$this->userManager->callForAllUsers(function ($user) use ($systemAddressBook, $progressCallback) {
267
-			$this->updateUser($user);
268
-			if (!is_null($progressCallback)) {
269
-				$progressCallback();
270
-			}
271
-		});
272
-
273
-		// remove no longer existing
274
-		$allCards = $this->backend->getCards($systemAddressBook['id']);
275
-		foreach ($allCards as $card) {
276
-			$vCard = Reader::read($card['carddata']);
277
-			$uid = isset($vCard->{'X-NEXTCLOUD-UID'}) ? $vCard->{'X-NEXTCLOUD-UID'}->getValue() : $vCard->UID->getValue();
278
-			// load backend and see if user exists
279
-			if (!$this->userManager->userExists($uid)) {
280
-				$this->deleteUser($card['uri']);
281
-			}
282
-		}
283
-	}
43
+    private CardDavBackend $backend;
44
+    private IUserManager $userManager;
45
+    private LoggerInterface $logger;
46
+    private ?array $localSystemAddressBook = null;
47
+    private Converter $converter;
48
+    protected string $certPath;
49
+
50
+    public function __construct(CardDavBackend $backend,
51
+                                IUserManager $userManager,
52
+                                LoggerInterface $logger,
53
+                                Converter $converter) {
54
+        $this->backend = $backend;
55
+        $this->userManager = $userManager;
56
+        $this->logger = $logger;
57
+        $this->converter = $converter;
58
+        $this->certPath = '';
59
+    }
60
+
61
+    /**
62
+     * @throws \Exception
63
+     */
64
+    public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string {
65
+        // 1. create addressbook
66
+        $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties);
67
+        $addressBookId = $book['id'];
68
+
69
+        // 2. query changes
70
+        try {
71
+            $response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken);
72
+        } catch (ClientHttpException $ex) {
73
+            if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
74
+                // remote server revoked access to the address book, remove it
75
+                $this->backend->deleteAddressBook($addressBookId);
76
+                $this->logger->error('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
77
+                throw $ex;
78
+            }
79
+            $this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
80
+            throw $ex;
81
+        }
82
+
83
+        // 3. apply changes
84
+        // TODO: use multi-get for download
85
+        foreach ($response['response'] as $resource => $status) {
86
+            $cardUri = basename($resource);
87
+            if (isset($status[200])) {
88
+                $vCard = $this->download($url, $userName, $sharedSecret, $resource);
89
+                $existingCard = $this->backend->getCard($addressBookId, $cardUri);
90
+                if ($existingCard === false) {
91
+                    $this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
92
+                } else {
93
+                    $this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
94
+                }
95
+            } else {
96
+                $this->backend->deleteCard($addressBookId, $cardUri);
97
+            }
98
+        }
99
+
100
+        return $response['token'];
101
+    }
102
+
103
+    /**
104
+     * @throws \Sabre\DAV\Exception\BadRequest
105
+     */
106
+    public function ensureSystemAddressBookExists(string $principal, string $uri, array $properties): ?array {
107
+        $book = $this->backend->getAddressBooksByUri($principal, $uri);
108
+        if (!is_null($book)) {
109
+            return $book;
110
+        }
111
+        // FIXME This might break in clustered DB setup
112
+        $this->backend->createAddressBook($principal, $uri, $properties);
113
+
114
+        return $this->backend->getAddressBooksByUri($principal, $uri);
115
+    }
116
+
117
+    /**
118
+     * Check if there is a valid certPath we should use
119
+     */
120
+    protected function getCertPath(): string {
121
+
122
+        // we already have a valid certPath
123
+        if ($this->certPath !== '') {
124
+            return $this->certPath;
125
+        }
126
+
127
+        $certManager = \OC::$server->getCertificateManager();
128
+        $certPath = $certManager->getAbsoluteBundlePath();
129
+        if (file_exists($certPath)) {
130
+            $this->certPath = $certPath;
131
+        }
132
+
133
+        return $this->certPath;
134
+    }
135
+
136
+    protected function getClient(string $url, string $userName, string $sharedSecret): Client {
137
+        $settings = [
138
+            'baseUri' => $url . '/',
139
+            'userName' => $userName,
140
+            'password' => $sharedSecret,
141
+        ];
142
+        $client = new Client($settings);
143
+        $certPath = $this->getCertPath();
144
+        $client->setThrowExceptions(true);
145
+
146
+        if ($certPath !== '' && strpos($url, 'http://') !== 0) {
147
+            $client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
148
+        }
149
+
150
+        return $client;
151
+    }
152
+
153
+    protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
154
+        $client = $this->getClient($url, $userName, $sharedSecret);
155
+
156
+        $body = $this->buildSyncCollectionRequestBody($syncToken);
157
+
158
+        $response = $client->request('REPORT', $addressBookUrl, $body, [
159
+            'Content-Type' => 'application/xml'
160
+        ]);
161
+
162
+        return $this->parseMultiStatus($response['body']);
163
+    }
164
+
165
+    protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): array {
166
+        $client = $this->getClient($url, $userName, $sharedSecret);
167
+        return $client->request('GET', $resourcePath);
168
+    }
169
+
170
+    private function buildSyncCollectionRequestBody(?string $syncToken): string {
171
+        $dom = new \DOMDocument('1.0', 'UTF-8');
172
+        $dom->formatOutput = true;
173
+        $root = $dom->createElementNS('DAV:', 'd:sync-collection');
174
+        $sync = $dom->createElement('d:sync-token', $syncToken);
175
+        $prop = $dom->createElement('d:prop');
176
+        $cont = $dom->createElement('d:getcontenttype');
177
+        $etag = $dom->createElement('d:getetag');
178
+
179
+        $prop->appendChild($cont);
180
+        $prop->appendChild($etag);
181
+        $root->appendChild($sync);
182
+        $root->appendChild($prop);
183
+        $dom->appendChild($root);
184
+        return $dom->saveXML();
185
+    }
186
+
187
+    /**
188
+     * @param string $body
189
+     * @return array
190
+     * @throws \Sabre\Xml\ParseException
191
+     */
192
+    private function parseMultiStatus($body) {
193
+        $xml = new Service();
194
+
195
+        /** @var MultiStatus $multiStatus */
196
+        $multiStatus = $xml->expect('{DAV:}multistatus', $body);
197
+
198
+        $result = [];
199
+        foreach ($multiStatus->getResponses() as $response) {
200
+            $result[$response->getHref()] = $response->getResponseProperties();
201
+        }
202
+
203
+        return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
204
+    }
205
+
206
+    /**
207
+     * @param IUser $user
208
+     */
209
+    public function updateUser(IUser $user) {
210
+        $systemAddressBook = $this->getLocalSystemAddressBook();
211
+        $addressBookId = $systemAddressBook['id'];
212
+        $name = $user->getBackendClassName();
213
+        $userId = $user->getUID();
214
+
215
+        $cardId = "$name:$userId.vcf";
216
+        if ($user->isEnabled()) {
217
+            $card = $this->backend->getCard($addressBookId, $cardId);
218
+            if ($card === false) {
219
+                $vCard = $this->converter->createCardFromUser($user);
220
+                if ($vCard !== null) {
221
+                    $this->backend->createCard($addressBookId, $cardId, $vCard->serialize(), false);
222
+                }
223
+            } else {
224
+                $vCard = $this->converter->createCardFromUser($user);
225
+                if (is_null($vCard)) {
226
+                    $this->backend->deleteCard($addressBookId, $cardId);
227
+                } else {
228
+                    $this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
229
+                }
230
+            }
231
+        } else {
232
+            $this->backend->deleteCard($addressBookId, $cardId);
233
+        }
234
+    }
235
+
236
+    /**
237
+     * @param IUser|string $userOrCardId
238
+     */
239
+    public function deleteUser($userOrCardId) {
240
+        $systemAddressBook = $this->getLocalSystemAddressBook();
241
+        if ($userOrCardId instanceof IUser) {
242
+            $name = $userOrCardId->getBackendClassName();
243
+            $userId = $userOrCardId->getUID();
244
+
245
+            $userOrCardId = "$name:$userId.vcf";
246
+        }
247
+        $this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
248
+    }
249
+
250
+    /**
251
+     * @return array|null
252
+     */
253
+    public function getLocalSystemAddressBook() {
254
+        if (is_null($this->localSystemAddressBook)) {
255
+            $systemPrincipal = "principals/system/system";
256
+            $this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
257
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
258
+            ]);
259
+        }
260
+
261
+        return $this->localSystemAddressBook;
262
+    }
263
+
264
+    public function syncInstance(\Closure $progressCallback = null) {
265
+        $systemAddressBook = $this->getLocalSystemAddressBook();
266
+        $this->userManager->callForAllUsers(function ($user) use ($systemAddressBook, $progressCallback) {
267
+            $this->updateUser($user);
268
+            if (!is_null($progressCallback)) {
269
+                $progressCallback();
270
+            }
271
+        });
272
+
273
+        // remove no longer existing
274
+        $allCards = $this->backend->getCards($systemAddressBook['id']);
275
+        foreach ($allCards as $card) {
276
+            $vCard = Reader::read($card['carddata']);
277
+            $uid = isset($vCard->{'X-NEXTCLOUD-UID'}) ? $vCard->{'X-NEXTCLOUD-UID'}->getValue() : $vCard->UID->getValue();
278
+            // load backend and see if user exists
279
+            if (!$this->userManager->userExists($uid)) {
280
+                $this->deleteUser($card['uri']);
281
+            }
282
+        }
283
+    }
284 284
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/Converter.php 1 patch
Indentation   +96 added lines, -96 removed lines patch added patch discarded remove patch
@@ -36,100 +36,100 @@
 block discarded – undo
36 36
 
37 37
 class Converter {
38 38
 
39
-	/** @var IAccountManager */
40
-	private $accountManager;
41
-
42
-	public function __construct(IAccountManager $accountManager) {
43
-		$this->accountManager = $accountManager;
44
-	}
45
-
46
-	public function createCardFromUser(IUser $user): ?VCard {
47
-		$userProperties = $this->accountManager->getAccount($user)->getProperties();
48
-
49
-		$uid = $user->getUID();
50
-		$backendClassName = $user->getBackendClassName();
51
-		$cloudId = $user->getCloudId();
52
-		$image = $this->getAvatarImage($user);
53
-
54
-		$vCard = new VCard();
55
-		$vCard->VERSION = '3.0';
56
-		$vCard->UID = md5("$backendClassName:$uid");
57
-	 	$vCard->add(new Text($vCard, 'X-NEXTCLOUD-UID', $uid));
58
-
59
-		$publish = false;
60
-
61
-		foreach ($userProperties as $property) {
62
-			$shareWithTrustedServers =
63
-				$property->getScope() === IAccountManager::SCOPE_FEDERATED ||
64
-				$property->getScope() === IAccountManager::SCOPE_PUBLISHED;
65
-
66
-			$emptyValue = $property->getValue() === '';
67
-
68
-			if ($shareWithTrustedServers && !$emptyValue) {
69
-				$publish = true;
70
-				switch ($property->getName()) {
71
-					case IAccountManager::PROPERTY_DISPLAYNAME:
72
-						$vCard->add(new Text($vCard, 'FN', $property->getValue()));
73
-						$vCard->add(new Text($vCard, 'N', $this->splitFullName($property->getValue())));
74
-						break;
75
-					case IAccountManager::PROPERTY_AVATAR:
76
-						if ($image !== null) {
77
-							$vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
78
-						}
79
-						break;
80
-					case IAccountManager::PROPERTY_EMAIL:
81
-						$vCard->add(new Text($vCard, 'EMAIL', $property->getValue(), ['TYPE' => 'OTHER']));
82
-						break;
83
-					case IAccountManager::PROPERTY_WEBSITE:
84
-						$vCard->add(new Text($vCard, 'URL', $property->getValue()));
85
-						break;
86
-					case IAccountManager::PROPERTY_PHONE:
87
-						$vCard->add(new Text($vCard, 'TEL', $property->getValue(), ['TYPE' => 'OTHER']));
88
-						break;
89
-					case IAccountManager::PROPERTY_ADDRESS:
90
-						$vCard->add(new Text($vCard, 'ADR', $property->getValue(), ['TYPE' => 'OTHER']));
91
-						break;
92
-					case IAccountManager::PROPERTY_TWITTER:
93
-						$vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $property->getValue(), ['TYPE' => 'TWITTER']));
94
-						break;
95
-				}
96
-			}
97
-		}
98
-
99
-		if ($publish && !empty($cloudId)) {
100
-			$vCard->add(new Text($vCard, 'CLOUD', $cloudId));
101
-			$vCard->validate();
102
-			return $vCard;
103
-		}
104
-
105
-		return null;
106
-	}
107
-
108
-	public function splitFullName(string $fullName): array {
109
-		// Very basic western style parsing. I'm not gonna implement
110
-		// https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;)
111
-
112
-		$elements = explode(' ', $fullName);
113
-		$result = ['', '', '', '', ''];
114
-		if (count($elements) > 2) {
115
-			$result[0] = implode(' ', array_slice($elements, count($elements) - 1));
116
-			$result[1] = $elements[0];
117
-			$result[2] = implode(' ', array_slice($elements, 1, count($elements) - 2));
118
-		} elseif (count($elements) === 2) {
119
-			$result[0] = $elements[1];
120
-			$result[1] = $elements[0];
121
-		} else {
122
-			$result[0] = $elements[0];
123
-		}
124
-
125
-		return $result;
126
-	}
127
-
128
-	private function getAvatarImage(IUser $user): ?IImage {
129
-		try {
130
-			return $user->getAvatarImage(-1);
131
-		} catch (Exception $ex) {
132
-			return null;
133
-		}
134
-	}
39
+    /** @var IAccountManager */
40
+    private $accountManager;
41
+
42
+    public function __construct(IAccountManager $accountManager) {
43
+        $this->accountManager = $accountManager;
44
+    }
45
+
46
+    public function createCardFromUser(IUser $user): ?VCard {
47
+        $userProperties = $this->accountManager->getAccount($user)->getProperties();
48
+
49
+        $uid = $user->getUID();
50
+        $backendClassName = $user->getBackendClassName();
51
+        $cloudId = $user->getCloudId();
52
+        $image = $this->getAvatarImage($user);
53
+
54
+        $vCard = new VCard();
55
+        $vCard->VERSION = '3.0';
56
+        $vCard->UID = md5("$backendClassName:$uid");
57
+            $vCard->add(new Text($vCard, 'X-NEXTCLOUD-UID', $uid));
58
+
59
+        $publish = false;
60
+
61
+        foreach ($userProperties as $property) {
62
+            $shareWithTrustedServers =
63
+                $property->getScope() === IAccountManager::SCOPE_FEDERATED ||
64
+                $property->getScope() === IAccountManager::SCOPE_PUBLISHED;
65
+
66
+            $emptyValue = $property->getValue() === '';
67
+
68
+            if ($shareWithTrustedServers && !$emptyValue) {
69
+                $publish = true;
70
+                switch ($property->getName()) {
71
+                    case IAccountManager::PROPERTY_DISPLAYNAME:
72
+                        $vCard->add(new Text($vCard, 'FN', $property->getValue()));
73
+                        $vCard->add(new Text($vCard, 'N', $this->splitFullName($property->getValue())));
74
+                        break;
75
+                    case IAccountManager::PROPERTY_AVATAR:
76
+                        if ($image !== null) {
77
+                            $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
78
+                        }
79
+                        break;
80
+                    case IAccountManager::PROPERTY_EMAIL:
81
+                        $vCard->add(new Text($vCard, 'EMAIL', $property->getValue(), ['TYPE' => 'OTHER']));
82
+                        break;
83
+                    case IAccountManager::PROPERTY_WEBSITE:
84
+                        $vCard->add(new Text($vCard, 'URL', $property->getValue()));
85
+                        break;
86
+                    case IAccountManager::PROPERTY_PHONE:
87
+                        $vCard->add(new Text($vCard, 'TEL', $property->getValue(), ['TYPE' => 'OTHER']));
88
+                        break;
89
+                    case IAccountManager::PROPERTY_ADDRESS:
90
+                        $vCard->add(new Text($vCard, 'ADR', $property->getValue(), ['TYPE' => 'OTHER']));
91
+                        break;
92
+                    case IAccountManager::PROPERTY_TWITTER:
93
+                        $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $property->getValue(), ['TYPE' => 'TWITTER']));
94
+                        break;
95
+                }
96
+            }
97
+        }
98
+
99
+        if ($publish && !empty($cloudId)) {
100
+            $vCard->add(new Text($vCard, 'CLOUD', $cloudId));
101
+            $vCard->validate();
102
+            return $vCard;
103
+        }
104
+
105
+        return null;
106
+    }
107
+
108
+    public function splitFullName(string $fullName): array {
109
+        // Very basic western style parsing. I'm not gonna implement
110
+        // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;)
111
+
112
+        $elements = explode(' ', $fullName);
113
+        $result = ['', '', '', '', ''];
114
+        if (count($elements) > 2) {
115
+            $result[0] = implode(' ', array_slice($elements, count($elements) - 1));
116
+            $result[1] = $elements[0];
117
+            $result[2] = implode(' ', array_slice($elements, 1, count($elements) - 2));
118
+        } elseif (count($elements) === 2) {
119
+            $result[0] = $elements[1];
120
+            $result[1] = $elements[0];
121
+        } else {
122
+            $result[0] = $elements[0];
123
+        }
124
+
125
+        return $result;
126
+    }
127
+
128
+    private function getAvatarImage(IUser $user): ?IImage {
129
+        try {
130
+            return $user->getAvatarImage(-1);
131
+        } catch (Exception $ex) {
132
+            return null;
133
+        }
134
+    }
135 135
 }
Please login to merge, or discard this patch.