%PDF- %PDF-
Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/ |
Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/MigrationService.php |
<?php declare(strict_types=1); /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Circles\Service; use Exception; use OC; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\CircleRequest; use OCA\Circles\Db\MemberRequest; use OCA\Circles\Db\ShareTokenRequest; use OCA\Circles\Exceptions\CircleNotFoundException; use OCA\Circles\Exceptions\ContactAddressBookNotFoundException; use OCA\Circles\Exceptions\ContactFormatException; use OCA\Circles\Exceptions\ContactNotFoundException; use OCA\Circles\Exceptions\FederatedItemException; use OCA\Circles\Exceptions\FederatedUserException; use OCA\Circles\Exceptions\FederatedUserNotFoundException; use OCA\Circles\Exceptions\InvalidIdException; use OCA\Circles\Exceptions\MemberNotFoundException; use OCA\Circles\Exceptions\MigrationException; use OCA\Circles\Exceptions\OwnerNotFoundException; use OCA\Circles\Exceptions\RemoteInstanceException; use OCA\Circles\Exceptions\RemoteNotFoundException; use OCA\Circles\Exceptions\RemoteResourceNotFoundException; use OCA\Circles\Exceptions\RequestBuilderException; use OCA\Circles\Exceptions\ShareTokenNotFoundException; use OCA\Circles\Exceptions\SingleCircleNotFoundException; use OCA\Circles\Exceptions\UnknownRemoteException; use OCA\Circles\Exceptions\UserTypeNotFoundException; use OCA\Circles\Model\Circle; use OCA\Circles\Model\FederatedUser; use OCA\Circles\Model\Member; use OCA\Circles\Model\Probes\CircleProbe; use OCA\Circles\Model\ShareToken; use OCA\Circles\Tools\Model\SimpleDataStore; use OCA\Circles\Tools\Traits\TNCLogger; use OCA\Circles\Tools\Traits\TStringTools; use OCA\DAV\CardDAV\ContactsManager; use OCP\Contacts\IManager; use OCP\IDBConnection; use OCP\IURLGenerator; use OCP\Share\IShare; /** * Class MigrationService * * @package OCA\Circles\Service */ class MigrationService { use TStringTools; use TNCLogger; /** @var IDBConnection */ private $dbConnection; /** @var IURLGenerator */ private $urlGenerator; /** @var CircleRequest */ private $circleRequest; /** @var MemberRequest */ private $memberRequest; /** @var ShareTokenRequest */ private $shareTokenRequest; /** @var MembershipService */ private $membershipService; /** @var FederatedUserService */ private $federatedUserService; /** @var CircleService */ private $circleService; /** @var ContactService */ private $contactService; /** @var TimezoneService */ private $timezoneService; /** @var OutputService */ private $outputService; /** @var ConfigService */ private $configService; /** @var FederatedUser */ private $appCircle = null; /** * MigrationService constructor. * * @param IDBConnection $dbConnection * @param IURLGenerator $urlGenerator * @param CircleRequest $circleRequest * @param MemberRequest $memberRequest * @param ShareTokenRequest $shareTokenRequest * @param MembershipService $membershipService * @param FederatedUserService $federatedUserService * @param CircleService $circleService * @param ContactService $contactService * @param TimezoneService $timezoneService * @param OutputService $outputService * @param ConfigService $configService */ public function __construct( IDBConnection $dbConnection, IURLGenerator $urlGenerator, CircleRequest $circleRequest, MemberRequest $memberRequest, ShareTokenRequest $shareTokenRequest, MembershipService $membershipService, FederatedUserService $federatedUserService, CircleService $circleService, ContactService $contactService, TimezoneService $timezoneService, OutputService $outputService, ConfigService $configService ) { $this->dbConnection = $dbConnection; $this->urlGenerator = $urlGenerator; $this->circleRequest = $circleRequest; $this->memberRequest = $memberRequest; $this->shareTokenRequest = $shareTokenRequest; $this->membershipService = $membershipService; $this->federatedUserService = $federatedUserService; $this->circleService = $circleService; $this->contactService = $contactService; $this->timezoneService = $timezoneService; $this->outputService = $outputService; $this->configService = $configService; $this->setup('app', Application::APP_ID); } /** * @param bool $force * * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws MigrationException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function migration(bool $force = false): void { if ($this->configService->getAppValueBool(ConfigService::MIGRATION_RUN)) { throw new MigrationException('A migration process is already running'); } $this->configService->setAppValue(ConfigService::MIGRATION_RUN, '1'); if ($force) { $this->configService->setAppValue(ConfigService::MIGRATION_22, '0'); $this->configService->setAppValue(ConfigService::MIGRATION_22_1, '0'); // $this->configService->setAppValue(ConfigService::MIGRATION_23, '0'); } $this->appCircle = $this->federatedUserService->getAppInitiator( Application::APP_ID, Member::APP_CIRCLES ); $this->migrationTo22(); $this->configService->setAppValue(ConfigService::MIGRATION_RUN, '0'); } /** * @throws RequestBuilderException */ private function migrationTo22(): void { if ($this->configService->getAppValueBool(ConfigService::MIGRATION_22)) { return; } if (!$this->migrationTo22Feasibility()) { $this->configService->setAppValue(ConfigService::MIGRATION_22, '1'); return; } $this->outputService->output('Migrating to 22'); $this->migrationTo22_Circles(); $this->migrationTo22_Members(); $this->membershipService->resetMemberships('', true); $this->migrationTo22_Members_Memberships(); $this->migrationTo22_Tokens(); $this->migrationTo22_1_SubShares(); $this->configService->setAppValue(ConfigService::MIGRATION_22, '1'); } /** * run migration if: * - old tables exist. * - new tables are (almost) empty. * * @return bool * @throws \OCP\DB\Exception */ public function migrationTo22Feasibility(): bool { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*')->from('circle_circles'); try { $cursor = $qb->executeQuery(); $cursor->closeCursor(); } catch (\OCP\DB\Exception $e) { return false; } $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*')->from('circles_circle'); $cursor = $qb->executeQuery(); if ($cursor->rowCount() > 1) { return false; } $cursor->closeCursor(); return true; } /** * @throws RequestBuilderException */ public function migrationTo22_Members_Memberships(): void { $probe = new CircleProbe(); $probe->includeSystemCircles(); $circles = $this->circleRequest->getCircles(null, $probe); $this->outputService->startMigrationProgress(sizeof($circles)); $done = []; foreach ($circles as $circle) { $this->outputService->output( 'Caching memberships for Members of \'' . $circle->getDisplayName() . '\'', true ); $members = $circle->getMembers(); foreach ($members as $member) { if (in_array($member->getSingleId(), $done)) { continue; } $this->membershipService->manageMemberships($member->getSingleId()); $done[] = $member->getSingleId(); } } $this->outputService->finishMigrationProgress(); } /** * */ private function migrationTo22_Circles(): void { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*')->from('circle_circles'); try { $cursor = $qb->executeQuery(); $this->outputService->startMigrationProgress($cursor->rowCount()); while ($row = $cursor->fetch()) { try { $data = new SimpleDataStore($row); $this->outputService->output( 'Migrating Circle \'' . $data->g('name') . '\' (' . $data->g('unique_id') . ')', true ); $circle = $this->generateCircleFrom21($data); $this->saveGeneratedCircle($circle); } catch (Exception $e) { } } $cursor->closeCursor(); } catch (\OCP\DB\Exception $e) { } $this->outputService->finishMigrationProgress(); } /** * @param SimpleDataStore $data * * @return Circle * @throws RequestBuilderException */ private function generateCircleFrom21(SimpleDataStore $data): Circle { $circle = new Circle(); $circle->setSingleId($data->g('unique_id')) ->setName($data->g('name')) ->setDisplayName($data->g('display_name')) ->setSettings($data->gArray('settings')) ->setDescription($data->g('description')) ->setContactAddressBook($data->gInt('contact_addressbook')) ->setContactGroupName($data->g('contact_groupname')) ->setSource(Member::TYPE_CIRCLE); $dTime = $this->timezoneService->getDateTime($data->g('creation')); $circle->setCreation($dTime->getTimestamp()); if ($circle->getDisplayName() === '') { $circle->setDisplayName($circle->getName()); } $this->circleService->generateSanitizedName($circle); $this->convertCircleTypeFrom21($circle, $data->gInt('type')); return $circle; } /** * @param Circle $circle * @param int $type */ private function convertCircleTypeFrom21(Circle $circle, int $type): void { switch ($type) { case 1: // personal $circle->setConfig(Circle::CFG_PERSONAL); break; case 2: // secret $circle->setConfig(Circle::CFG_OPEN + Circle::CFG_REQUEST); break; case 4: // closed $circle->setConfig(Circle::CFG_OPEN + Circle::CFG_REQUEST + Circle::CFG_VISIBLE); break; case 8: // public $circle->setConfig(Circle::CFG_OPEN + Circle::CFG_VISIBLE); break; } } /** * @param Circle $circle */ private function saveGeneratedCircle(Circle $circle): void { try { $this->circleRequest->getCircle($circle->getSingleId()); } catch (CircleNotFoundException $e) { try { $this->circleRequest->save($circle); } catch (InvalidIdException $e) { } } catch (RequestBuilderException $e) { } } /** */ private function migrationTo22_Members(): void { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*')->from('circle_members'); try { $cursor = $qb->executeQuery(); $this->outputService->startMigrationProgress($cursor->rowCount()); while ($row = $cursor->fetch()) { try { $data = new SimpleDataStore($row); $this->outputService->output( 'Migrating Member \'' . $data->g('user_id') . '\' from \'' . $data->g('circle_id') . '\'', true ); $member = $this->generateMemberFrom21($data); $this->saveGeneratedMember($member); } catch (Exception $e) { } } $cursor->closeCursor(); } catch (\OCP\DB\Exception $e) { } $this->outputService->finishMigrationProgress(); } /** * @throws CircleNotFoundException * @throws RemoteInstanceException * @throws UserTypeNotFoundException * @throws FederatedUserNotFoundException * @throws OwnerNotFoundException * @throws RequestBuilderException * @throws RemoteNotFoundException * @throws UnknownRemoteException * @throws FederatedUserException * @throws ContactAddressBookNotFoundException * @throws RemoteResourceNotFoundException * @throws MemberNotFoundException * @throws FederatedItemException * @throws SingleCircleNotFoundException * @throws InvalidIdException */ private function generateMemberFrom21(SimpleDataStore $data): Member { $member = new Member(); $member->setCircleId($data->g('circle_id')) ->setId($data->g('member_id')) ->setUserId($data->g('user_id')) ->setInstance($data->g('instance')) ->setDisplayName($data->g('cached_name')) ->setLevel($data->gInt('level')) ->setStatus($data->g('status')) ->setContactMeta($data->g('contact_meta')) ->setContactId($data->g('contact_id')) ->setInvitedBy($this->appCircle); $this->convertMemberUserTypeFrom21($member, $data->gInt('user_type')); $singleMember = $this->federatedUserService->getFederatedUser( $member->getUserId(), $member->getUserType() ); $member->setSingleId($singleMember->getSingleId()); return $member; } /** * @param Member $member * @param int $userType * * @throws ContactAddressBookNotFoundException */ private function convertMemberUserTypeFrom21(Member $member, int $userType): void { switch ($userType) { case 1: $member->setUserType(1); return; case 2: $member->setUserType(2); return; case 3: $member->setUserType(4); return; case 4: $member->setUserType(8); $this->fixContactId($member); return; } } private function migrationTo22_1_SubShares(): void { $qb = $this->dbConnection->getQueryBuilder(); $expr = $qb->expr(); $qb->select('*') ->from('share') ->where($expr->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE))) ->andWhere($expr->isNotNull('parent')); try { $cursor = $qb->executeQuery(); $this->outputService->startMigrationProgress($cursor->rowCount()); while ($row = $cursor->fetch()) { try { $data = new SimpleDataStore($row); $federatedUser = $this->federatedUserService->getLocalFederatedUser($data->g('share_with')); $this->outputService->output( 'Migrating child share #' . $data->gInt('id') . ' owner: ' . $data->g('share_with') . ' -> ' . $federatedUser->getSingleId(), true ); $this->updateSubShare($data, $federatedUser); } catch (Exception $e) { } } $cursor->closeCursor(); } catch (\OCP\DB\Exception $e) { } $this->outputService->finishMigrationProgress(); } /** * @param SimpleDataStore $data * @param FederatedUser $federatedUser * * @throws \OCP\DB\Exception */ private function updateSubShare(SimpleDataStore $data, FederatedUser $federatedUser): void { $qb = $this->dbConnection->getQueryBuilder(); $expr = $qb->expr(); $qb->select('*') ->from('share') ->where($expr->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE))) ->andWhere($expr->eq('parent', $qb->createNamedParameter($data->gInt('parent')))) ->andWhere($expr->eq('share_with', $qb->createNamedParameter($federatedUser->getSingleId()))); $cursor = $qb->executeQuery(); if ($cursor->rowCount() > 0) { // TODO: delete current row ? return; } $cursor->closeCursor(); $qb = $this->dbConnection->getQueryBuilder(); $expr = $qb->expr(); $qb->update('share') ->set('share_with', $qb->createNamedParameter($federatedUser->getSingleId())) ->where($expr->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE))) ->andWhere($expr->eq('id', $qb->createNamedParameter($data->gInt('id')))) ->setMaxResults(1); $qb->executeStatement(); } /** * @param Member $member * * @throws ContactAddressBookNotFoundException */ private function fixContactId(Member $member) { [$userId, $contactId] = explode(':', $member->getUserId()); $contactsManager = OC::$server->get(ContactsManager::class); /** @var IManager $cm */ $cm = OC::$server->get(IManager::class); $contactsManager->setupContactsProvider($cm, $userId, $this->urlGenerator); $contact = $cm->search($contactId, ['UID']); if (sizeof($contact) === 1) { $entry = array_shift($contact); $addressBook = $this->contactService->getAddressBoxById($cm, $this->get('addressbook-key', $entry)); $member->setUserId($userId . '/' . $addressBook->getUri() . '/' . $contactId); } } /** * @param Member $member */ private function saveGeneratedMember(Member $member): void { try { $this->memberRequest->getMemberById($member->getId()); } catch (MemberNotFoundException $e) { try { $this->memberRequest->save($member); } catch (InvalidIdException $e) { } } catch (RequestBuilderException $e) { } } /** */ public function migrationTo22_Tokens(): void { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*')->from('circle_tokens'); try { $cursor = $qb->executeQuery(); $this->outputService->startMigrationProgress($cursor->rowCount()); while ($row = $cursor->fetch()) { try { $data = new SimpleDataStore($row); $this->outputService->output( 'Migrating ShareToken \'' . $data->g('token') . '\' for \'' . $data->g('user_id') . '\'', true ); $shareToken = $this->generateShareTokenFrom21($data); $this->saveGeneratedShareToken($shareToken); } catch (Exception $e) { } } $cursor->closeCursor(); } catch (\OCP\DB\Exception $e) { } $this->outputService->finishMigrationProgress(); } /** * @param SimpleDataStore $data * * @return ShareToken * @throws MemberNotFoundException * @throws RequestBuilderException */ private function generateShareTokenFrom21(SimpleDataStore $data): ShareToken { $shareToken = new ShareToken(); $member = $this->memberRequest->getMemberById($data->g('member_id')); if ($member->getUserType() !== Member::TYPE_MAIL && $member->getUserType() !== Member::TYPE_CONTACT) { throw new MemberNotFoundException(); } $shareToken->setShareId($data->gInt('share_id')) ->setCircleId($data->g('circle_id')) ->setSingleId($member->getSingleId()) ->setMemberId($data->g('member_id')) ->setToken($data->g('token')) ->setPassword($data->g('password')) ->setAccepted(IShare::STATUS_ACCEPTED); return $shareToken; } /** * @param ShareToken $shareToken */ private function saveGeneratedShareToken(ShareToken $shareToken): void { try { $this->shareTokenRequest->getByToken($shareToken->getToken()); } catch (ShareTokenNotFoundException $e) { $this->shareTokenRequest->save($shareToken); } catch (RequestBuilderException $e) { } } }