2022-02-28 20:26:19 -05:00
< ? 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 />.
*
*/
2022-03-04 01:36:49 -05:00
namespace OCA\Settings\UserMigration ;
2022-02-28 20:26:19 -05:00
use InvalidArgumentException ;
2022-03-04 01:36:49 -05:00
use OC\Accounts\TAccountsHelper ;
2022-07-08 14:58:02 -04:00
use OC\Core\Db\ProfileConfigMapper ;
2022-02-28 21:44:30 -05:00
use OC\NotSquareException ;
2022-07-08 14:58:02 -04:00
use OC\Profile\ProfileManager ;
2022-03-21 22:25:04 -04:00
use OCA\Settings\AppInfo\Application ;
2022-02-28 20:26:19 -05:00
use OCP\Accounts\IAccountManager ;
2022-02-28 21:44:30 -05:00
use OCP\IAvatarManager ;
2022-04-08 15:19:29 -04:00
use OCP\IL10N ;
2022-02-28 20:26:19 -05:00
use OCP\IUser ;
use OCP\UserMigration\IExportDestination ;
use OCP\UserMigration\IImportSource ;
use OCP\UserMigration\IMigrator ;
2022-05-24 04:01:14 -04:00
use OCP\UserMigration\ISizeEstimationMigrator ;
2022-02-28 20:26:19 -05:00
use OCP\UserMigration\TMigratorBasicVersionHandling ;
use Symfony\Component\Console\Output\OutputInterface ;
2022-02-28 21:44:30 -05:00
use Throwable ;
2022-02-28 20:26:19 -05:00
2022-05-24 04:01:14 -04:00
class AccountMigrator implements IMigrator , ISizeEstimationMigrator {
2022-02-28 20:26:19 -05:00
use TMigratorBasicVersionHandling ;
use TAccountsHelper ;
private IAccountManager $accountManager ;
2022-02-28 21:44:30 -05:00
private IAvatarManager $avatarManager ;
2022-07-08 14:58:02 -04:00
private ProfileManager $profileManager ;
private ProfileConfigMapper $configMapper ;
2022-04-08 15:19:29 -04:00
private IL10N $l10n ;
2022-03-21 22:25:04 -04:00
private const PATH_ROOT = Application :: APP_ID . '/' ;
2022-03-03 00:39:03 -05:00
2022-03-21 22:25:04 -04:00
private const PATH_ACCOUNT_FILE = AccountMigrator :: PATH_ROOT . 'account.json' ;
private const AVATAR_BASENAME = 'avatar' ;
2022-02-28 20:26:19 -05:00
2022-07-08 14:58:02 -04:00
private const PATH_CONFIG_FILE = AccountMigrator :: PATH_ROOT . 'config.json' ;
2022-02-28 20:26:19 -05:00
public function __construct (
2022-02-28 21:44:30 -05:00
IAccountManager $accountManager ,
2022-04-08 15:19:29 -04:00
IAvatarManager $avatarManager ,
2022-07-08 14:58:02 -04:00
ProfileManager $profileManager ,
ProfileConfigMapper $configMapper ,
2022-04-08 15:19:29 -04:00
IL10N $l10n
2022-02-28 20:26:19 -05:00
) {
$this -> accountManager = $accountManager ;
2022-02-28 21:44:30 -05:00
$this -> avatarManager = $avatarManager ;
2022-07-08 14:58:02 -04:00
$this -> profileManager = $profileManager ;
$this -> configMapper = $configMapper ;
2022-04-08 15:19:29 -04:00
$this -> l10n = $l10n ;
2022-02-28 21:44:30 -05:00
}
2022-04-28 05:47:04 -04:00
/**
* { @ inheritDoc }
*/
2023-05-05 20:59:57 -04:00
public function getEstimatedExportSize ( IUser $user ) : int | float {
2022-04-28 05:47:04 -04:00
$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 ) {
2022-04-28 21:53:41 -04:00
// Skip avatar in size estimate on failure
2022-04-28 05:47:04 -04:00
}
2023-05-05 20:59:57 -04:00
return ceil ( $size );
2022-04-28 05:47:04 -04:00
}
2022-02-28 20:26:19 -05:00
/**
* { @ inheritDoc }
*/
public function export ( IUser $user , IExportDestination $exportDestination , OutputInterface $output ) : void {
2022-03-21 22:25:04 -04:00
$output -> writeln ( 'Exporting account information in ' . AccountMigrator :: PATH_ACCOUNT_FILE . '…' );
2022-02-28 20:26:19 -05:00
2022-04-12 10:25:13 -04:00
try {
$account = $this -> accountManager -> getAccount ( $user );
$exportDestination -> addFileContents ( AccountMigrator :: PATH_ACCOUNT_FILE , json_encode ( $account ));
2022-04-13 13:02:25 -04:00
} catch ( Throwable $e ) {
throw new AccountMigratorException ( 'Could not export account information' , 0 , $e );
}
2022-02-28 21:44:30 -05:00
2022-04-13 13:02:25 -04:00
try {
2022-04-12 10:25:13 -04:00
$avatar = $this -> avatarManager -> getAvatar ( $user -> getUID ());
if ( $avatar -> isCustomAvatar ()) {
$avatarFile = $avatar -> getFile ( - 1 );
$exportPath = AccountMigrator :: PATH_ROOT . AccountMigrator :: AVATAR_BASENAME . '.' . $avatarFile -> getExtension ();
2022-03-03 00:39:03 -05:00
2022-04-12 10:25:13 -04:00
$output -> writeln ( 'Exporting avatar to ' . $exportPath . '…' );
$exportDestination -> addFileAsStream ( $exportPath , $avatarFile -> read ());
2022-02-28 21:44:30 -05:00
}
2022-04-12 10:25:13 -04:00
} catch ( Throwable $e ) {
2022-04-13 13:02:25 -04:00
throw new AccountMigratorException ( 'Could not export avatar' , 0 , $e );
2022-02-28 21:44:30 -05:00
}
2022-07-08 14:58:02 -04:00
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 );
}
2022-02-28 20:26:19 -05:00
}
/**
* { @ inheritDoc }
*/
public function import ( IUser $user , IImportSource $importSource , OutputInterface $output ) : void {
2022-04-11 04:53:51 -04:00
if ( $importSource -> getMigratorVersion ( $this -> getId ()) === null ) {
2022-02-28 20:26:19 -05:00
$output -> writeln ( 'No version for ' . static :: class . ', skipping import…' );
return ;
}
2022-03-21 22:25:04 -04:00
$output -> writeln ( 'Importing account information from ' . AccountMigrator :: PATH_ACCOUNT_FILE . '…' );
2022-02-28 20:26:19 -05:00
$account = $this -> accountManager -> getAccount ( $user );
2022-03-18 00:10:22 -04:00
/** @var array<string, array<string, string>>|array<string, array<int, array<string, string>>> $data */
2022-03-21 22:25:04 -04:00
$data = json_decode ( $importSource -> getFileContents ( AccountMigrator :: PATH_ACCOUNT_FILE ), true , 512 , JSON_THROW_ON_ERROR );
2022-03-18 00:10:22 -04:00
$account -> setAllPropertiesFromJson ( $data );
2022-02-28 20:26:19 -05:00
try {
$this -> accountManager -> updateAccount ( $account );
} catch ( InvalidArgumentException $e ) {
throw new AccountMigratorException ( 'Failed to import account information' );
}
2022-02-28 21:44:30 -05:00
2022-03-21 22:25:04 -04:00
/** @var array<int, string> $avatarFiles */
2022-03-03 00:39:03 -05:00
$avatarFiles = array_filter (
2022-03-21 22:25:04 -04:00
$importSource -> getFolderListing ( AccountMigrator :: PATH_ROOT ),
fn ( string $filename ) => pathinfo ( $filename , PATHINFO_FILENAME ) === AccountMigrator :: AVATAR_BASENAME ,
2022-03-03 00:39:03 -05:00
);
if ( ! empty ( $avatarFiles )) {
2022-03-21 22:25:04 -04:00
if ( count ( $avatarFiles ) > 1 ) {
2022-03-03 00:39:03 -05:00
$output -> writeln ( 'Expected single avatar image file, using first file found' );
2022-02-28 21:44:30 -05:00
}
2022-03-21 22:25:04 -04:00
$importPath = AccountMigrator :: PATH_ROOT . reset ( $avatarFiles );
2022-03-03 00:39:03 -05:00
2022-03-21 22:25:04 -04:00
$output -> writeln ( 'Importing avatar from ' . $importPath . '…' );
$stream = $importSource -> getFileAsStream ( $importPath );
2022-06-01 20:37:36 -04:00
$image = new \OCP\Image ();
2022-03-10 00:49:33 -05:00
$image -> loadFromFileHandle ( $stream );
2022-03-03 00:39:03 -05:00
2022-02-28 21:44:30 -05:00
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 );
}
}
2022-07-08 14:58:02 -04:00
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' );
}
2022-02-28 20:26:19 -05:00
}
2022-04-08 15:19:29 -04:00
/**
* { @ 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' );
}
2022-02-28 20:26:19 -05:00
}