1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Circles - Bring cloud-users closer together. |
8
|
|
|
* |
9
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
10
|
|
|
* later. See the COPYING file. |
11
|
|
|
* |
12
|
|
|
* @author Maxence Lange <[email protected]> |
13
|
|
|
* @copyright 2021 |
14
|
|
|
* @license GNU AGPL version 3 or any later version |
15
|
|
|
* |
16
|
|
|
* This program is free software: you can redistribute it and/or modify |
17
|
|
|
* it under the terms of the GNU Affero General Public License as |
18
|
|
|
* published by the Free Software Foundation, either version 3 of the |
19
|
|
|
* License, or (at your option) any later version. |
20
|
|
|
* |
21
|
|
|
* This program is distributed in the hope that it will be useful, |
22
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
23
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24
|
|
|
* GNU Affero General Public License for more details. |
25
|
|
|
* |
26
|
|
|
* You should have received a copy of the GNU Affero General Public License |
27
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
28
|
|
|
* |
29
|
|
|
*/ |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
namespace OCA\Circles\Service; |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
use ArtificialOwl\MySmallPhpTools\Model\SimpleDataStore; |
36
|
|
|
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; |
37
|
|
|
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; |
38
|
|
|
use Exception; |
39
|
|
|
use OC; |
40
|
|
|
use OCA\Circles\AppInfo\Application; |
41
|
|
|
use OCA\Circles\Db\CircleRequest; |
42
|
|
|
use OCA\Circles\Db\MemberRequest; |
43
|
|
|
use OCA\Circles\Exceptions\CircleNotFoundException; |
44
|
|
|
use OCA\Circles\Exceptions\ContactAddressBookNotFoundException; |
45
|
|
|
use OCA\Circles\Exceptions\ContactFormatException; |
46
|
|
|
use OCA\Circles\Exceptions\ContactNotFoundException; |
47
|
|
|
use OCA\Circles\Exceptions\FederatedItemException; |
48
|
|
|
use OCA\Circles\Exceptions\FederatedUserException; |
49
|
|
|
use OCA\Circles\Exceptions\FederatedUserNotFoundException; |
50
|
|
|
use OCA\Circles\Exceptions\InvalidIdException; |
51
|
|
|
use OCA\Circles\Exceptions\MemberNotFoundException; |
52
|
|
|
use OCA\Circles\Exceptions\MigrationException; |
53
|
|
|
use OCA\Circles\Exceptions\OwnerNotFoundException; |
54
|
|
|
use OCA\Circles\Exceptions\RemoteInstanceException; |
55
|
|
|
use OCA\Circles\Exceptions\RemoteNotFoundException; |
56
|
|
|
use OCA\Circles\Exceptions\RemoteResourceNotFoundException; |
57
|
|
|
use OCA\Circles\Exceptions\RequestBuilderException; |
58
|
|
|
use OCA\Circles\Exceptions\SingleCircleNotFoundException; |
59
|
|
|
use OCA\Circles\Exceptions\UnknownRemoteException; |
60
|
|
|
use OCA\Circles\Exceptions\UserTypeNotFoundException; |
61
|
|
|
use OCA\Circles\Model\Circle; |
62
|
|
|
use OCA\Circles\Model\FederatedUser; |
63
|
|
|
use OCA\Circles\Model\Member; |
64
|
|
|
use OCA\DAV\CardDAV\ContactsManager; |
65
|
|
|
use OCP\Contacts\IManager; |
66
|
|
|
use OCP\IDBConnection; |
67
|
|
|
use OCP\IURLGenerator; |
68
|
|
|
|
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Class MigrationService |
72
|
|
|
* |
73
|
|
|
* @package OCA\Circles\Service |
74
|
|
|
*/ |
75
|
|
|
class MigrationService { |
76
|
|
|
|
77
|
|
|
|
78
|
|
|
use TStringTools; |
79
|
|
|
use TNC22Logger; |
80
|
|
|
|
81
|
|
|
|
82
|
|
|
/** @var IDBConnection */ |
83
|
|
|
private $dbConnection; |
84
|
|
|
|
85
|
|
|
/** @var IURLGenerator */ |
86
|
|
|
private $urlGenerator; |
87
|
|
|
|
88
|
|
|
/** @var CircleRequest */ |
89
|
|
|
private $circleRequest; |
90
|
|
|
|
91
|
|
|
/** @var MemberRequest */ |
92
|
|
|
private $memberRequest; |
93
|
|
|
|
94
|
|
|
/** @var SyncService */ |
95
|
|
|
private $syncService; |
96
|
|
|
|
97
|
|
|
/** @var MembershipService */ |
98
|
|
|
private $membershipService; |
99
|
|
|
|
100
|
|
|
/** @var FederatedUserService */ |
101
|
|
|
private $federatedUserService; |
102
|
|
|
|
103
|
|
|
/** @var CircleService */ |
104
|
|
|
private $circleService; |
105
|
|
|
|
106
|
|
|
/** @var ContactService */ |
107
|
|
|
private $contactService; |
108
|
|
|
|
109
|
|
|
/** @var TimezoneService */ |
110
|
|
|
private $timezoneService; |
111
|
|
|
|
112
|
|
|
/** @var OutputService */ |
113
|
|
|
private $outputService; |
114
|
|
|
|
115
|
|
|
/** @var ConfigService */ |
116
|
|
|
private $configService; |
117
|
|
|
|
118
|
|
|
|
119
|
|
|
/** @var FederatedUser */ |
120
|
|
|
private $appCircle = null; |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* MigrationService constructor. |
125
|
|
|
* |
126
|
|
|
* @param IDBConnection $dbConnection |
127
|
|
|
* @param IURLGenerator $urlGenerator |
128
|
|
|
* @param CircleRequest $circleRequest |
129
|
|
|
* @param MemberRequest $memberRequest |
130
|
|
|
* @param FederatedUserService $federatedUserService |
131
|
|
|
* @param CircleService $circleService |
132
|
|
|
* @param ContactService $contactService |
133
|
|
|
* @param TimezoneService $timezoneService |
134
|
|
|
* @param OutputService $outputService |
135
|
|
|
* @param ConfigService $configService |
136
|
|
|
*/ |
137
|
|
|
public function __construct( |
138
|
|
|
IDBConnection $dbConnection, |
139
|
|
|
IURLGenerator $urlGenerator, |
140
|
|
|
CircleRequest $circleRequest, |
141
|
|
|
MemberRequest $memberRequest, |
142
|
|
|
SyncService $syncService, |
143
|
|
|
MembershipService $membershipService, |
144
|
|
|
FederatedUserService $federatedUserService, |
145
|
|
|
CircleService $circleService, |
146
|
|
|
ContactService $contactService, |
147
|
|
|
TimezoneService $timezoneService, |
148
|
|
|
OutputService $outputService, |
149
|
|
|
ConfigService $configService |
150
|
|
|
) { |
151
|
|
|
$this->dbConnection = $dbConnection; |
152
|
|
|
$this->urlGenerator = $urlGenerator; |
153
|
|
|
$this->circleRequest = $circleRequest; |
154
|
|
|
$this->memberRequest = $memberRequest; |
155
|
|
|
$this->syncService = $syncService; |
156
|
|
|
$this->membershipService = $membershipService; |
157
|
|
|
$this->federatedUserService = $federatedUserService; |
158
|
|
|
$this->circleService = $circleService; |
159
|
|
|
$this->contactService = $contactService; |
160
|
|
|
$this->timezoneService = $timezoneService; |
161
|
|
|
$this->outputService = $outputService; |
162
|
|
|
$this->configService = $configService; |
163
|
|
|
|
164
|
|
|
$this->setup('app', Application::APP_ID); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @param bool $force |
170
|
|
|
* |
171
|
|
|
* @throws ContactAddressBookNotFoundException |
172
|
|
|
* @throws ContactFormatException |
173
|
|
|
* @throws ContactNotFoundException |
174
|
|
|
* @throws FederatedUserException |
175
|
|
|
* @throws InvalidIdException |
176
|
|
|
* @throws MigrationException |
177
|
|
|
* @throws RequestBuilderException |
178
|
|
|
* @throws SingleCircleNotFoundException |
179
|
|
|
*/ |
180
|
|
|
public function migration(bool $force = false): void { |
181
|
|
|
if ($this->configService->getAppValueBool(ConfigService::MIGRATION_RUN)) { |
182
|
|
|
throw new MigrationException('A migration process is already running'); |
183
|
|
|
} |
184
|
|
|
$this->configService->setAppValue(ConfigService::MIGRATION_RUN, '1'); |
185
|
|
|
|
186
|
|
|
if ($force) { |
187
|
|
|
$this->configService->setAppValue(ConfigService::MIGRATION_22, '0'); |
188
|
|
|
// $this->configService->setAppValue(ConfigService::MIGRATION_23, '0'); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$this->appCircle = $this->federatedUserService->getAppInitiator( |
192
|
|
|
Application::APP_ID, |
193
|
|
|
Member::APP_CIRCLES |
194
|
|
|
); |
195
|
|
|
|
196
|
|
|
$this->migrationTo22(); |
197
|
|
|
// $this->migrationTo23(); |
198
|
|
|
|
199
|
|
|
$this->configService->setAppValue(ConfigService::MIGRATION_RUN, '0'); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* |
205
|
|
|
*/ |
206
|
|
|
private function migrationTo22(): void { |
207
|
|
|
if ($this->configService->getAppValueBool(ConfigService::MIGRATION_22)) { |
208
|
|
|
return; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
$this->outputService->output('Migrating to 22'); |
212
|
|
|
|
213
|
|
|
$this->syncService->sync(); |
214
|
|
|
$this->migrationTo22_Circles(); |
215
|
|
|
$this->migrationTo22_Members(); |
216
|
|
|
$this->membershipService->manageAll(); |
217
|
|
|
|
218
|
|
|
$this->configService->setAppValue(ConfigService::MIGRATION_22, '1'); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
|
222
|
|
View Code Duplication |
private function migrationTo22_Circles(): void { |
|
|
|
|
223
|
|
|
$qb = $this->dbConnection->getQueryBuilder(); |
224
|
|
|
$qb->select('*')->from('circle_circles'); |
225
|
|
|
|
226
|
|
|
try { |
227
|
|
|
$cursor = $qb->executeQuery(); |
228
|
|
|
$this->outputService->startMigrationProgress($cursor->rowCount()); |
229
|
|
|
|
230
|
|
|
while ($row = $cursor->fetch()) { |
231
|
|
|
try { |
232
|
|
|
$data = new SimpleDataStore($row); |
233
|
|
|
$this->outputService->output( |
234
|
|
|
'Migrating Circle \'' . $data->g('name') . '\'', |
235
|
|
|
true |
236
|
|
|
); |
237
|
|
|
|
238
|
|
|
$circle = $this->generateCircleFrom21($data); |
239
|
|
|
$this->saveGeneratedCircle($circle); |
240
|
|
|
} catch (Exception $e) { |
|
|
|
|
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
$cursor->closeCursor(); |
245
|
|
|
} catch (\OCP\DB\Exception $e) { |
|
|
|
|
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$this->outputService->finishMigrationProgress(); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @param SimpleDataStore $data |
254
|
|
|
* |
255
|
|
|
* @return Circle |
256
|
|
|
* @throws RequestBuilderException |
257
|
|
|
*/ |
258
|
|
|
private function generateCircleFrom21(SimpleDataStore $data): Circle { |
259
|
|
|
$circle = new Circle(); |
260
|
|
|
$circle->setSingleId($data->g('unique_id')) |
261
|
|
|
->setName($data->g('name')) |
262
|
|
|
->setDisplayName($data->g('display_name')) |
263
|
|
|
->setSettings($data->gArray('settings')) |
264
|
|
|
->setDescription($data->g('description')) |
265
|
|
|
->setContactAddressBook($data->gInt('contact_addressbook')) |
266
|
|
|
->setContactGroupName($data->g('contact_groupname')) |
267
|
|
|
->setSource(Member::TYPE_CIRCLE); |
268
|
|
|
|
269
|
|
|
$dTime = $this->timezoneService->getDateTime($data->g('creation')); |
270
|
|
|
$circle->setCreation($dTime->getTimestamp()); |
271
|
|
|
|
272
|
|
|
if ($circle->getDisplayName() === '') { |
273
|
|
|
$circle->setDisplayName($circle->getName()); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
$this->circleService->generateSanitizedName($circle); |
277
|
|
|
$this->convertCircleTypeFrom21($circle, $data->gInt('type')); |
278
|
|
|
|
279
|
|
|
return $circle; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @param Circle $circle |
285
|
|
|
* @param int $type |
286
|
|
|
*/ |
287
|
|
|
private function convertCircleTypeFrom21(Circle $circle, int $type): void { |
288
|
|
|
switch ($type) { |
289
|
|
|
case 1: // personal |
290
|
|
|
$circle->setConfig(Circle::CFG_PERSONAL); |
291
|
|
|
break; |
292
|
|
|
|
293
|
|
|
case 2: // secret |
294
|
|
|
$circle->setConfig(Circle::CFG_CIRCLE); |
295
|
|
|
break; |
296
|
|
|
|
297
|
|
|
case 4: // closed |
298
|
|
|
$circle->setConfig(Circle::CFG_OPEN + Circle::CFG_REQUEST); |
299
|
|
|
break; |
300
|
|
|
|
301
|
|
|
case 8: // public |
302
|
|
|
$circle->setConfig(Circle::CFG_OPEN); |
303
|
|
|
break; |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* @param Circle $circle |
310
|
|
|
*/ |
311
|
|
|
private function saveGeneratedCircle(Circle $circle): void { |
312
|
|
|
try { |
313
|
|
|
$this->circleRequest->getCircle($circle->getSingleId()); |
314
|
|
|
} catch (CircleNotFoundException $e) { |
315
|
|
|
try { |
316
|
|
|
$this->circleRequest->save($circle); |
317
|
|
|
} catch (InvalidIdException $e) { |
|
|
|
|
318
|
|
|
} |
319
|
|
|
usleep(50); |
320
|
|
|
} catch (RequestBuilderException $e) { |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
*/ |
327
|
|
View Code Duplication |
private function migrationTo22_Members(): void { |
|
|
|
|
328
|
|
|
$qb = $this->dbConnection->getQueryBuilder(); |
329
|
|
|
$qb->select('*')->from('circle_members'); |
330
|
|
|
|
331
|
|
|
try { |
332
|
|
|
$cursor = $qb->executeQuery(); |
333
|
|
|
$this->outputService->startMigrationProgress($cursor->rowCount()); |
334
|
|
|
|
335
|
|
|
while ($row = $cursor->fetch()) { |
336
|
|
|
try { |
337
|
|
|
$data = new SimpleDataStore($row); |
338
|
|
|
$this->outputService->output( |
339
|
|
|
'Migrating Member \'' . $data->g('user_id') . '\'', |
340
|
|
|
true |
341
|
|
|
); |
342
|
|
|
|
343
|
|
|
$member = $this->generateMemberFrom21($data); |
344
|
|
|
$this->saveGeneratedMember($member); |
345
|
|
|
} catch (Exception $e) { |
|
|
|
|
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
$cursor->closeCursor(); |
350
|
|
|
} catch (\OCP\DB\Exception $e) { |
|
|
|
|
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
$this->outputService->finishMigrationProgress(); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* @throws CircleNotFoundException |
359
|
|
|
* @throws RemoteInstanceException |
360
|
|
|
* @throws UserTypeNotFoundException |
361
|
|
|
* @throws FederatedUserNotFoundException |
362
|
|
|
* @throws OwnerNotFoundException |
363
|
|
|
* @throws RequestBuilderException |
364
|
|
|
* @throws RemoteNotFoundException |
365
|
|
|
* @throws UnknownRemoteException |
366
|
|
|
* @throws FederatedUserException |
367
|
|
|
* @throws ContactAddressBookNotFoundException |
368
|
|
|
* @throws RemoteResourceNotFoundException |
369
|
|
|
* @throws MemberNotFoundException |
370
|
|
|
* @throws FederatedItemException |
371
|
|
|
* @throws SingleCircleNotFoundException |
372
|
|
|
* @throws InvalidIdException |
373
|
|
|
*/ |
374
|
|
|
private function generateMemberFrom21(SimpleDataStore $data): Member { |
375
|
|
|
$member = new Member(); |
376
|
|
|
|
377
|
|
|
$member->setCircleId($data->g('circle_id')) |
378
|
|
|
->setId($data->g('member_id')) |
379
|
|
|
->setUserId($data->g('user_id')) |
380
|
|
|
->setInstance($data->g('instance')) |
381
|
|
|
->setDisplayName($data->g('cached_name')) |
382
|
|
|
->setLevel($data->gInt('level')) |
383
|
|
|
->setStatus($data->g('status')) |
384
|
|
|
->setContactMeta($data->g('contact_meta')) |
385
|
|
|
->setContactId($data->g('contact_id')) |
386
|
|
|
->setInvitedBy($this->appCircle); |
387
|
|
|
|
388
|
|
|
$this->convertMemberUserTypeFrom21($member, $data->gInt('user_type')); |
389
|
|
|
|
390
|
|
|
$singleMember = $this->federatedUserService->getFederatedUser( |
391
|
|
|
$member->getUserId(), |
392
|
|
|
$member->getUserType() |
393
|
|
|
); |
394
|
|
|
|
395
|
|
|
$member->setSingleId($singleMember->getSingleId()); |
396
|
|
|
|
397
|
|
|
// "cached_update":"2021-05-02 12:13:22", |
398
|
|
|
// "joined":"2021-05-02 12:13:22", |
399
|
|
|
// "contact_checked":null," |
400
|
|
|
// single_id":"wt6WQYYCry3EOud", |
401
|
|
|
// "circle_source":null} |
402
|
|
|
|
403
|
|
|
return $member; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* @param Member $member |
409
|
|
|
* @param int $userType |
410
|
|
|
* |
411
|
|
|
* @throws ContactAddressBookNotFoundException |
412
|
|
|
*/ |
413
|
|
|
private function convertMemberUserTypeFrom21(Member $member, int $userType): void { |
414
|
|
|
switch ($userType) { |
415
|
|
|
case 1: |
416
|
|
|
$member->setUserType(1); |
417
|
|
|
|
418
|
|
|
return; |
419
|
|
|
case 2: |
420
|
|
|
$member->setUserType(2); |
421
|
|
|
|
422
|
|
|
return; |
423
|
|
|
case 3: |
424
|
|
|
$member->setUserType(4); |
425
|
|
|
|
426
|
|
|
return; |
427
|
|
|
case 4: |
428
|
|
|
$member->setUserType(8); |
429
|
|
|
$this->fixContactId($member); |
430
|
|
|
|
431
|
|
|
return; |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* @param Member $member |
438
|
|
|
* |
439
|
|
|
* @throws ContactAddressBookNotFoundException |
440
|
|
|
*/ |
441
|
|
|
private function fixContactId(Member $member) { |
442
|
|
|
list($userId, $contactId) = explode(':', $member->getUserId()); |
443
|
|
|
|
444
|
|
|
$contactsManager = OC::$server->get(ContactsManager::class); |
445
|
|
|
|
446
|
|
|
/** @var IManager $cm */ |
447
|
|
|
$cm = OC::$server->get(IManager::class); |
448
|
|
|
$contactsManager->setupContactsProvider($cm, $userId, $this->urlGenerator); |
449
|
|
|
|
450
|
|
|
$contact = $cm->search($contactId, ['UID']); |
451
|
|
|
if (sizeof($contact) === 1) { |
452
|
|
|
$entry = array_shift($contact); |
453
|
|
|
$addressBook = |
454
|
|
|
$this->contactService->getAddressBoxById($cm, $this->get('addressbook-key', $entry)); |
455
|
|
|
|
456
|
|
|
$member->setUserId($userId . '/' . $addressBook->getUri() . '/' . $contactId); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* @param Member $member |
463
|
|
|
*/ |
464
|
|
|
private function saveGeneratedMember(Member $member): void { |
465
|
|
|
try { |
466
|
|
|
$this->memberRequest->getMemberById($member->getId()); |
467
|
|
|
} catch (MemberNotFoundException $e) { |
468
|
|
|
try { |
469
|
|
|
$this->memberRequest->save($member); |
470
|
|
|
} catch (InvalidIdException $e) { |
|
|
|
|
471
|
|
|
} |
472
|
|
|
} catch (RequestBuilderException $e) { |
|
|
|
|
473
|
|
|
} |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.