123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- <?php
- declare(strict_types=1);
- /**
- * @copyright 2022 Christopher Ng <chrng8@gmail.com>
- *
- * @author Christopher Ng <chrng8@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- namespace OCA\Settings\UserMigration;
- use InvalidArgumentException;
- use OC\Accounts\TAccountsHelper;
- use OC\Core\Db\ProfileConfigMapper;
- use OC\NotSquareException;
- use OC\Profile\ProfileManager;
- use OCA\Settings\AppInfo\Application;
- use OCP\Accounts\IAccountManager;
- use OCP\IAvatarManager;
- use OCP\IL10N;
- use OCP\IUser;
- use OCP\UserMigration\IExportDestination;
- use OCP\UserMigration\IImportSource;
- use OCP\UserMigration\IMigrator;
- use OCP\UserMigration\ISizeEstimationMigrator;
- use OCP\UserMigration\TMigratorBasicVersionHandling;
- use Symfony\Component\Console\Output\OutputInterface;
- use Throwable;
- class AccountMigrator implements IMigrator, ISizeEstimationMigrator {
- use TMigratorBasicVersionHandling;
- use TAccountsHelper;
- private IAccountManager $accountManager;
- private IAvatarManager $avatarManager;
- private ProfileManager $profileManager;
- private ProfileConfigMapper $configMapper;
- private IL10N $l10n;
- private const PATH_ROOT = Application::APP_ID . '/';
- private const PATH_ACCOUNT_FILE = AccountMigrator::PATH_ROOT . 'account.json';
- private const AVATAR_BASENAME = 'avatar';
- private const PATH_CONFIG_FILE = AccountMigrator::PATH_ROOT . 'config.json';
- public function __construct(
- IAccountManager $accountManager,
- IAvatarManager $avatarManager,
- ProfileManager $profileManager,
- ProfileConfigMapper $configMapper,
- IL10N $l10n
- ) {
- $this->accountManager = $accountManager;
- $this->avatarManager = $avatarManager;
- $this->profileManager = $profileManager;
- $this->configMapper = $configMapper;
- $this->l10n = $l10n;
- }
- /**
- * {@inheritDoc}
- */
- public function getEstimatedExportSize(IUser $user): int|float {
- $size = 100; // 100KiB for account JSON
- try {
- $avatar = $this->avatarManager->getAvatar($user->getUID());
- if ($avatar->isCustomAvatar()) {
- $avatarFile = $avatar->getFile(-1);
- $size += $avatarFile->getSize() / 1024;
- }
- } catch (Throwable $e) {
- // Skip avatar in size estimate on failure
- }
- return ceil($size);
- }
- /**
- * {@inheritDoc}
- */
- public function export(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void {
- $output->writeln('Exporting account information in ' . AccountMigrator::PATH_ACCOUNT_FILE . '…');
- try {
- $account = $this->accountManager->getAccount($user);
- $exportDestination->addFileContents(AccountMigrator::PATH_ACCOUNT_FILE, json_encode($account));
- } catch (Throwable $e) {
- throw new AccountMigratorException('Could not export account information', 0, $e);
- }
- try {
- $avatar = $this->avatarManager->getAvatar($user->getUID());
- if ($avatar->isCustomAvatar()) {
- $avatarFile = $avatar->getFile(-1);
- $exportPath = AccountMigrator::PATH_ROOT . AccountMigrator::AVATAR_BASENAME . '.' . $avatarFile->getExtension();
- $output->writeln('Exporting avatar to ' . $exportPath . '…');
- $exportDestination->addFileAsStream($exportPath, $avatarFile->read());
- }
- } catch (Throwable $e) {
- throw new AccountMigratorException('Could not export avatar', 0, $e);
- }
- try {
- $output->writeln('Exporting profile config in ' . AccountMigrator::PATH_CONFIG_FILE . '…');
- $config = $this->profileManager->getProfileConfig($user, $user);
- $exportDestination->addFileContents(AccountMigrator::PATH_CONFIG_FILE, json_encode($config));
- } catch (Throwable $e) {
- throw new AccountMigratorException('Could not export profile config', 0, $e);
- }
- }
- /**
- * {@inheritDoc}
- */
- public function import(IUser $user, IImportSource $importSource, OutputInterface $output): void {
- if ($importSource->getMigratorVersion($this->getId()) === null) {
- $output->writeln('No version for ' . static::class . ', skipping import…');
- return;
- }
- $output->writeln('Importing account information from ' . AccountMigrator::PATH_ACCOUNT_FILE . '…');
- $account = $this->accountManager->getAccount($user);
- /** @var array<string, array<string, string>>|array<string, array<int, array<string, string>>> $data */
- $data = json_decode($importSource->getFileContents(AccountMigrator::PATH_ACCOUNT_FILE), true, 512, JSON_THROW_ON_ERROR);
- $account->setAllPropertiesFromJson($data);
- try {
- $this->accountManager->updateAccount($account);
- } catch (InvalidArgumentException $e) {
- throw new AccountMigratorException('Failed to import account information');
- }
- /** @var array<int, string> $avatarFiles */
- $avatarFiles = array_filter(
- $importSource->getFolderListing(AccountMigrator::PATH_ROOT),
- fn (string $filename) => pathinfo($filename, PATHINFO_FILENAME) === AccountMigrator::AVATAR_BASENAME,
- );
- if (!empty($avatarFiles)) {
- if (count($avatarFiles) > 1) {
- $output->writeln('Expected single avatar image file, using first file found');
- }
- $importPath = AccountMigrator::PATH_ROOT . reset($avatarFiles);
- $output->writeln('Importing avatar from ' . $importPath . '…');
- $stream = $importSource->getFileAsStream($importPath);
- $image = new \OCP\Image();
- $image->loadFromFileHandle($stream);
- try {
- $avatar = $this->avatarManager->getAvatar($user->getUID());
- $avatar->set($image);
- } catch (NotSquareException $e) {
- throw new AccountMigratorException('Avatar image must be square');
- } catch (Throwable $e) {
- throw new AccountMigratorException('Failed to import avatar', 0, $e);
- }
- }
- try {
- $output->writeln('Importing profile config from ' . AccountMigrator::PATH_CONFIG_FILE . '…');
- /** @var array $configData */
- $configData = json_decode($importSource->getFileContents(AccountMigrator::PATH_CONFIG_FILE), true, 512, JSON_THROW_ON_ERROR);
- // Ensure that a profile config entry exists in the database
- $this->profileManager->getProfileConfig($user, $user);
- $config = $this->configMapper->get($user->getUID());
- $config->setConfigArray($configData);
- $this->configMapper->update($config);
- } catch (Throwable $e) {
- throw new AccountMigratorException('Failed to import profile config');
- }
- }
- /**
- * {@inheritDoc}
- */
- public function getId(): string {
- return 'account';
- }
- /**
- * {@inheritDoc}
- */
- public function getDisplayName(): string {
- return $this->l10n->t('Profile information');
- }
- /**
- * {@inheritDoc}
- */
- public function getDescription(): string {
- return $this->l10n->t('Profile picture, full name, email, phone number, address, website, Twitter, organisation, role, headline, biography, and whether your profile is enabled');
- }
- }
|