@@ -16,106 +16,106 @@ |
||
| 16 | 16 | |
| 17 | 17 | class SupportedDatabase implements ISetupCheck { |
| 18 | 18 | |
| 19 | - private const MIN_MARIADB = '10.6'; |
|
| 20 | - private const MAX_MARIADB = '11.8'; |
|
| 21 | - private const MIN_MYSQL = '8.0'; |
|
| 22 | - private const MAX_MYSQL = '8.4'; |
|
| 23 | - private const MIN_POSTGRES = '14'; |
|
| 24 | - private const MAX_POSTGRES = '18'; |
|
| 19 | + private const MIN_MARIADB = '10.6'; |
|
| 20 | + private const MAX_MARIADB = '11.8'; |
|
| 21 | + private const MIN_MYSQL = '8.0'; |
|
| 22 | + private const MAX_MYSQL = '8.4'; |
|
| 23 | + private const MIN_POSTGRES = '14'; |
|
| 24 | + private const MAX_POSTGRES = '18'; |
|
| 25 | 25 | |
| 26 | - public function __construct( |
|
| 27 | - private IL10N $l10n, |
|
| 28 | - private IURLGenerator $urlGenerator, |
|
| 29 | - private IDBConnection $connection, |
|
| 30 | - ) { |
|
| 31 | - } |
|
| 26 | + public function __construct( |
|
| 27 | + private IL10N $l10n, |
|
| 28 | + private IURLGenerator $urlGenerator, |
|
| 29 | + private IDBConnection $connection, |
|
| 30 | + ) { |
|
| 31 | + } |
|
| 32 | 32 | |
| 33 | - public function getCategory(): string { |
|
| 34 | - return 'database'; |
|
| 35 | - } |
|
| 33 | + public function getCategory(): string { |
|
| 34 | + return 'database'; |
|
| 35 | + } |
|
| 36 | 36 | |
| 37 | - public function getName(): string { |
|
| 38 | - return $this->l10n->t('Database version'); |
|
| 39 | - } |
|
| 37 | + public function getName(): string { |
|
| 38 | + return $this->l10n->t('Database version'); |
|
| 39 | + } |
|
| 40 | 40 | |
| 41 | - public function run(): SetupResult { |
|
| 42 | - $databasePlatform = $this->connection->getDatabaseProvider(); |
|
| 43 | - if ($databasePlatform === IDBConnection::PLATFORM_MYSQL || $databasePlatform === IDBConnection::PLATFORM_MARIADB) { |
|
| 44 | - $statement = $this->connection->prepare("SHOW VARIABLES LIKE 'version';"); |
|
| 45 | - $result = $statement->execute(); |
|
| 46 | - $row = $result->fetchAssociative(); |
|
| 47 | - $version = $row['Value']; |
|
| 48 | - $versionlc = strtolower($version); |
|
| 49 | - // we only care about X.Y not X.Y.Z differences |
|
| 50 | - [$major, $minor, ] = explode('.', $versionlc); |
|
| 51 | - $versionConcern = $major . '.' . $minor; |
|
| 52 | - if (str_contains($versionlc, 'mariadb')) { |
|
| 53 | - if (version_compare($versionConcern, '10.3', '=')) { |
|
| 54 | - return SetupResult::info( |
|
| 55 | - $this->l10n->t( |
|
| 56 | - 'MariaDB version 10.3 detected, this version is end-of-life and only supported as part of Ubuntu 20.04. MariaDB >=%1$s and <=%2$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 57 | - [ |
|
| 58 | - self::MIN_MARIADB, |
|
| 59 | - self::MAX_MARIADB, |
|
| 60 | - ] |
|
| 61 | - ), |
|
| 62 | - ); |
|
| 63 | - } elseif (version_compare($versionConcern, self::MIN_MARIADB, '<') || version_compare($versionConcern, self::MAX_MARIADB, '>')) { |
|
| 64 | - return SetupResult::warning( |
|
| 65 | - $this->l10n->t( |
|
| 66 | - 'MariaDB version "%1$s" detected. MariaDB >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 67 | - [ |
|
| 68 | - $version, |
|
| 69 | - self::MIN_MARIADB, |
|
| 70 | - self::MAX_MARIADB, |
|
| 71 | - ], |
|
| 72 | - ), |
|
| 73 | - ); |
|
| 74 | - } |
|
| 75 | - } else { |
|
| 76 | - if (version_compare($versionConcern, self::MIN_MYSQL, '<') || version_compare($versionConcern, self::MAX_MYSQL, '>')) { |
|
| 77 | - return SetupResult::warning( |
|
| 78 | - $this->l10n->t( |
|
| 79 | - 'MySQL version "%1$s" detected. MySQL >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 80 | - [ |
|
| 81 | - $version, |
|
| 82 | - self::MIN_MYSQL, |
|
| 83 | - self::MAX_MYSQL, |
|
| 84 | - ], |
|
| 85 | - ), |
|
| 86 | - ); |
|
| 87 | - } |
|
| 88 | - } |
|
| 89 | - } elseif ($databasePlatform === IDBConnection::PLATFORM_POSTGRES) { |
|
| 90 | - $statement = $this->connection->prepare('SHOW server_version;'); |
|
| 91 | - $result = $statement->execute(); |
|
| 92 | - $row = $result->fetchAssociative(); |
|
| 93 | - $version = $row['server_version']; |
|
| 94 | - $versionlc = strtolower($version); |
|
| 95 | - // we only care about X not X.Y or X.Y.Z differences |
|
| 96 | - [$major, ] = explode('.', $versionlc); |
|
| 97 | - $versionConcern = $major; |
|
| 98 | - if (version_compare($versionConcern, self::MIN_POSTGRES, '<') || version_compare($versionConcern, self::MAX_POSTGRES, '>')) { |
|
| 99 | - return SetupResult::warning( |
|
| 100 | - $this->l10n->t( |
|
| 101 | - 'PostgreSQL version "%1$s" detected. PostgreSQL >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 102 | - [ |
|
| 103 | - $version, |
|
| 104 | - self::MIN_POSTGRES, |
|
| 105 | - self::MAX_POSTGRES, |
|
| 106 | - ]) |
|
| 107 | - ); |
|
| 108 | - } |
|
| 109 | - } elseif ($databasePlatform === IDBConnection::PLATFORM_ORACLE) { |
|
| 110 | - $version = 'Oracle'; |
|
| 111 | - } elseif ($databasePlatform === IDBConnection::PLATFORM_SQLITE) { |
|
| 112 | - return SetupResult::warning( |
|
| 113 | - $this->l10n->t('SQLite is currently being used as the backend database. For larger installations we recommend that you switch to a different database backend. This is particularly recommended when using the desktop client for file synchronisation. To migrate to another database use the command line tool: "occ db:convert-type".'), |
|
| 114 | - $this->urlGenerator->linkToDocs('admin-db-conversion') |
|
| 115 | - ); |
|
| 116 | - } else { |
|
| 117 | - return SetupResult::error($this->l10n->t('Unknown database platform')); |
|
| 118 | - } |
|
| 119 | - return SetupResult::success($version); |
|
| 120 | - } |
|
| 41 | + public function run(): SetupResult { |
|
| 42 | + $databasePlatform = $this->connection->getDatabaseProvider(); |
|
| 43 | + if ($databasePlatform === IDBConnection::PLATFORM_MYSQL || $databasePlatform === IDBConnection::PLATFORM_MARIADB) { |
|
| 44 | + $statement = $this->connection->prepare("SHOW VARIABLES LIKE 'version';"); |
|
| 45 | + $result = $statement->execute(); |
|
| 46 | + $row = $result->fetchAssociative(); |
|
| 47 | + $version = $row['Value']; |
|
| 48 | + $versionlc = strtolower($version); |
|
| 49 | + // we only care about X.Y not X.Y.Z differences |
|
| 50 | + [$major, $minor, ] = explode('.', $versionlc); |
|
| 51 | + $versionConcern = $major . '.' . $minor; |
|
| 52 | + if (str_contains($versionlc, 'mariadb')) { |
|
| 53 | + if (version_compare($versionConcern, '10.3', '=')) { |
|
| 54 | + return SetupResult::info( |
|
| 55 | + $this->l10n->t( |
|
| 56 | + 'MariaDB version 10.3 detected, this version is end-of-life and only supported as part of Ubuntu 20.04. MariaDB >=%1$s and <=%2$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 57 | + [ |
|
| 58 | + self::MIN_MARIADB, |
|
| 59 | + self::MAX_MARIADB, |
|
| 60 | + ] |
|
| 61 | + ), |
|
| 62 | + ); |
|
| 63 | + } elseif (version_compare($versionConcern, self::MIN_MARIADB, '<') || version_compare($versionConcern, self::MAX_MARIADB, '>')) { |
|
| 64 | + return SetupResult::warning( |
|
| 65 | + $this->l10n->t( |
|
| 66 | + 'MariaDB version "%1$s" detected. MariaDB >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 67 | + [ |
|
| 68 | + $version, |
|
| 69 | + self::MIN_MARIADB, |
|
| 70 | + self::MAX_MARIADB, |
|
| 71 | + ], |
|
| 72 | + ), |
|
| 73 | + ); |
|
| 74 | + } |
|
| 75 | + } else { |
|
| 76 | + if (version_compare($versionConcern, self::MIN_MYSQL, '<') || version_compare($versionConcern, self::MAX_MYSQL, '>')) { |
|
| 77 | + return SetupResult::warning( |
|
| 78 | + $this->l10n->t( |
|
| 79 | + 'MySQL version "%1$s" detected. MySQL >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 80 | + [ |
|
| 81 | + $version, |
|
| 82 | + self::MIN_MYSQL, |
|
| 83 | + self::MAX_MYSQL, |
|
| 84 | + ], |
|
| 85 | + ), |
|
| 86 | + ); |
|
| 87 | + } |
|
| 88 | + } |
|
| 89 | + } elseif ($databasePlatform === IDBConnection::PLATFORM_POSTGRES) { |
|
| 90 | + $statement = $this->connection->prepare('SHOW server_version;'); |
|
| 91 | + $result = $statement->execute(); |
|
| 92 | + $row = $result->fetchAssociative(); |
|
| 93 | + $version = $row['server_version']; |
|
| 94 | + $versionlc = strtolower($version); |
|
| 95 | + // we only care about X not X.Y or X.Y.Z differences |
|
| 96 | + [$major, ] = explode('.', $versionlc); |
|
| 97 | + $versionConcern = $major; |
|
| 98 | + if (version_compare($versionConcern, self::MIN_POSTGRES, '<') || version_compare($versionConcern, self::MAX_POSTGRES, '>')) { |
|
| 99 | + return SetupResult::warning( |
|
| 100 | + $this->l10n->t( |
|
| 101 | + 'PostgreSQL version "%1$s" detected. PostgreSQL >=%2$s and <=%3$s is suggested for best performance, stability and functionality with this version of Nextcloud.', |
|
| 102 | + [ |
|
| 103 | + $version, |
|
| 104 | + self::MIN_POSTGRES, |
|
| 105 | + self::MAX_POSTGRES, |
|
| 106 | + ]) |
|
| 107 | + ); |
|
| 108 | + } |
|
| 109 | + } elseif ($databasePlatform === IDBConnection::PLATFORM_ORACLE) { |
|
| 110 | + $version = 'Oracle'; |
|
| 111 | + } elseif ($databasePlatform === IDBConnection::PLATFORM_SQLITE) { |
|
| 112 | + return SetupResult::warning( |
|
| 113 | + $this->l10n->t('SQLite is currently being used as the backend database. For larger installations we recommend that you switch to a different database backend. This is particularly recommended when using the desktop client for file synchronisation. To migrate to another database use the command line tool: "occ db:convert-type".'), |
|
| 114 | + $this->urlGenerator->linkToDocs('admin-db-conversion') |
|
| 115 | + ); |
|
| 116 | + } else { |
|
| 117 | + return SetupResult::error($this->l10n->t('Unknown database platform')); |
|
| 118 | + } |
|
| 119 | + return SetupResult::success($version); |
|
| 120 | + } |
|
| 121 | 121 | } |
@@ -19,94 +19,94 @@ |
||
| 19 | 19 | use OCP\Settings\IDelegatedSettings; |
| 20 | 20 | |
| 21 | 21 | class Server implements IDelegatedSettings { |
| 22 | - use TProfileHelper; |
|
| 23 | - |
|
| 24 | - public function __construct( |
|
| 25 | - private IDBConnection $connection, |
|
| 26 | - private IInitialState $initialStateService, |
|
| 27 | - private ProfileManager $profileManager, |
|
| 28 | - private ITimeFactory $timeFactory, |
|
| 29 | - private IURLGenerator $urlGenerator, |
|
| 30 | - private IConfig $config, |
|
| 31 | - private IAppConfig $appConfig, |
|
| 32 | - private IL10N $l, |
|
| 33 | - ) { |
|
| 34 | - } |
|
| 35 | - |
|
| 36 | - /** |
|
| 37 | - * @return TemplateResponse |
|
| 38 | - */ |
|
| 39 | - public function getForm() { |
|
| 40 | - $ownerConfigFile = fileowner(\OC::$configDir . 'config.php'); |
|
| 41 | - $cliBasedCronPossible = function_exists('posix_getpwuid') && $ownerConfigFile !== false; |
|
| 42 | - $cliBasedCronUser = $cliBasedCronPossible ? (posix_getpwuid($ownerConfigFile)['name'] ?? '') : ''; |
|
| 43 | - |
|
| 44 | - // Background jobs |
|
| 45 | - $this->initialStateService->provideInitialState('backgroundJobsMode', $this->appConfig->getValueString('core', 'backgroundjobs_mode', 'ajax')); |
|
| 46 | - $this->initialStateService->provideInitialState('lastCron', $this->appConfig->getValueInt('core', 'lastcron', 0)); |
|
| 47 | - $this->initialStateService->provideInitialState('cronMaxAge', $this->cronMaxAge()); |
|
| 48 | - $this->initialStateService->provideInitialState('cronErrors', $this->config->getAppValue('core', 'cronErrors')); |
|
| 49 | - $this->initialStateService->provideInitialState('cliBasedCronPossible', $cliBasedCronPossible); |
|
| 50 | - $this->initialStateService->provideInitialState('cliBasedCronUser', $cliBasedCronUser); |
|
| 51 | - $this->initialStateService->provideInitialState('backgroundJobsDocUrl', $this->urlGenerator->linkToDocs('admin-background-jobs')); |
|
| 52 | - |
|
| 53 | - // Profile page |
|
| 54 | - $this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled()); |
|
| 55 | - $this->initialStateService->provideInitialState('profileEnabledByDefault', $this->isProfileEnabledByDefault($this->config)); |
|
| 56 | - |
|
| 57 | - // Basic settings |
|
| 58 | - $this->initialStateService->provideInitialState('restrictSystemTagsCreationToAdmin', $this->appConfig->getValueBool('systemtags', 'restrict_creation_to_admin', false)); |
|
| 59 | - |
|
| 60 | - return new TemplateResponse('settings', 'settings/admin/server', [ |
|
| 61 | - 'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(), |
|
| 62 | - ], ''); |
|
| 63 | - } |
|
| 64 | - |
|
| 65 | - protected function cronMaxAge(): int { |
|
| 66 | - $query = $this->connection->getQueryBuilder(); |
|
| 67 | - $query->select('last_checked') |
|
| 68 | - ->from('jobs') |
|
| 69 | - ->orderBy('last_checked', 'ASC') |
|
| 70 | - ->setMaxResults(1); |
|
| 71 | - |
|
| 72 | - $result = $query->executeQuery(); |
|
| 73 | - if ($row = $result->fetchAssociative()) { |
|
| 74 | - $maxAge = (int)$row['last_checked']; |
|
| 75 | - } else { |
|
| 76 | - $maxAge = $this->timeFactory->getTime(); |
|
| 77 | - } |
|
| 78 | - $result->closeCursor(); |
|
| 79 | - |
|
| 80 | - return $maxAge; |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - /** |
|
| 84 | - * @return string the section ID, e.g. 'sharing' |
|
| 85 | - */ |
|
| 86 | - public function getSection(): string { |
|
| 87 | - return 'server'; |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - /** |
|
| 91 | - * @return int whether the form should be rather on the top or bottom of |
|
| 92 | - * the admin section. The forms are arranged in ascending order of the |
|
| 93 | - * priority values. It is required to return a value between 0 and 100. |
|
| 94 | - * |
|
| 95 | - * E.g.: 70 |
|
| 96 | - */ |
|
| 97 | - public function getPriority(): int { |
|
| 98 | - return 0; |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - public function getName(): ?string { |
|
| 102 | - return $this->l->t('Background jobs'); |
|
| 103 | - } |
|
| 104 | - |
|
| 105 | - public function getAuthorizedAppConfig(): array { |
|
| 106 | - return [ |
|
| 107 | - 'core' => [ |
|
| 108 | - '/mail_general_settings/', |
|
| 109 | - ], |
|
| 110 | - ]; |
|
| 111 | - } |
|
| 22 | + use TProfileHelper; |
|
| 23 | + |
|
| 24 | + public function __construct( |
|
| 25 | + private IDBConnection $connection, |
|
| 26 | + private IInitialState $initialStateService, |
|
| 27 | + private ProfileManager $profileManager, |
|
| 28 | + private ITimeFactory $timeFactory, |
|
| 29 | + private IURLGenerator $urlGenerator, |
|
| 30 | + private IConfig $config, |
|
| 31 | + private IAppConfig $appConfig, |
|
| 32 | + private IL10N $l, |
|
| 33 | + ) { |
|
| 34 | + } |
|
| 35 | + |
|
| 36 | + /** |
|
| 37 | + * @return TemplateResponse |
|
| 38 | + */ |
|
| 39 | + public function getForm() { |
|
| 40 | + $ownerConfigFile = fileowner(\OC::$configDir . 'config.php'); |
|
| 41 | + $cliBasedCronPossible = function_exists('posix_getpwuid') && $ownerConfigFile !== false; |
|
| 42 | + $cliBasedCronUser = $cliBasedCronPossible ? (posix_getpwuid($ownerConfigFile)['name'] ?? '') : ''; |
|
| 43 | + |
|
| 44 | + // Background jobs |
|
| 45 | + $this->initialStateService->provideInitialState('backgroundJobsMode', $this->appConfig->getValueString('core', 'backgroundjobs_mode', 'ajax')); |
|
| 46 | + $this->initialStateService->provideInitialState('lastCron', $this->appConfig->getValueInt('core', 'lastcron', 0)); |
|
| 47 | + $this->initialStateService->provideInitialState('cronMaxAge', $this->cronMaxAge()); |
|
| 48 | + $this->initialStateService->provideInitialState('cronErrors', $this->config->getAppValue('core', 'cronErrors')); |
|
| 49 | + $this->initialStateService->provideInitialState('cliBasedCronPossible', $cliBasedCronPossible); |
|
| 50 | + $this->initialStateService->provideInitialState('cliBasedCronUser', $cliBasedCronUser); |
|
| 51 | + $this->initialStateService->provideInitialState('backgroundJobsDocUrl', $this->urlGenerator->linkToDocs('admin-background-jobs')); |
|
| 52 | + |
|
| 53 | + // Profile page |
|
| 54 | + $this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled()); |
|
| 55 | + $this->initialStateService->provideInitialState('profileEnabledByDefault', $this->isProfileEnabledByDefault($this->config)); |
|
| 56 | + |
|
| 57 | + // Basic settings |
|
| 58 | + $this->initialStateService->provideInitialState('restrictSystemTagsCreationToAdmin', $this->appConfig->getValueBool('systemtags', 'restrict_creation_to_admin', false)); |
|
| 59 | + |
|
| 60 | + return new TemplateResponse('settings', 'settings/admin/server', [ |
|
| 61 | + 'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(), |
|
| 62 | + ], ''); |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + protected function cronMaxAge(): int { |
|
| 66 | + $query = $this->connection->getQueryBuilder(); |
|
| 67 | + $query->select('last_checked') |
|
| 68 | + ->from('jobs') |
|
| 69 | + ->orderBy('last_checked', 'ASC') |
|
| 70 | + ->setMaxResults(1); |
|
| 71 | + |
|
| 72 | + $result = $query->executeQuery(); |
|
| 73 | + if ($row = $result->fetchAssociative()) { |
|
| 74 | + $maxAge = (int)$row['last_checked']; |
|
| 75 | + } else { |
|
| 76 | + $maxAge = $this->timeFactory->getTime(); |
|
| 77 | + } |
|
| 78 | + $result->closeCursor(); |
|
| 79 | + |
|
| 80 | + return $maxAge; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + /** |
|
| 84 | + * @return string the section ID, e.g. 'sharing' |
|
| 85 | + */ |
|
| 86 | + public function getSection(): string { |
|
| 87 | + return 'server'; |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + /** |
|
| 91 | + * @return int whether the form should be rather on the top or bottom of |
|
| 92 | + * the admin section. The forms are arranged in ascending order of the |
|
| 93 | + * priority values. It is required to return a value between 0 and 100. |
|
| 94 | + * |
|
| 95 | + * E.g.: 70 |
|
| 96 | + */ |
|
| 97 | + public function getPriority(): int { |
|
| 98 | + return 0; |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + public function getName(): ?string { |
|
| 102 | + return $this->l->t('Background jobs'); |
|
| 103 | + } |
|
| 104 | + |
|
| 105 | + public function getAuthorizedAppConfig(): array { |
|
| 106 | + return [ |
|
| 107 | + 'core' => [ |
|
| 108 | + '/mail_general_settings/', |
|
| 109 | + ], |
|
| 110 | + ]; |
|
| 111 | + } |
|
| 112 | 112 | } |
@@ -37,7 +37,7 @@ discard block |
||
| 37 | 37 | * @return TemplateResponse |
| 38 | 38 | */ |
| 39 | 39 | public function getForm() { |
| 40 | - $ownerConfigFile = fileowner(\OC::$configDir . 'config.php'); |
|
| 40 | + $ownerConfigFile = fileowner(\OC::$configDir.'config.php'); |
|
| 41 | 41 | $cliBasedCronPossible = function_exists('posix_getpwuid') && $ownerConfigFile !== false; |
| 42 | 42 | $cliBasedCronUser = $cliBasedCronPossible ? (posix_getpwuid($ownerConfigFile)['name'] ?? '') : ''; |
| 43 | 43 | |
@@ -71,7 +71,7 @@ discard block |
||
| 71 | 71 | |
| 72 | 72 | $result = $query->executeQuery(); |
| 73 | 73 | if ($row = $result->fetchAssociative()) { |
| 74 | - $maxAge = (int)$row['last_checked']; |
|
| 74 | + $maxAge = (int) $row['last_checked']; |
|
| 75 | 75 | } else { |
| 76 | 76 | $maxAge = $this->timeFactory->getTime(); |
| 77 | 77 | } |
@@ -29,196 +29,196 @@ |
||
| 29 | 29 | */ |
| 30 | 30 | #[\PHPUnit\Framework\Attributes\Group('DB')] |
| 31 | 31 | class CleanUpTest extends TestCase { |
| 32 | - protected IUserManager&MockObject $userManager; |
|
| 33 | - protected IRootFolder&MockObject $rootFolder; |
|
| 34 | - protected IDBConnection $dbConnection; |
|
| 35 | - protected CleanUp $cleanup; |
|
| 36 | - protected string $trashTable = 'files_trash'; |
|
| 37 | - protected string $user0 = 'user0'; |
|
| 38 | - |
|
| 39 | - protected function setUp(): void { |
|
| 40 | - parent::setUp(); |
|
| 41 | - $this->rootFolder = $this->createMock(IRootFolder::class); |
|
| 42 | - $this->userManager = $this->createMock(IUserManager::class); |
|
| 43 | - |
|
| 44 | - $this->dbConnection = Server::get(IDBConnection::class); |
|
| 45 | - |
|
| 46 | - $this->cleanup = new CleanUp($this->rootFolder, $this->userManager, $this->dbConnection); |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - /** |
|
| 50 | - * populate files_trash table with 10 dummy values |
|
| 51 | - */ |
|
| 52 | - public function initTable(): void { |
|
| 53 | - $query = $this->dbConnection->getQueryBuilder(); |
|
| 54 | - $query->delete($this->trashTable)->executeStatement(); |
|
| 55 | - for ($i = 0; $i < 10; $i++) { |
|
| 56 | - $query->insert($this->trashTable) |
|
| 57 | - ->values([ |
|
| 58 | - 'id' => $query->expr()->literal('file' . $i), |
|
| 59 | - 'timestamp' => $query->expr()->literal($i), |
|
| 60 | - 'location' => $query->expr()->literal('.'), |
|
| 61 | - 'user' => $query->expr()->literal('user' . $i % 2) |
|
| 62 | - ])->executeStatement(); |
|
| 63 | - } |
|
| 64 | - $getAllQuery = $this->dbConnection->getQueryBuilder(); |
|
| 65 | - $result = $getAllQuery->select('id') |
|
| 66 | - ->from($this->trashTable) |
|
| 67 | - ->executeQuery() |
|
| 68 | - ->fetchAllAssociative(); |
|
| 69 | - $this->assertCount(10, $result); |
|
| 70 | - } |
|
| 71 | - |
|
| 72 | - #[\PHPUnit\Framework\Attributes\DataProvider('dataTestRemoveDeletedFiles')] |
|
| 73 | - public function testRemoveDeletedFiles(bool $nodeExists): void { |
|
| 74 | - $this->initTable(); |
|
| 75 | - $this->rootFolder |
|
| 76 | - ->method('nodeExists') |
|
| 77 | - ->with('/' . $this->user0 . '/files_trashbin') |
|
| 78 | - ->willReturnOnConsecutiveCalls($nodeExists, false); |
|
| 79 | - if ($nodeExists) { |
|
| 80 | - $this->rootFolder |
|
| 81 | - ->method('get') |
|
| 82 | - ->with('/' . $this->user0 . '/files_trashbin') |
|
| 83 | - ->willReturn($this->rootFolder); |
|
| 84 | - $this->rootFolder |
|
| 85 | - ->method('delete'); |
|
| 86 | - } else { |
|
| 87 | - $this->rootFolder->expects($this->never())->method('get'); |
|
| 88 | - $this->rootFolder->expects($this->never())->method('delete'); |
|
| 89 | - } |
|
| 90 | - self::invokePrivate($this->cleanup, 'removeDeletedFiles', [$this->user0, new NullOutput(), false]); |
|
| 91 | - |
|
| 92 | - if ($nodeExists) { |
|
| 93 | - // if the delete operation was executed only files from user1 |
|
| 94 | - // should be left. |
|
| 95 | - $query = $this->dbConnection->getQueryBuilder(); |
|
| 96 | - $query->select('user') |
|
| 97 | - ->from($this->trashTable); |
|
| 98 | - |
|
| 99 | - $qResult = $query->executeQuery(); |
|
| 100 | - $result = $qResult->fetchAllAssociative(); |
|
| 101 | - $qResult->closeCursor(); |
|
| 102 | - |
|
| 103 | - $this->assertCount(5, $result); |
|
| 104 | - foreach ($result as $r) { |
|
| 105 | - $this->assertSame('user1', $r['user']); |
|
| 106 | - } |
|
| 107 | - } else { |
|
| 108 | - // if no delete operation was executed we should still have all 10 |
|
| 109 | - // database entries |
|
| 110 | - $getAllQuery = $this->dbConnection->getQueryBuilder(); |
|
| 111 | - $result = $getAllQuery->select('id') |
|
| 112 | - ->from($this->trashTable) |
|
| 113 | - ->executeQuery() |
|
| 114 | - ->fetchAllAssociative(); |
|
| 115 | - $this->assertCount(10, $result); |
|
| 116 | - } |
|
| 117 | - } |
|
| 118 | - public static function dataTestRemoveDeletedFiles(): array { |
|
| 119 | - return [ |
|
| 120 | - [true], |
|
| 121 | - [false] |
|
| 122 | - ]; |
|
| 123 | - } |
|
| 124 | - |
|
| 125 | - /** |
|
| 126 | - * test remove deleted files from users given as parameter |
|
| 127 | - */ |
|
| 128 | - public function testExecuteDeleteListOfUsers(): void { |
|
| 129 | - $userIds = ['user1', 'user2', 'user3']; |
|
| 130 | - $instance = $this->getMockBuilder(CleanUp::class) |
|
| 131 | - ->onlyMethods(['removeDeletedFiles']) |
|
| 132 | - ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection]) |
|
| 133 | - ->getMock(); |
|
| 134 | - $instance->expects($this->exactly(count($userIds))) |
|
| 135 | - ->method('removeDeletedFiles') |
|
| 136 | - ->willReturnCallback(function ($user) use ($userIds): void { |
|
| 137 | - $this->assertTrue(in_array($user, $userIds)); |
|
| 138 | - }); |
|
| 139 | - $this->userManager->expects($this->exactly(count($userIds))) |
|
| 140 | - ->method('userExists')->willReturn(true); |
|
| 141 | - $inputInterface = $this->createMock(\Symfony\Component\Console\Input\InputInterface::class); |
|
| 142 | - $inputInterface->method('getArgument') |
|
| 143 | - ->with('user_id') |
|
| 144 | - ->willReturn($userIds); |
|
| 145 | - $inputInterface->method('getOption') |
|
| 146 | - ->willReturnMap([ |
|
| 147 | - ['all-users', false], |
|
| 148 | - ['verbose', false], |
|
| 149 | - ]); |
|
| 150 | - $outputInterface = $this->createMock(\Symfony\Component\Console\Output\OutputInterface::class); |
|
| 151 | - self::invokePrivate($instance, 'execute', [$inputInterface, $outputInterface]); |
|
| 152 | - } |
|
| 153 | - |
|
| 154 | - /** |
|
| 155 | - * test remove deleted files of all users |
|
| 156 | - */ |
|
| 157 | - public function testExecuteAllUsers(): void { |
|
| 158 | - $userIds = []; |
|
| 159 | - $backendUsers = ['user1', 'user2']; |
|
| 160 | - $instance = $this->getMockBuilder(CleanUp::class) |
|
| 161 | - ->onlyMethods(['removeDeletedFiles']) |
|
| 162 | - ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection]) |
|
| 163 | - ->getMock(); |
|
| 164 | - $backend = $this->createMock(UserInterface::class); |
|
| 165 | - $backend->method('getUsers') |
|
| 166 | - ->with('', 500, 0) |
|
| 167 | - ->willReturn($backendUsers); |
|
| 168 | - $instance->expects($this->exactly(count($backendUsers))) |
|
| 169 | - ->method('removeDeletedFiles') |
|
| 170 | - ->willReturnCallback(function ($user) use ($backendUsers): void { |
|
| 171 | - $this->assertTrue(in_array($user, $backendUsers)); |
|
| 172 | - }); |
|
| 173 | - $inputInterface = $this->createMock(InputInterface::class); |
|
| 174 | - $inputInterface->method('getArgument') |
|
| 175 | - ->with('user_id') |
|
| 176 | - ->willReturn($userIds); |
|
| 177 | - $inputInterface->method('getOption') |
|
| 178 | - ->willReturnMap([ |
|
| 179 | - ['all-users', true], |
|
| 180 | - ['verbose', false], |
|
| 181 | - ]); |
|
| 182 | - $outputInterface = $this->createMock(OutputInterface::class); |
|
| 183 | - $this->userManager |
|
| 184 | - ->method('getBackends') |
|
| 185 | - ->willReturn([$backend]); |
|
| 186 | - self::invokePrivate($instance, 'execute', [$inputInterface, $outputInterface]); |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - public function testExecuteNoUsersAndNoAllUsers(): void { |
|
| 190 | - $inputInterface = $this->createMock(InputInterface::class); |
|
| 191 | - $inputInterface->method('getArgument') |
|
| 192 | - ->with('user_id') |
|
| 193 | - ->willReturn([]); |
|
| 194 | - $inputInterface->method('getOption') |
|
| 195 | - ->willReturnMap([ |
|
| 196 | - ['all-users', false], |
|
| 197 | - ['verbose', false], |
|
| 198 | - ]); |
|
| 199 | - $outputInterface = $this->createMock(OutputInterface::class); |
|
| 200 | - |
|
| 201 | - $this->expectException(InvalidOptionException::class); |
|
| 202 | - $this->expectExceptionMessage('Either specify a user_id or --all-users'); |
|
| 203 | - |
|
| 204 | - self::invokePrivate($this->cleanup, 'execute', [$inputInterface, $outputInterface]); |
|
| 205 | - } |
|
| 206 | - |
|
| 207 | - public function testExecuteUsersAndAllUsers(): void { |
|
| 208 | - $inputInterface = $this->createMock(InputInterface::class); |
|
| 209 | - $inputInterface->method('getArgument') |
|
| 210 | - ->with('user_id') |
|
| 211 | - ->willReturn(['user1', 'user2']); |
|
| 212 | - $inputInterface->method('getOption') |
|
| 213 | - ->willReturnMap([ |
|
| 214 | - ['all-users', true], |
|
| 215 | - ['verbose', false], |
|
| 216 | - ]); |
|
| 217 | - $outputInterface = $this->createMock(OutputInterface::class); |
|
| 218 | - |
|
| 219 | - $this->expectException(InvalidOptionException::class); |
|
| 220 | - $this->expectExceptionMessage('Either specify a user_id or --all-users'); |
|
| 221 | - |
|
| 222 | - self::invokePrivate($this->cleanup, 'execute', [$inputInterface, $outputInterface]); |
|
| 223 | - } |
|
| 32 | + protected IUserManager&MockObject $userManager; |
|
| 33 | + protected IRootFolder&MockObject $rootFolder; |
|
| 34 | + protected IDBConnection $dbConnection; |
|
| 35 | + protected CleanUp $cleanup; |
|
| 36 | + protected string $trashTable = 'files_trash'; |
|
| 37 | + protected string $user0 = 'user0'; |
|
| 38 | + |
|
| 39 | + protected function setUp(): void { |
|
| 40 | + parent::setUp(); |
|
| 41 | + $this->rootFolder = $this->createMock(IRootFolder::class); |
|
| 42 | + $this->userManager = $this->createMock(IUserManager::class); |
|
| 43 | + |
|
| 44 | + $this->dbConnection = Server::get(IDBConnection::class); |
|
| 45 | + |
|
| 46 | + $this->cleanup = new CleanUp($this->rootFolder, $this->userManager, $this->dbConnection); |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + /** |
|
| 50 | + * populate files_trash table with 10 dummy values |
|
| 51 | + */ |
|
| 52 | + public function initTable(): void { |
|
| 53 | + $query = $this->dbConnection->getQueryBuilder(); |
|
| 54 | + $query->delete($this->trashTable)->executeStatement(); |
|
| 55 | + for ($i = 0; $i < 10; $i++) { |
|
| 56 | + $query->insert($this->trashTable) |
|
| 57 | + ->values([ |
|
| 58 | + 'id' => $query->expr()->literal('file' . $i), |
|
| 59 | + 'timestamp' => $query->expr()->literal($i), |
|
| 60 | + 'location' => $query->expr()->literal('.'), |
|
| 61 | + 'user' => $query->expr()->literal('user' . $i % 2) |
|
| 62 | + ])->executeStatement(); |
|
| 63 | + } |
|
| 64 | + $getAllQuery = $this->dbConnection->getQueryBuilder(); |
|
| 65 | + $result = $getAllQuery->select('id') |
|
| 66 | + ->from($this->trashTable) |
|
| 67 | + ->executeQuery() |
|
| 68 | + ->fetchAllAssociative(); |
|
| 69 | + $this->assertCount(10, $result); |
|
| 70 | + } |
|
| 71 | + |
|
| 72 | + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestRemoveDeletedFiles')] |
|
| 73 | + public function testRemoveDeletedFiles(bool $nodeExists): void { |
|
| 74 | + $this->initTable(); |
|
| 75 | + $this->rootFolder |
|
| 76 | + ->method('nodeExists') |
|
| 77 | + ->with('/' . $this->user0 . '/files_trashbin') |
|
| 78 | + ->willReturnOnConsecutiveCalls($nodeExists, false); |
|
| 79 | + if ($nodeExists) { |
|
| 80 | + $this->rootFolder |
|
| 81 | + ->method('get') |
|
| 82 | + ->with('/' . $this->user0 . '/files_trashbin') |
|
| 83 | + ->willReturn($this->rootFolder); |
|
| 84 | + $this->rootFolder |
|
| 85 | + ->method('delete'); |
|
| 86 | + } else { |
|
| 87 | + $this->rootFolder->expects($this->never())->method('get'); |
|
| 88 | + $this->rootFolder->expects($this->never())->method('delete'); |
|
| 89 | + } |
|
| 90 | + self::invokePrivate($this->cleanup, 'removeDeletedFiles', [$this->user0, new NullOutput(), false]); |
|
| 91 | + |
|
| 92 | + if ($nodeExists) { |
|
| 93 | + // if the delete operation was executed only files from user1 |
|
| 94 | + // should be left. |
|
| 95 | + $query = $this->dbConnection->getQueryBuilder(); |
|
| 96 | + $query->select('user') |
|
| 97 | + ->from($this->trashTable); |
|
| 98 | + |
|
| 99 | + $qResult = $query->executeQuery(); |
|
| 100 | + $result = $qResult->fetchAllAssociative(); |
|
| 101 | + $qResult->closeCursor(); |
|
| 102 | + |
|
| 103 | + $this->assertCount(5, $result); |
|
| 104 | + foreach ($result as $r) { |
|
| 105 | + $this->assertSame('user1', $r['user']); |
|
| 106 | + } |
|
| 107 | + } else { |
|
| 108 | + // if no delete operation was executed we should still have all 10 |
|
| 109 | + // database entries |
|
| 110 | + $getAllQuery = $this->dbConnection->getQueryBuilder(); |
|
| 111 | + $result = $getAllQuery->select('id') |
|
| 112 | + ->from($this->trashTable) |
|
| 113 | + ->executeQuery() |
|
| 114 | + ->fetchAllAssociative(); |
|
| 115 | + $this->assertCount(10, $result); |
|
| 116 | + } |
|
| 117 | + } |
|
| 118 | + public static function dataTestRemoveDeletedFiles(): array { |
|
| 119 | + return [ |
|
| 120 | + [true], |
|
| 121 | + [false] |
|
| 122 | + ]; |
|
| 123 | + } |
|
| 124 | + |
|
| 125 | + /** |
|
| 126 | + * test remove deleted files from users given as parameter |
|
| 127 | + */ |
|
| 128 | + public function testExecuteDeleteListOfUsers(): void { |
|
| 129 | + $userIds = ['user1', 'user2', 'user3']; |
|
| 130 | + $instance = $this->getMockBuilder(CleanUp::class) |
|
| 131 | + ->onlyMethods(['removeDeletedFiles']) |
|
| 132 | + ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection]) |
|
| 133 | + ->getMock(); |
|
| 134 | + $instance->expects($this->exactly(count($userIds))) |
|
| 135 | + ->method('removeDeletedFiles') |
|
| 136 | + ->willReturnCallback(function ($user) use ($userIds): void { |
|
| 137 | + $this->assertTrue(in_array($user, $userIds)); |
|
| 138 | + }); |
|
| 139 | + $this->userManager->expects($this->exactly(count($userIds))) |
|
| 140 | + ->method('userExists')->willReturn(true); |
|
| 141 | + $inputInterface = $this->createMock(\Symfony\Component\Console\Input\InputInterface::class); |
|
| 142 | + $inputInterface->method('getArgument') |
|
| 143 | + ->with('user_id') |
|
| 144 | + ->willReturn($userIds); |
|
| 145 | + $inputInterface->method('getOption') |
|
| 146 | + ->willReturnMap([ |
|
| 147 | + ['all-users', false], |
|
| 148 | + ['verbose', false], |
|
| 149 | + ]); |
|
| 150 | + $outputInterface = $this->createMock(\Symfony\Component\Console\Output\OutputInterface::class); |
|
| 151 | + self::invokePrivate($instance, 'execute', [$inputInterface, $outputInterface]); |
|
| 152 | + } |
|
| 153 | + |
|
| 154 | + /** |
|
| 155 | + * test remove deleted files of all users |
|
| 156 | + */ |
|
| 157 | + public function testExecuteAllUsers(): void { |
|
| 158 | + $userIds = []; |
|
| 159 | + $backendUsers = ['user1', 'user2']; |
|
| 160 | + $instance = $this->getMockBuilder(CleanUp::class) |
|
| 161 | + ->onlyMethods(['removeDeletedFiles']) |
|
| 162 | + ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection]) |
|
| 163 | + ->getMock(); |
|
| 164 | + $backend = $this->createMock(UserInterface::class); |
|
| 165 | + $backend->method('getUsers') |
|
| 166 | + ->with('', 500, 0) |
|
| 167 | + ->willReturn($backendUsers); |
|
| 168 | + $instance->expects($this->exactly(count($backendUsers))) |
|
| 169 | + ->method('removeDeletedFiles') |
|
| 170 | + ->willReturnCallback(function ($user) use ($backendUsers): void { |
|
| 171 | + $this->assertTrue(in_array($user, $backendUsers)); |
|
| 172 | + }); |
|
| 173 | + $inputInterface = $this->createMock(InputInterface::class); |
|
| 174 | + $inputInterface->method('getArgument') |
|
| 175 | + ->with('user_id') |
|
| 176 | + ->willReturn($userIds); |
|
| 177 | + $inputInterface->method('getOption') |
|
| 178 | + ->willReturnMap([ |
|
| 179 | + ['all-users', true], |
|
| 180 | + ['verbose', false], |
|
| 181 | + ]); |
|
| 182 | + $outputInterface = $this->createMock(OutputInterface::class); |
|
| 183 | + $this->userManager |
|
| 184 | + ->method('getBackends') |
|
| 185 | + ->willReturn([$backend]); |
|
| 186 | + self::invokePrivate($instance, 'execute', [$inputInterface, $outputInterface]); |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + public function testExecuteNoUsersAndNoAllUsers(): void { |
|
| 190 | + $inputInterface = $this->createMock(InputInterface::class); |
|
| 191 | + $inputInterface->method('getArgument') |
|
| 192 | + ->with('user_id') |
|
| 193 | + ->willReturn([]); |
|
| 194 | + $inputInterface->method('getOption') |
|
| 195 | + ->willReturnMap([ |
|
| 196 | + ['all-users', false], |
|
| 197 | + ['verbose', false], |
|
| 198 | + ]); |
|
| 199 | + $outputInterface = $this->createMock(OutputInterface::class); |
|
| 200 | + |
|
| 201 | + $this->expectException(InvalidOptionException::class); |
|
| 202 | + $this->expectExceptionMessage('Either specify a user_id or --all-users'); |
|
| 203 | + |
|
| 204 | + self::invokePrivate($this->cleanup, 'execute', [$inputInterface, $outputInterface]); |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + public function testExecuteUsersAndAllUsers(): void { |
|
| 208 | + $inputInterface = $this->createMock(InputInterface::class); |
|
| 209 | + $inputInterface->method('getArgument') |
|
| 210 | + ->with('user_id') |
|
| 211 | + ->willReturn(['user1', 'user2']); |
|
| 212 | + $inputInterface->method('getOption') |
|
| 213 | + ->willReturnMap([ |
|
| 214 | + ['all-users', true], |
|
| 215 | + ['verbose', false], |
|
| 216 | + ]); |
|
| 217 | + $outputInterface = $this->createMock(OutputInterface::class); |
|
| 218 | + |
|
| 219 | + $this->expectException(InvalidOptionException::class); |
|
| 220 | + $this->expectExceptionMessage('Either specify a user_id or --all-users'); |
|
| 221 | + |
|
| 222 | + self::invokePrivate($this->cleanup, 'execute', [$inputInterface, $outputInterface]); |
|
| 223 | + } |
|
| 224 | 224 | } |
@@ -50,1141 +50,1141 @@ |
||
| 50 | 50 | |
| 51 | 51 | /** @template-implements IEventListener<BeforeNodeDeletedEvent> */ |
| 52 | 52 | class Trashbin implements IEventListener { |
| 53 | - // unit: percentage; 50% of available disk space/quota |
|
| 54 | - public const DEFAULTMAXSIZE = 50; |
|
| 55 | - |
|
| 56 | - /** |
|
| 57 | - * Ensure we don't need to scan the file during the move to trash |
|
| 58 | - * by triggering the scan in the pre-hook |
|
| 59 | - */ |
|
| 60 | - public static function ensureFileScannedHook(Node $node): void { |
|
| 61 | - try { |
|
| 62 | - self::getUidAndFilename($node->getPath()); |
|
| 63 | - } catch (NotFoundException $e) { |
|
| 64 | - // Nothing to scan for non existing files |
|
| 65 | - } |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - /** |
|
| 69 | - * get the UID of the owner of the file and the path to the file relative to |
|
| 70 | - * owners files folder |
|
| 71 | - * |
|
| 72 | - * @param string $filename |
|
| 73 | - * @return array |
|
| 74 | - * @throws NoUserException |
|
| 75 | - */ |
|
| 76 | - public static function getUidAndFilename($filename) { |
|
| 77 | - $uid = Filesystem::getOwner($filename); |
|
| 78 | - $userManager = Server::get(IUserManager::class); |
|
| 79 | - // if the user with the UID doesn't exists, e.g. because the UID points |
|
| 80 | - // to a remote user with a federated cloud ID we use the current logged-in |
|
| 81 | - // user. We need a valid local user to move the file to the right trash bin |
|
| 82 | - if (!$userManager->userExists($uid)) { |
|
| 83 | - $uid = OC_User::getUser(); |
|
| 84 | - } |
|
| 85 | - if (!$uid) { |
|
| 86 | - // no owner, usually because of share link from ext storage |
|
| 87 | - return [null, null]; |
|
| 88 | - } |
|
| 89 | - Filesystem::initMountPoints($uid); |
|
| 90 | - if ($uid !== OC_User::getUser()) { |
|
| 91 | - $info = Filesystem::getFileInfo($filename); |
|
| 92 | - $ownerView = new View('/' . $uid . '/files'); |
|
| 93 | - try { |
|
| 94 | - $filename = $ownerView->getPath($info['fileid']); |
|
| 95 | - } catch (NotFoundException $e) { |
|
| 96 | - $filename = null; |
|
| 97 | - } |
|
| 98 | - } |
|
| 99 | - return [$uid, $filename]; |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - /** |
|
| 103 | - * get original location and deleted by of files for user |
|
| 104 | - * |
|
| 105 | - * @param string $user |
|
| 106 | - * @return array<string, array<string, array{location: string, deletedBy: string}>> |
|
| 107 | - */ |
|
| 108 | - public static function getExtraData($user) { |
|
| 109 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 110 | - $query->select('id', 'timestamp', 'location', 'deleted_by') |
|
| 111 | - ->from('files_trash') |
|
| 112 | - ->where($query->expr()->eq('user', $query->createNamedParameter($user))); |
|
| 113 | - $result = $query->executeQuery(); |
|
| 114 | - $array = []; |
|
| 115 | - foreach ($result->iterateAssociative() as $row) { |
|
| 116 | - $array[$row['id']][$row['timestamp']] = [ |
|
| 117 | - 'location' => (string)$row['location'], |
|
| 118 | - 'deletedBy' => (string)$row['deleted_by'], |
|
| 119 | - ]; |
|
| 120 | - } |
|
| 121 | - $result->closeCursor(); |
|
| 122 | - return $array; |
|
| 123 | - } |
|
| 124 | - |
|
| 125 | - /** |
|
| 126 | - * get original location of file |
|
| 127 | - * |
|
| 128 | - * @param string $user |
|
| 129 | - * @param string $filename |
|
| 130 | - * @param string $timestamp |
|
| 131 | - * @return string|false original location |
|
| 132 | - */ |
|
| 133 | - public static function getLocation($user, $filename, $timestamp) { |
|
| 134 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 135 | - $query->select('location') |
|
| 136 | - ->from('files_trash') |
|
| 137 | - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 138 | - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 139 | - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 140 | - |
|
| 141 | - $result = $query->executeQuery(); |
|
| 142 | - $row = $result->fetchAssociative(); |
|
| 143 | - $result->closeCursor(); |
|
| 144 | - |
|
| 145 | - if (isset($row['location'])) { |
|
| 146 | - return $row['location']; |
|
| 147 | - } else { |
|
| 148 | - return false; |
|
| 149 | - } |
|
| 150 | - } |
|
| 151 | - |
|
| 152 | - /** @param string $user */ |
|
| 153 | - private static function setUpTrash($user): void { |
|
| 154 | - $view = new View('/' . $user); |
|
| 155 | - if (!$view->is_dir('files_trashbin')) { |
|
| 156 | - $view->mkdir('files_trashbin'); |
|
| 157 | - } |
|
| 158 | - if (!$view->is_dir('files_trashbin/files')) { |
|
| 159 | - $view->mkdir('files_trashbin/files'); |
|
| 160 | - } |
|
| 161 | - if (!$view->is_dir('files_trashbin/versions')) { |
|
| 162 | - $view->mkdir('files_trashbin/versions'); |
|
| 163 | - } |
|
| 164 | - if (!$view->is_dir('files_trashbin/keys')) { |
|
| 165 | - $view->mkdir('files_trashbin/keys'); |
|
| 166 | - } |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - |
|
| 170 | - /** |
|
| 171 | - * copy file to owners trash |
|
| 172 | - * |
|
| 173 | - * @param string $sourcePath |
|
| 174 | - * @param string $owner |
|
| 175 | - * @param string $targetPath |
|
| 176 | - * @param string $user |
|
| 177 | - * @param int $timestamp |
|
| 178 | - */ |
|
| 179 | - private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void { |
|
| 180 | - self::setUpTrash($owner); |
|
| 181 | - |
|
| 182 | - $targetFilename = basename($targetPath); |
|
| 183 | - $targetLocation = dirname($targetPath); |
|
| 184 | - |
|
| 185 | - $sourceFilename = basename($sourcePath); |
|
| 186 | - |
|
| 187 | - $view = new View('/'); |
|
| 188 | - |
|
| 189 | - $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp); |
|
| 190 | - $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp); |
|
| 191 | - $free = $view->free_space($target); |
|
| 192 | - $isUnknownOrUnlimitedFreeSpace = $free < 0; |
|
| 193 | - $isEnoughFreeSpaceLeft = $view->filesize($source) < $free; |
|
| 194 | - if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) { |
|
| 195 | - self::copy_recursive($source, $target, $view); |
|
| 196 | - } |
|
| 197 | - |
|
| 198 | - |
|
| 199 | - if ($view->file_exists($target)) { |
|
| 200 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 201 | - $query->insert('files_trash') |
|
| 202 | - ->setValue('id', $query->createNamedParameter($targetFilename)) |
|
| 203 | - ->setValue('timestamp', $query->createNamedParameter($timestamp)) |
|
| 204 | - ->setValue('location', $query->createNamedParameter($targetLocation)) |
|
| 205 | - ->setValue('user', $query->createNamedParameter($user)) |
|
| 206 | - ->setValue('deleted_by', $query->createNamedParameter($user)); |
|
| 207 | - $result = $query->executeStatement(); |
|
| 208 | - if (!$result) { |
|
| 209 | - Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']); |
|
| 210 | - } |
|
| 211 | - } |
|
| 212 | - } |
|
| 213 | - |
|
| 214 | - |
|
| 215 | - /** |
|
| 216 | - * move file to the trash bin |
|
| 217 | - * |
|
| 218 | - * @param string $file_path path to the deleted file/directory relative to the files root directory |
|
| 219 | - * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder) |
|
| 220 | - * |
|
| 221 | - * @return bool |
|
| 222 | - */ |
|
| 223 | - public static function move2trash($file_path, $ownerOnly = false) { |
|
| 224 | - // get the user for which the filesystem is setup |
|
| 225 | - $root = Filesystem::getRoot(); |
|
| 226 | - [, $user] = explode('/', $root); |
|
| 227 | - [$owner, $ownerPath] = self::getUidAndFilename($file_path); |
|
| 228 | - |
|
| 229 | - // if no owner found (ex: ext storage + share link), will use the current user's trashbin then |
|
| 230 | - if (is_null($owner)) { |
|
| 231 | - $owner = $user; |
|
| 232 | - $ownerPath = $file_path; |
|
| 233 | - } |
|
| 234 | - |
|
| 235 | - $ownerView = new View('/' . $owner); |
|
| 236 | - |
|
| 237 | - // file has been deleted in between |
|
| 238 | - if (is_null($ownerPath) || $ownerPath === '') { |
|
| 239 | - return true; |
|
| 240 | - } |
|
| 241 | - |
|
| 242 | - $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath); |
|
| 243 | - |
|
| 244 | - if ($sourceInfo === false) { |
|
| 245 | - return true; |
|
| 246 | - } |
|
| 247 | - |
|
| 248 | - self::setUpTrash($user); |
|
| 249 | - if ($owner !== $user) { |
|
| 250 | - // also setup for owner |
|
| 251 | - self::setUpTrash($owner); |
|
| 252 | - } |
|
| 253 | - |
|
| 254 | - $path_parts = pathinfo($ownerPath); |
|
| 255 | - |
|
| 256 | - $filename = $path_parts['basename']; |
|
| 257 | - $location = $path_parts['dirname']; |
|
| 258 | - /** @var ITimeFactory $timeFactory */ |
|
| 259 | - $timeFactory = Server::get(ITimeFactory::class); |
|
| 260 | - $timestamp = $timeFactory->getTime(); |
|
| 261 | - |
|
| 262 | - $lockingProvider = Server::get(ILockingProvider::class); |
|
| 263 | - |
|
| 264 | - // disable proxy to prevent recursive calls |
|
| 265 | - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 266 | - $gotLock = false; |
|
| 267 | - |
|
| 268 | - do { |
|
| 269 | - /** @var ILockingStorage & IStorage $trashStorage */ |
|
| 270 | - [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath); |
|
| 271 | - try { |
|
| 272 | - $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); |
|
| 273 | - $gotLock = true; |
|
| 274 | - } catch (LockedException $e) { |
|
| 275 | - // a file with the same name is being deleted concurrently |
|
| 276 | - // nudge the timestamp a bit to resolve the conflict |
|
| 277 | - |
|
| 278 | - $timestamp = $timestamp + 1; |
|
| 279 | - |
|
| 280 | - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 281 | - } |
|
| 282 | - } while (!$gotLock); |
|
| 283 | - |
|
| 284 | - $sourceStorage = $sourceInfo->getStorage(); |
|
| 285 | - $sourceInternalPath = $sourceInfo->getInternalPath(); |
|
| 286 | - |
|
| 287 | - if ($trashStorage->file_exists($trashInternalPath)) { |
|
| 288 | - $trashStorage->unlink($trashInternalPath); |
|
| 289 | - } |
|
| 290 | - |
|
| 291 | - $configuredTrashbinSize = static::getConfiguredTrashbinSize($owner); |
|
| 292 | - if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) { |
|
| 293 | - return false; |
|
| 294 | - } |
|
| 295 | - |
|
| 296 | - try { |
|
| 297 | - $moveSuccessful = true; |
|
| 298 | - |
|
| 299 | - $inCache = $sourceStorage->getCache()->inCache($sourceInternalPath); |
|
| 300 | - $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); |
|
| 301 | - if ($inCache) { |
|
| 302 | - $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); |
|
| 303 | - } |
|
| 304 | - } catch (CopyRecursiveException $e) { |
|
| 305 | - $moveSuccessful = false; |
|
| 306 | - if ($trashStorage->file_exists($trashInternalPath)) { |
|
| 307 | - $trashStorage->unlink($trashInternalPath); |
|
| 308 | - } |
|
| 309 | - Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']); |
|
| 310 | - } |
|
| 311 | - |
|
| 312 | - if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort |
|
| 313 | - if ($sourceStorage->is_dir($sourceInternalPath)) { |
|
| 314 | - $sourceStorage->rmdir($sourceInternalPath); |
|
| 315 | - } else { |
|
| 316 | - $sourceStorage->unlink($sourceInternalPath); |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - if ($sourceStorage->file_exists($sourceInternalPath)) { |
|
| 320 | - // undo the cache move |
|
| 321 | - $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath); |
|
| 322 | - } else { |
|
| 323 | - $trashStorage->getUpdater()->remove($trashInternalPath); |
|
| 324 | - } |
|
| 325 | - return false; |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - if ($moveSuccessful) { |
|
| 329 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 330 | - $query->insert('files_trash') |
|
| 331 | - ->setValue('id', $query->createNamedParameter($filename)) |
|
| 332 | - ->setValue('timestamp', $query->createNamedParameter($timestamp)) |
|
| 333 | - ->setValue('location', $query->createNamedParameter($location)) |
|
| 334 | - ->setValue('user', $query->createNamedParameter($owner)) |
|
| 335 | - ->setValue('deleted_by', $query->createNamedParameter($user)); |
|
| 336 | - $result = $query->executeStatement(); |
|
| 337 | - if (!$result) { |
|
| 338 | - Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']); |
|
| 339 | - } |
|
| 340 | - Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), |
|
| 341 | - 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); |
|
| 342 | - |
|
| 343 | - self::retainVersions($filename, $owner, $ownerPath, $timestamp); |
|
| 344 | - |
|
| 345 | - // if owner !== user we need to also add a copy to the users trash |
|
| 346 | - if ($user !== $owner && $ownerOnly === false) { |
|
| 347 | - self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); |
|
| 348 | - } |
|
| 349 | - } |
|
| 350 | - |
|
| 351 | - $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); |
|
| 352 | - |
|
| 353 | - self::scheduleExpire($user); |
|
| 354 | - |
|
| 355 | - // if owner !== user we also need to update the owners trash size |
|
| 356 | - if ($owner !== $user) { |
|
| 357 | - self::scheduleExpire($owner); |
|
| 358 | - } |
|
| 359 | - |
|
| 360 | - return $moveSuccessful; |
|
| 361 | - } |
|
| 362 | - |
|
| 363 | - private static function getConfiguredTrashbinSize(string $user): int|float { |
|
| 364 | - $config = Server::get(IConfig::class); |
|
| 365 | - $userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1'); |
|
| 366 | - if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) { |
|
| 367 | - return Util::numericToNumber($userTrashbinSize); |
|
| 368 | - } |
|
| 369 | - $systemTrashbinSize = $config->getAppValue('files_trashbin', 'trashbin_size', '-1'); |
|
| 370 | - if (is_numeric($systemTrashbinSize)) { |
|
| 371 | - return Util::numericToNumber($systemTrashbinSize); |
|
| 372 | - } |
|
| 373 | - return -1; |
|
| 374 | - } |
|
| 375 | - |
|
| 376 | - /** |
|
| 377 | - * Move file versions to trash so that they can be restored later |
|
| 378 | - * |
|
| 379 | - * @param string $filename of deleted file |
|
| 380 | - * @param string $owner owner user id |
|
| 381 | - * @param string $ownerPath path relative to the owner's home storage |
|
| 382 | - * @param int $timestamp when the file was deleted |
|
| 383 | - */ |
|
| 384 | - private static function retainVersions($filename, $owner, $ownerPath, $timestamp) { |
|
| 385 | - if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) { |
|
| 386 | - $user = OC_User::getUser(); |
|
| 387 | - $rootView = new View('/'); |
|
| 388 | - |
|
| 389 | - if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { |
|
| 390 | - if ($owner !== $user) { |
|
| 391 | - self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView); |
|
| 392 | - } |
|
| 393 | - self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp)); |
|
| 394 | - } elseif ($versions = Storage::getVersions($owner, $ownerPath)) { |
|
| 395 | - foreach ($versions as $v) { |
|
| 396 | - if ($owner !== $user) { |
|
| 397 | - self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp)); |
|
| 398 | - } |
|
| 399 | - self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp)); |
|
| 400 | - } |
|
| 401 | - } |
|
| 402 | - } |
|
| 403 | - } |
|
| 404 | - |
|
| 405 | - /** |
|
| 406 | - * Move a file or folder on storage level |
|
| 407 | - * |
|
| 408 | - * @param View $view |
|
| 409 | - * @param string $source |
|
| 410 | - * @param string $target |
|
| 411 | - * @return bool |
|
| 412 | - */ |
|
| 413 | - private static function move(View $view, $source, $target) { |
|
| 414 | - /** @var \OC\Files\Storage\Storage $sourceStorage */ |
|
| 415 | - [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source); |
|
| 416 | - /** @var \OC\Files\Storage\Storage $targetStorage */ |
|
| 417 | - [$targetStorage, $targetInternalPath] = $view->resolvePath($target); |
|
| 418 | - /** @var \OC\Files\Storage\Storage $ownerTrashStorage */ |
|
| 419 | - |
|
| 420 | - $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 421 | - if ($result) { |
|
| 422 | - $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 423 | - } |
|
| 424 | - return $result; |
|
| 425 | - } |
|
| 426 | - |
|
| 427 | - /** |
|
| 428 | - * Copy a file or folder on storage level |
|
| 429 | - * |
|
| 430 | - * @param View $view |
|
| 431 | - * @param string $source |
|
| 432 | - * @param string $target |
|
| 433 | - * @return bool |
|
| 434 | - */ |
|
| 435 | - private static function copy(View $view, $source, $target) { |
|
| 436 | - /** @var \OC\Files\Storage\Storage $sourceStorage */ |
|
| 437 | - [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source); |
|
| 438 | - /** @var \OC\Files\Storage\Storage $targetStorage */ |
|
| 439 | - [$targetStorage, $targetInternalPath] = $view->resolvePath($target); |
|
| 440 | - /** @var \OC\Files\Storage\Storage $ownerTrashStorage */ |
|
| 441 | - |
|
| 442 | - $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 443 | - if ($result) { |
|
| 444 | - $targetStorage->getUpdater()->update($targetInternalPath); |
|
| 445 | - } |
|
| 446 | - return $result; |
|
| 447 | - } |
|
| 448 | - |
|
| 449 | - /** |
|
| 450 | - * Restore a file or folder from trash bin |
|
| 451 | - * |
|
| 452 | - * @param string $file path to the deleted file/folder relative to "files_trashbin/files/", |
|
| 453 | - * including the timestamp suffix ".d12345678" |
|
| 454 | - * @param string $filename name of the file/folder |
|
| 455 | - * @param int $timestamp time when the file/folder was deleted |
|
| 456 | - * |
|
| 457 | - * @return bool true on success, false otherwise |
|
| 458 | - */ |
|
| 459 | - public static function restore($file, $filename, $timestamp) { |
|
| 460 | - $user = OC_User::getUser(); |
|
| 461 | - if (!$user) { |
|
| 462 | - throw new \Exception('Tried to restore a file while not logged in'); |
|
| 463 | - } |
|
| 464 | - $view = new View('/' . $user); |
|
| 465 | - |
|
| 466 | - $location = ''; |
|
| 467 | - if ($timestamp) { |
|
| 468 | - $location = self::getLocation($user, $filename, $timestamp); |
|
| 469 | - if ($location === false) { |
|
| 470 | - Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']); |
|
| 471 | - } else { |
|
| 472 | - // if location no longer exists, restore file in the root directory |
|
| 473 | - if ($location !== '/' |
|
| 474 | - && (!$view->is_dir('files/' . $location) |
|
| 475 | - || !$view->isCreatable('files/' . $location)) |
|
| 476 | - ) { |
|
| 477 | - $location = ''; |
|
| 478 | - } |
|
| 479 | - } |
|
| 480 | - } |
|
| 481 | - |
|
| 482 | - // we need a extension in case a file/dir with the same name already exists |
|
| 483 | - $uniqueFilename = self::getUniqueFilename($location, $filename, $view); |
|
| 484 | - |
|
| 485 | - $source = Filesystem::normalizePath('files_trashbin/files/' . $file); |
|
| 486 | - $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename); |
|
| 487 | - if (!$view->file_exists($source)) { |
|
| 488 | - return false; |
|
| 489 | - } |
|
| 490 | - $mtime = $view->filemtime($source); |
|
| 491 | - |
|
| 492 | - // restore file |
|
| 493 | - if (!$view->isCreatable(dirname($target))) { |
|
| 494 | - throw new NotPermittedException("Can't restore trash item because the target folder is not writable"); |
|
| 495 | - } |
|
| 496 | - |
|
| 497 | - $sourcePath = Filesystem::normalizePath($file); |
|
| 498 | - $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 499 | - |
|
| 500 | - $sourceNode = self::getNodeForPath($user, $sourcePath); |
|
| 501 | - $targetNode = self::getNodeForPath($user, $targetPath, 'files'); |
|
| 502 | - $run = true; |
|
| 503 | - $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run); |
|
| 504 | - $dispatcher = Server::get(IEventDispatcher::class); |
|
| 505 | - $dispatcher->dispatchTyped($event); |
|
| 506 | - |
|
| 507 | - if (!$run) { |
|
| 508 | - return false; |
|
| 509 | - } |
|
| 510 | - |
|
| 511 | - $restoreResult = $view->rename($source, $target); |
|
| 512 | - |
|
| 513 | - // handle the restore result |
|
| 514 | - if ($restoreResult) { |
|
| 515 | - $fakeRoot = $view->getRoot(); |
|
| 516 | - $view->chroot('/' . $user . '/files'); |
|
| 517 | - $view->touch('/' . $location . '/' . $uniqueFilename, $mtime); |
|
| 518 | - $view->chroot($fakeRoot); |
|
| 519 | - Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]); |
|
| 520 | - |
|
| 521 | - $sourceNode = self::getNodeForPath($user, $sourcePath); |
|
| 522 | - $targetNode = self::getNodeForPath($user, $targetPath, 'files'); |
|
| 523 | - $event = new NodeRestoredEvent($sourceNode, $targetNode); |
|
| 524 | - $dispatcher = Server::get(IEventDispatcher::class); |
|
| 525 | - $dispatcher->dispatchTyped($event); |
|
| 526 | - |
|
| 527 | - self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp); |
|
| 528 | - |
|
| 529 | - if ($timestamp) { |
|
| 530 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 531 | - $query->delete('files_trash') |
|
| 532 | - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 533 | - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 534 | - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 535 | - $query->executeStatement(); |
|
| 536 | - } |
|
| 537 | - |
|
| 538 | - return true; |
|
| 539 | - } |
|
| 540 | - |
|
| 541 | - return false; |
|
| 542 | - } |
|
| 543 | - |
|
| 544 | - /** |
|
| 545 | - * restore versions from trash bin |
|
| 546 | - * |
|
| 547 | - * @param View $view file view |
|
| 548 | - * @param string $file complete path to file |
|
| 549 | - * @param string $filename name of file once it was deleted |
|
| 550 | - * @param string $uniqueFilename new file name to restore the file without overwriting existing files |
|
| 551 | - * @param string $location location if file |
|
| 552 | - * @param int $timestamp deletion time |
|
| 553 | - * @return false|null |
|
| 554 | - */ |
|
| 555 | - private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) { |
|
| 556 | - if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) { |
|
| 557 | - $user = OC_User::getUser(); |
|
| 558 | - $rootView = new View('/'); |
|
| 559 | - |
|
| 560 | - $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 561 | - |
|
| 562 | - [$owner, $ownerPath] = self::getUidAndFilename($target); |
|
| 563 | - |
|
| 564 | - // file has been deleted in between |
|
| 565 | - if (empty($ownerPath)) { |
|
| 566 | - return false; |
|
| 567 | - } |
|
| 568 | - |
|
| 569 | - if ($timestamp) { |
|
| 570 | - $versionedFile = $filename; |
|
| 571 | - } else { |
|
| 572 | - $versionedFile = $file; |
|
| 573 | - } |
|
| 574 | - |
|
| 575 | - if ($view->is_dir('/files_trashbin/versions/' . $file)) { |
|
| 576 | - $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath)); |
|
| 577 | - } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) { |
|
| 578 | - foreach ($versions as $v) { |
|
| 579 | - if ($timestamp) { |
|
| 580 | - $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 581 | - } else { |
|
| 582 | - $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 583 | - } |
|
| 584 | - } |
|
| 585 | - } |
|
| 586 | - } |
|
| 587 | - } |
|
| 588 | - |
|
| 589 | - /** |
|
| 590 | - * delete all files from the trash |
|
| 591 | - */ |
|
| 592 | - public static function deleteAll() { |
|
| 593 | - $user = OC_User::getUser(); |
|
| 594 | - $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
|
| 595 | - $view = new View('/' . $user); |
|
| 596 | - $fileInfos = $view->getDirectoryContent('files_trashbin/files'); |
|
| 597 | - |
|
| 598 | - try { |
|
| 599 | - $trash = $userRoot->get('files_trashbin'); |
|
| 600 | - } catch (NotFoundException $e) { |
|
| 601 | - return false; |
|
| 602 | - } |
|
| 603 | - |
|
| 604 | - // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore) |
|
| 605 | - $filePaths = []; |
|
| 606 | - foreach ($fileInfos as $fileInfo) { |
|
| 607 | - $filePaths[] = $view->getRelativePath($fileInfo->getPath()); |
|
| 608 | - } |
|
| 609 | - unset($fileInfos); // save memory |
|
| 610 | - |
|
| 611 | - // Bulk PreDelete-Hook |
|
| 612 | - \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]); |
|
| 613 | - |
|
| 614 | - // Single-File Hooks |
|
| 615 | - foreach ($filePaths as $path) { |
|
| 616 | - self::emitTrashbinPreDelete($path); |
|
| 617 | - } |
|
| 618 | - |
|
| 619 | - // actual file deletion |
|
| 620 | - $trash->delete(); |
|
| 621 | - |
|
| 622 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 623 | - $query->delete('files_trash') |
|
| 624 | - ->where($query->expr()->eq('user', $query->createNamedParameter($user))); |
|
| 625 | - $query->executeStatement(); |
|
| 626 | - |
|
| 627 | - // Bulk PostDelete-Hook |
|
| 628 | - \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]); |
|
| 629 | - |
|
| 630 | - // Single-File Hooks |
|
| 631 | - foreach ($filePaths as $path) { |
|
| 632 | - self::emitTrashbinPostDelete($path); |
|
| 633 | - } |
|
| 634 | - |
|
| 635 | - $trash = $userRoot->newFolder('files_trashbin'); |
|
| 636 | - $trash->newFolder('files'); |
|
| 637 | - |
|
| 638 | - return true; |
|
| 639 | - } |
|
| 640 | - |
|
| 641 | - /** |
|
| 642 | - * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted |
|
| 643 | - * |
|
| 644 | - * @param string $path |
|
| 645 | - */ |
|
| 646 | - protected static function emitTrashbinPreDelete($path) { |
|
| 647 | - \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]); |
|
| 648 | - } |
|
| 649 | - |
|
| 650 | - /** |
|
| 651 | - * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted |
|
| 652 | - * |
|
| 653 | - * @param string $path |
|
| 654 | - */ |
|
| 655 | - protected static function emitTrashbinPostDelete($path) { |
|
| 656 | - \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]); |
|
| 657 | - } |
|
| 658 | - |
|
| 659 | - /** |
|
| 660 | - * delete file from trash bin permanently |
|
| 661 | - * |
|
| 662 | - * @param string $filename path to the file |
|
| 663 | - * @param string $user |
|
| 664 | - * @param int $timestamp of deletion time |
|
| 665 | - * |
|
| 666 | - * @return int|float size of deleted files |
|
| 667 | - */ |
|
| 668 | - public static function delete($filename, $user, $timestamp = null) { |
|
| 669 | - $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
|
| 670 | - $view = new View('/' . $user); |
|
| 671 | - $size = 0; |
|
| 672 | - |
|
| 673 | - if ($timestamp) { |
|
| 674 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 675 | - $query->delete('files_trash') |
|
| 676 | - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 677 | - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 678 | - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 679 | - $query->executeStatement(); |
|
| 680 | - |
|
| 681 | - $file = static::getTrashFilename($filename, $timestamp); |
|
| 682 | - } else { |
|
| 683 | - $file = $filename; |
|
| 684 | - } |
|
| 685 | - |
|
| 686 | - $size += self::deleteVersions($view, $file, $filename, $timestamp, $user); |
|
| 687 | - |
|
| 688 | - try { |
|
| 689 | - $node = $userRoot->get('/files_trashbin/files/' . $file); |
|
| 690 | - } catch (NotFoundException $e) { |
|
| 691 | - return $size; |
|
| 692 | - } |
|
| 693 | - |
|
| 694 | - if ($node instanceof Folder) { |
|
| 695 | - $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file)); |
|
| 696 | - } elseif ($node instanceof File) { |
|
| 697 | - $size += $view->filesize('/files_trashbin/files/' . $file); |
|
| 698 | - } |
|
| 699 | - |
|
| 700 | - self::emitTrashbinPreDelete('/files_trashbin/files/' . $file); |
|
| 701 | - $node->delete(); |
|
| 702 | - self::emitTrashbinPostDelete('/files_trashbin/files/' . $file); |
|
| 703 | - |
|
| 704 | - return $size; |
|
| 705 | - } |
|
| 706 | - |
|
| 707 | - /** |
|
| 708 | - * @param string $file |
|
| 709 | - * @param string $filename |
|
| 710 | - * @param ?int $timestamp |
|
| 711 | - */ |
|
| 712 | - private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float { |
|
| 713 | - $size = 0; |
|
| 714 | - if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) { |
|
| 715 | - if ($view->is_dir('files_trashbin/versions/' . $file)) { |
|
| 716 | - $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file)); |
|
| 717 | - $view->unlink('files_trashbin/versions/' . $file); |
|
| 718 | - } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) { |
|
| 719 | - foreach ($versions as $v) { |
|
| 720 | - if ($timestamp) { |
|
| 721 | - $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 722 | - $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 723 | - } else { |
|
| 724 | - $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 725 | - $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 726 | - } |
|
| 727 | - } |
|
| 728 | - } |
|
| 729 | - } |
|
| 730 | - return $size; |
|
| 731 | - } |
|
| 732 | - |
|
| 733 | - /** |
|
| 734 | - * check to see whether a file exists in trashbin |
|
| 735 | - * |
|
| 736 | - * @param string $filename path to the file |
|
| 737 | - * @param int $timestamp of deletion time |
|
| 738 | - * @return bool true if file exists, otherwise false |
|
| 739 | - */ |
|
| 740 | - public static function file_exists($filename, $timestamp = null) { |
|
| 741 | - $user = OC_User::getUser(); |
|
| 742 | - $view = new View('/' . $user); |
|
| 743 | - |
|
| 744 | - if ($timestamp) { |
|
| 745 | - $filename = static::getTrashFilename($filename, $timestamp); |
|
| 746 | - } |
|
| 747 | - |
|
| 748 | - $target = Filesystem::normalizePath('files_trashbin/files/' . $filename); |
|
| 749 | - return $view->file_exists($target); |
|
| 750 | - } |
|
| 751 | - |
|
| 752 | - /** |
|
| 753 | - * deletes used space for trash bin in db if user was deleted |
|
| 754 | - * |
|
| 755 | - * @param string $uid id of deleted user |
|
| 756 | - * @return bool result of db delete operation |
|
| 757 | - */ |
|
| 758 | - public static function deleteUser($uid) { |
|
| 759 | - $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 760 | - $query->delete('files_trash') |
|
| 761 | - ->where($query->expr()->eq('user', $query->createNamedParameter($uid))); |
|
| 762 | - return (bool)$query->executeStatement(); |
|
| 763 | - } |
|
| 764 | - |
|
| 765 | - /** |
|
| 766 | - * calculate remaining free space for trash bin |
|
| 767 | - * |
|
| 768 | - * @param int|float $trashbinSize current size of the trash bin |
|
| 769 | - * @param string $user |
|
| 770 | - * @return int|float available free space for trash bin |
|
| 771 | - */ |
|
| 772 | - private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float { |
|
| 773 | - $configuredTrashbinSize = static::getConfiguredTrashbinSize($user); |
|
| 774 | - if ($configuredTrashbinSize > -1) { |
|
| 775 | - return $configuredTrashbinSize - $trashbinSize; |
|
| 776 | - } |
|
| 777 | - |
|
| 778 | - $userObject = Server::get(IUserManager::class)->get($user); |
|
| 779 | - if (is_null($userObject)) { |
|
| 780 | - return 0; |
|
| 781 | - } |
|
| 782 | - $softQuota = true; |
|
| 783 | - $quota = $userObject->getQuota(); |
|
| 784 | - if ($quota === null || $quota === 'none') { |
|
| 785 | - $quota = Filesystem::free_space('/'); |
|
| 786 | - $softQuota = false; |
|
| 787 | - // inf or unknown free space |
|
| 788 | - if ($quota < 0) { |
|
| 789 | - $quota = PHP_INT_MAX; |
|
| 790 | - } |
|
| 791 | - } else { |
|
| 792 | - $quota = Util::computerFileSize($quota); |
|
| 793 | - // invalid quota |
|
| 794 | - if ($quota === false) { |
|
| 795 | - $quota = PHP_INT_MAX; |
|
| 796 | - } |
|
| 797 | - } |
|
| 798 | - |
|
| 799 | - // calculate available space for trash bin |
|
| 800 | - // subtract size of files and current trash bin size from quota |
|
| 801 | - if ($softQuota) { |
|
| 802 | - $userFolder = \OC::$server->getUserFolder($user); |
|
| 803 | - if (is_null($userFolder)) { |
|
| 804 | - return 0; |
|
| 805 | - } |
|
| 806 | - $free = $quota - $userFolder->getSize(false); // remaining free space for user |
|
| 807 | - if ($free > 0) { |
|
| 808 | - $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions |
|
| 809 | - } else { |
|
| 810 | - $availableSpace = $free - $trashbinSize; |
|
| 811 | - } |
|
| 812 | - } else { |
|
| 813 | - $availableSpace = $quota; |
|
| 814 | - } |
|
| 815 | - |
|
| 816 | - return Util::numericToNumber($availableSpace); |
|
| 817 | - } |
|
| 818 | - |
|
| 819 | - /** |
|
| 820 | - * resize trash bin if necessary after a new file was added to Nextcloud |
|
| 821 | - * |
|
| 822 | - * @param string $user user id |
|
| 823 | - */ |
|
| 824 | - public static function resizeTrash($user) { |
|
| 825 | - $size = self::getTrashbinSize($user); |
|
| 826 | - |
|
| 827 | - $freeSpace = self::calculateFreeSpace($size, $user); |
|
| 828 | - |
|
| 829 | - if ($freeSpace < 0) { |
|
| 830 | - self::scheduleExpire($user); |
|
| 831 | - } |
|
| 832 | - } |
|
| 833 | - |
|
| 834 | - /** |
|
| 835 | - * clean up the trash bin |
|
| 836 | - * |
|
| 837 | - * @param string $user |
|
| 838 | - */ |
|
| 839 | - public static function expire($user) { |
|
| 840 | - $trashBinSize = self::getTrashbinSize($user); |
|
| 841 | - $availableSpace = self::calculateFreeSpace($trashBinSize, $user); |
|
| 842 | - |
|
| 843 | - $dirContent = Helper::getTrashFiles('/', $user, 'mtime'); |
|
| 844 | - |
|
| 845 | - // delete all files older then $retention_obligation |
|
| 846 | - [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user); |
|
| 847 | - |
|
| 848 | - $availableSpace += $delSize; |
|
| 849 | - |
|
| 850 | - // delete files from trash until we meet the trash bin size limit again |
|
| 851 | - self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace); |
|
| 852 | - } |
|
| 853 | - |
|
| 854 | - /** |
|
| 855 | - * @param string $user |
|
| 856 | - */ |
|
| 857 | - private static function scheduleExpire($user) { |
|
| 858 | - // let the admin disable auto expire |
|
| 859 | - $expiration = Server::get(Expiration::class); |
|
| 860 | - if ($expiration->isEnabled()) { |
|
| 861 | - Server::get(IBus::class)->push(new Expire($user)); |
|
| 862 | - } |
|
| 863 | - } |
|
| 864 | - |
|
| 865 | - /** |
|
| 866 | - * if the size limit for the trash bin is reached, we delete the oldest |
|
| 867 | - * files in the trash bin until we meet the limit again |
|
| 868 | - * |
|
| 869 | - * @param array $files |
|
| 870 | - * @param string $user |
|
| 871 | - * @param int|float $availableSpace available disc space |
|
| 872 | - * @return int|float size of deleted files |
|
| 873 | - */ |
|
| 874 | - protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float { |
|
| 875 | - $expiration = Server::get(Expiration::class); |
|
| 876 | - $size = 0; |
|
| 877 | - |
|
| 878 | - if ($availableSpace <= 0) { |
|
| 879 | - foreach ($files as $file) { |
|
| 880 | - if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) { |
|
| 881 | - $tmp = self::delete($file['name'], $user, $file['mtime']); |
|
| 882 | - Server::get(LoggerInterface::class)->info( |
|
| 883 | - 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"', |
|
| 884 | - [ |
|
| 885 | - 'app' => 'files_trashbin', |
|
| 886 | - 'user' => $user, |
|
| 887 | - ] |
|
| 888 | - ); |
|
| 889 | - $availableSpace += $tmp; |
|
| 890 | - $size += $tmp; |
|
| 891 | - } else { |
|
| 892 | - break; |
|
| 893 | - } |
|
| 894 | - } |
|
| 895 | - } |
|
| 896 | - return $size; |
|
| 897 | - } |
|
| 898 | - |
|
| 899 | - /** |
|
| 900 | - * delete files older then max storage time |
|
| 901 | - * |
|
| 902 | - * @param array $files list of files sorted by mtime |
|
| 903 | - * @param string $user |
|
| 904 | - * @return array{int|float, int} size of deleted files and number of deleted files |
|
| 905 | - */ |
|
| 906 | - public static function deleteExpiredFiles($files, $user) { |
|
| 907 | - $expiration = Server::get(Expiration::class); |
|
| 908 | - $size = 0; |
|
| 909 | - $count = 0; |
|
| 910 | - foreach ($files as $file) { |
|
| 911 | - $timestamp = $file['mtime']; |
|
| 912 | - $filename = $file['name']; |
|
| 913 | - if ($expiration->isExpired($timestamp)) { |
|
| 914 | - try { |
|
| 915 | - $size += self::delete($filename, $user, $timestamp); |
|
| 916 | - $count++; |
|
| 917 | - } catch (NotPermittedException $e) { |
|
| 918 | - Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"', |
|
| 919 | - [ |
|
| 920 | - 'exception' => $e, |
|
| 921 | - 'app' => 'files_trashbin', |
|
| 922 | - 'user' => $user, |
|
| 923 | - ] |
|
| 924 | - ); |
|
| 925 | - } |
|
| 926 | - Server::get(LoggerInterface::class)->info( |
|
| 927 | - 'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.', |
|
| 928 | - [ |
|
| 929 | - 'app' => 'files_trashbin', |
|
| 930 | - 'user' => $user, |
|
| 931 | - ], |
|
| 932 | - ); |
|
| 933 | - } else { |
|
| 934 | - break; |
|
| 935 | - } |
|
| 936 | - } |
|
| 937 | - |
|
| 938 | - return [$size, $count]; |
|
| 939 | - } |
|
| 940 | - |
|
| 941 | - /** |
|
| 942 | - * recursive copy to copy a whole directory |
|
| 943 | - * |
|
| 944 | - * @param string $source source path, relative to the users files directory |
|
| 945 | - * @param string $destination destination path relative to the users root directory |
|
| 946 | - * @param View $view file view for the users root directory |
|
| 947 | - * @return int|float |
|
| 948 | - * @throws Exceptions\CopyRecursiveException |
|
| 949 | - */ |
|
| 950 | - private static function copy_recursive($source, $destination, View $view): int|float { |
|
| 951 | - $size = 0; |
|
| 952 | - if ($view->is_dir($source)) { |
|
| 953 | - $view->mkdir($destination); |
|
| 954 | - $view->touch($destination, $view->filemtime($source)); |
|
| 955 | - foreach ($view->getDirectoryContent($source) as $i) { |
|
| 956 | - $pathDir = $source . '/' . $i['name']; |
|
| 957 | - if ($view->is_dir($pathDir)) { |
|
| 958 | - $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view); |
|
| 959 | - } else { |
|
| 960 | - $size += $view->filesize($pathDir); |
|
| 961 | - $result = $view->copy($pathDir, $destination . '/' . $i['name']); |
|
| 962 | - if (!$result) { |
|
| 963 | - throw new CopyRecursiveException(); |
|
| 964 | - } |
|
| 965 | - $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir)); |
|
| 966 | - } |
|
| 967 | - } |
|
| 968 | - } else { |
|
| 969 | - $size += $view->filesize($source); |
|
| 970 | - $result = $view->copy($source, $destination); |
|
| 971 | - if (!$result) { |
|
| 972 | - throw new CopyRecursiveException(); |
|
| 973 | - } |
|
| 974 | - $view->touch($destination, $view->filemtime($source)); |
|
| 975 | - } |
|
| 976 | - return $size; |
|
| 977 | - } |
|
| 978 | - |
|
| 979 | - /** |
|
| 980 | - * find all versions which belong to the file we want to restore |
|
| 981 | - * |
|
| 982 | - * @param string $filename name of the file which should be restored |
|
| 983 | - * @param int $timestamp timestamp when the file was deleted |
|
| 984 | - */ |
|
| 985 | - private static function getVersionsFromTrash($filename, $timestamp, string $user): array { |
|
| 986 | - $view = new View('/' . $user . '/files_trashbin/versions'); |
|
| 987 | - $versions = []; |
|
| 988 | - |
|
| 989 | - /** @var \OC\Files\Storage\Storage $storage */ |
|
| 990 | - [$storage,] = $view->resolvePath('/'); |
|
| 991 | - |
|
| 992 | - $pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename)); |
|
| 993 | - if ($timestamp) { |
|
| 994 | - // fetch for old versions |
|
| 995 | - $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp); |
|
| 996 | - $pattern .= '.v%.d' . $escapedTimestamp; |
|
| 997 | - $offset = -strlen($escapedTimestamp) - 2; |
|
| 998 | - } else { |
|
| 999 | - $pattern .= '.v%'; |
|
| 1000 | - } |
|
| 1001 | - |
|
| 1002 | - // Manually fetch all versions from the file cache to be able to filter them by their parent |
|
| 1003 | - $cache = $storage->getCache(''); |
|
| 1004 | - $query = new CacheQueryBuilder( |
|
| 1005 | - Server::get(IDBConnection::class)->getQueryBuilder(), |
|
| 1006 | - Server::get(IFilesMetadataManager::class), |
|
| 1007 | - ); |
|
| 1008 | - $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/'); |
|
| 1009 | - $parentId = $cache->getId($normalizedParentPath); |
|
| 1010 | - if ($parentId === -1) { |
|
| 1011 | - return []; |
|
| 1012 | - } |
|
| 1013 | - |
|
| 1014 | - $query->selectFileCache() |
|
| 1015 | - ->whereStorageId($cache->getNumericStorageId()) |
|
| 1016 | - ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId))) |
|
| 1017 | - ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern))); |
|
| 1018 | - |
|
| 1019 | - $result = $query->executeQuery(); |
|
| 1020 | - $entries = $result->fetchAllAssociative(); |
|
| 1021 | - $result->closeCursor(); |
|
| 1022 | - |
|
| 1023 | - /** @var CacheEntry[] $matches */ |
|
| 1024 | - $matches = array_map(function (array $data) { |
|
| 1025 | - return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class)); |
|
| 1026 | - }, $entries); |
|
| 1027 | - |
|
| 1028 | - foreach ($matches as $ma) { |
|
| 1029 | - if ($timestamp) { |
|
| 1030 | - $parts = explode('.v', substr($ma['path'], 0, $offset)); |
|
| 1031 | - $versions[] = end($parts); |
|
| 1032 | - } else { |
|
| 1033 | - $parts = explode('.v', $ma['path']); |
|
| 1034 | - $versions[] = end($parts); |
|
| 1035 | - } |
|
| 1036 | - } |
|
| 1037 | - |
|
| 1038 | - return $versions; |
|
| 1039 | - } |
|
| 1040 | - |
|
| 1041 | - /** |
|
| 1042 | - * find unique extension for restored file if a file with the same name already exists |
|
| 1043 | - * |
|
| 1044 | - * @param string $location where the file should be restored |
|
| 1045 | - * @param string $filename name of the file |
|
| 1046 | - * @param View $view filesystem view relative to users root directory |
|
| 1047 | - * @return string with unique extension |
|
| 1048 | - */ |
|
| 1049 | - private static function getUniqueFilename($location, $filename, View $view) { |
|
| 1050 | - $ext = pathinfo($filename, PATHINFO_EXTENSION); |
|
| 1051 | - $name = pathinfo($filename, PATHINFO_FILENAME); |
|
| 1052 | - $l = Util::getL10N('files_trashbin'); |
|
| 1053 | - |
|
| 1054 | - $location = '/' . trim($location, '/'); |
|
| 1055 | - |
|
| 1056 | - // if extension is not empty we set a dot in front of it |
|
| 1057 | - if ($ext !== '') { |
|
| 1058 | - $ext = '.' . $ext; |
|
| 1059 | - } |
|
| 1060 | - |
|
| 1061 | - if ($view->file_exists('files' . $location . '/' . $filename)) { |
|
| 1062 | - $i = 2; |
|
| 1063 | - $uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext; |
|
| 1064 | - while ($view->file_exists('files' . $location . '/' . $uniqueName)) { |
|
| 1065 | - $uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext; |
|
| 1066 | - $i++; |
|
| 1067 | - } |
|
| 1068 | - |
|
| 1069 | - return $uniqueName; |
|
| 1070 | - } |
|
| 1071 | - |
|
| 1072 | - return $filename; |
|
| 1073 | - } |
|
| 1074 | - |
|
| 1075 | - /** |
|
| 1076 | - * get the size from a given root folder |
|
| 1077 | - * |
|
| 1078 | - * @param View $view file view on the root folder |
|
| 1079 | - * @return int|float size of the folder |
|
| 1080 | - */ |
|
| 1081 | - private static function calculateSize(View $view): int|float { |
|
| 1082 | - $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath(''); |
|
| 1083 | - if (!file_exists($root)) { |
|
| 1084 | - return 0; |
|
| 1085 | - } |
|
| 1086 | - $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST); |
|
| 1087 | - $size = 0; |
|
| 1088 | - |
|
| 1089 | - /** |
|
| 1090 | - * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach |
|
| 1091 | - * This bug is fixed in PHP 5.5.9 or before |
|
| 1092 | - * See #8376 |
|
| 1093 | - */ |
|
| 1094 | - $iterator->rewind(); |
|
| 1095 | - while ($iterator->valid()) { |
|
| 1096 | - $path = $iterator->current(); |
|
| 1097 | - $relpath = substr($path, strlen($root) - 1); |
|
| 1098 | - if (!$view->is_dir($relpath)) { |
|
| 1099 | - $size += $view->filesize($relpath); |
|
| 1100 | - } |
|
| 1101 | - $iterator->next(); |
|
| 1102 | - } |
|
| 1103 | - return $size; |
|
| 1104 | - } |
|
| 1105 | - |
|
| 1106 | - /** |
|
| 1107 | - * get current size of trash bin from a given user |
|
| 1108 | - * |
|
| 1109 | - * @param string $user user who owns the trash bin |
|
| 1110 | - * @return int|float trash bin size |
|
| 1111 | - */ |
|
| 1112 | - private static function getTrashbinSize(string $user): int|float { |
|
| 1113 | - $view = new View('/' . $user); |
|
| 1114 | - $fileInfo = $view->getFileInfo('/files_trashbin'); |
|
| 1115 | - return isset($fileInfo['size']) ? $fileInfo['size'] : 0; |
|
| 1116 | - } |
|
| 1117 | - |
|
| 1118 | - /** |
|
| 1119 | - * check if trash bin is empty for a given user |
|
| 1120 | - * |
|
| 1121 | - * @param string $user |
|
| 1122 | - * @return bool |
|
| 1123 | - */ |
|
| 1124 | - public static function isEmpty($user) { |
|
| 1125 | - $view = new View('/' . $user . '/files_trashbin'); |
|
| 1126 | - if ($view->is_dir('/files') && $dh = $view->opendir('/files')) { |
|
| 1127 | - while (($file = readdir($dh)) !== false) { |
|
| 1128 | - if (!Filesystem::isIgnoredDir($file)) { |
|
| 1129 | - return false; |
|
| 1130 | - } |
|
| 1131 | - } |
|
| 1132 | - } |
|
| 1133 | - return true; |
|
| 1134 | - } |
|
| 1135 | - |
|
| 1136 | - /** |
|
| 1137 | - * @param $path |
|
| 1138 | - * @return string |
|
| 1139 | - */ |
|
| 1140 | - public static function preview_icon($path) { |
|
| 1141 | - return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]); |
|
| 1142 | - } |
|
| 1143 | - |
|
| 1144 | - /** |
|
| 1145 | - * Return the filename used in the trash bin |
|
| 1146 | - */ |
|
| 1147 | - public static function getTrashFilename(string $filename, int $timestamp): string { |
|
| 1148 | - $trashFilename = $filename . '.d' . $timestamp; |
|
| 1149 | - $length = strlen($trashFilename); |
|
| 1150 | - // oc_filecache `name` column has a limit of 250 chars |
|
| 1151 | - $maxLength = 250; |
|
| 1152 | - if ($length > $maxLength) { |
|
| 1153 | - $trashFilename = substr_replace( |
|
| 1154 | - $trashFilename, |
|
| 1155 | - '', |
|
| 1156 | - $maxLength / 2, |
|
| 1157 | - $length - $maxLength |
|
| 1158 | - ); |
|
| 1159 | - } |
|
| 1160 | - return $trashFilename; |
|
| 1161 | - } |
|
| 1162 | - |
|
| 1163 | - private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node { |
|
| 1164 | - $rootFolder = Server::get(IRootFolder::class); |
|
| 1165 | - $path = ltrim($path, '/'); |
|
| 1166 | - |
|
| 1167 | - $userFolder = $rootFolder->getUserFolder($user); |
|
| 1168 | - /** @var Folder $trashFolder */ |
|
| 1169 | - $trashFolder = $userFolder->getParent()->get($baseDir); |
|
| 1170 | - try { |
|
| 1171 | - return $trashFolder->get($path); |
|
| 1172 | - } catch (NotFoundException $ex) { |
|
| 1173 | - } |
|
| 1174 | - |
|
| 1175 | - $view = Server::get(View::class); |
|
| 1176 | - $fullPath = '/' . $user . '/' . $baseDir . '/' . $path; |
|
| 1177 | - |
|
| 1178 | - if (Filesystem::is_dir($path)) { |
|
| 1179 | - return new NonExistingFolder($rootFolder, $view, $fullPath); |
|
| 1180 | - } else { |
|
| 1181 | - return new NonExistingFile($rootFolder, $view, $fullPath); |
|
| 1182 | - } |
|
| 1183 | - } |
|
| 1184 | - |
|
| 1185 | - public function handle(Event $event): void { |
|
| 1186 | - if ($event instanceof BeforeNodeDeletedEvent) { |
|
| 1187 | - self::ensureFileScannedHook($event->getNode()); |
|
| 1188 | - } |
|
| 1189 | - } |
|
| 53 | + // unit: percentage; 50% of available disk space/quota |
|
| 54 | + public const DEFAULTMAXSIZE = 50; |
|
| 55 | + |
|
| 56 | + /** |
|
| 57 | + * Ensure we don't need to scan the file during the move to trash |
|
| 58 | + * by triggering the scan in the pre-hook |
|
| 59 | + */ |
|
| 60 | + public static function ensureFileScannedHook(Node $node): void { |
|
| 61 | + try { |
|
| 62 | + self::getUidAndFilename($node->getPath()); |
|
| 63 | + } catch (NotFoundException $e) { |
|
| 64 | + // Nothing to scan for non existing files |
|
| 65 | + } |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + /** |
|
| 69 | + * get the UID of the owner of the file and the path to the file relative to |
|
| 70 | + * owners files folder |
|
| 71 | + * |
|
| 72 | + * @param string $filename |
|
| 73 | + * @return array |
|
| 74 | + * @throws NoUserException |
|
| 75 | + */ |
|
| 76 | + public static function getUidAndFilename($filename) { |
|
| 77 | + $uid = Filesystem::getOwner($filename); |
|
| 78 | + $userManager = Server::get(IUserManager::class); |
|
| 79 | + // if the user with the UID doesn't exists, e.g. because the UID points |
|
| 80 | + // to a remote user with a federated cloud ID we use the current logged-in |
|
| 81 | + // user. We need a valid local user to move the file to the right trash bin |
|
| 82 | + if (!$userManager->userExists($uid)) { |
|
| 83 | + $uid = OC_User::getUser(); |
|
| 84 | + } |
|
| 85 | + if (!$uid) { |
|
| 86 | + // no owner, usually because of share link from ext storage |
|
| 87 | + return [null, null]; |
|
| 88 | + } |
|
| 89 | + Filesystem::initMountPoints($uid); |
|
| 90 | + if ($uid !== OC_User::getUser()) { |
|
| 91 | + $info = Filesystem::getFileInfo($filename); |
|
| 92 | + $ownerView = new View('/' . $uid . '/files'); |
|
| 93 | + try { |
|
| 94 | + $filename = $ownerView->getPath($info['fileid']); |
|
| 95 | + } catch (NotFoundException $e) { |
|
| 96 | + $filename = null; |
|
| 97 | + } |
|
| 98 | + } |
|
| 99 | + return [$uid, $filename]; |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + /** |
|
| 103 | + * get original location and deleted by of files for user |
|
| 104 | + * |
|
| 105 | + * @param string $user |
|
| 106 | + * @return array<string, array<string, array{location: string, deletedBy: string}>> |
|
| 107 | + */ |
|
| 108 | + public static function getExtraData($user) { |
|
| 109 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 110 | + $query->select('id', 'timestamp', 'location', 'deleted_by') |
|
| 111 | + ->from('files_trash') |
|
| 112 | + ->where($query->expr()->eq('user', $query->createNamedParameter($user))); |
|
| 113 | + $result = $query->executeQuery(); |
|
| 114 | + $array = []; |
|
| 115 | + foreach ($result->iterateAssociative() as $row) { |
|
| 116 | + $array[$row['id']][$row['timestamp']] = [ |
|
| 117 | + 'location' => (string)$row['location'], |
|
| 118 | + 'deletedBy' => (string)$row['deleted_by'], |
|
| 119 | + ]; |
|
| 120 | + } |
|
| 121 | + $result->closeCursor(); |
|
| 122 | + return $array; |
|
| 123 | + } |
|
| 124 | + |
|
| 125 | + /** |
|
| 126 | + * get original location of file |
|
| 127 | + * |
|
| 128 | + * @param string $user |
|
| 129 | + * @param string $filename |
|
| 130 | + * @param string $timestamp |
|
| 131 | + * @return string|false original location |
|
| 132 | + */ |
|
| 133 | + public static function getLocation($user, $filename, $timestamp) { |
|
| 134 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 135 | + $query->select('location') |
|
| 136 | + ->from('files_trash') |
|
| 137 | + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 138 | + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 139 | + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 140 | + |
|
| 141 | + $result = $query->executeQuery(); |
|
| 142 | + $row = $result->fetchAssociative(); |
|
| 143 | + $result->closeCursor(); |
|
| 144 | + |
|
| 145 | + if (isset($row['location'])) { |
|
| 146 | + return $row['location']; |
|
| 147 | + } else { |
|
| 148 | + return false; |
|
| 149 | + } |
|
| 150 | + } |
|
| 151 | + |
|
| 152 | + /** @param string $user */ |
|
| 153 | + private static function setUpTrash($user): void { |
|
| 154 | + $view = new View('/' . $user); |
|
| 155 | + if (!$view->is_dir('files_trashbin')) { |
|
| 156 | + $view->mkdir('files_trashbin'); |
|
| 157 | + } |
|
| 158 | + if (!$view->is_dir('files_trashbin/files')) { |
|
| 159 | + $view->mkdir('files_trashbin/files'); |
|
| 160 | + } |
|
| 161 | + if (!$view->is_dir('files_trashbin/versions')) { |
|
| 162 | + $view->mkdir('files_trashbin/versions'); |
|
| 163 | + } |
|
| 164 | + if (!$view->is_dir('files_trashbin/keys')) { |
|
| 165 | + $view->mkdir('files_trashbin/keys'); |
|
| 166 | + } |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + |
|
| 170 | + /** |
|
| 171 | + * copy file to owners trash |
|
| 172 | + * |
|
| 173 | + * @param string $sourcePath |
|
| 174 | + * @param string $owner |
|
| 175 | + * @param string $targetPath |
|
| 176 | + * @param string $user |
|
| 177 | + * @param int $timestamp |
|
| 178 | + */ |
|
| 179 | + private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void { |
|
| 180 | + self::setUpTrash($owner); |
|
| 181 | + |
|
| 182 | + $targetFilename = basename($targetPath); |
|
| 183 | + $targetLocation = dirname($targetPath); |
|
| 184 | + |
|
| 185 | + $sourceFilename = basename($sourcePath); |
|
| 186 | + |
|
| 187 | + $view = new View('/'); |
|
| 188 | + |
|
| 189 | + $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp); |
|
| 190 | + $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp); |
|
| 191 | + $free = $view->free_space($target); |
|
| 192 | + $isUnknownOrUnlimitedFreeSpace = $free < 0; |
|
| 193 | + $isEnoughFreeSpaceLeft = $view->filesize($source) < $free; |
|
| 194 | + if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) { |
|
| 195 | + self::copy_recursive($source, $target, $view); |
|
| 196 | + } |
|
| 197 | + |
|
| 198 | + |
|
| 199 | + if ($view->file_exists($target)) { |
|
| 200 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 201 | + $query->insert('files_trash') |
|
| 202 | + ->setValue('id', $query->createNamedParameter($targetFilename)) |
|
| 203 | + ->setValue('timestamp', $query->createNamedParameter($timestamp)) |
|
| 204 | + ->setValue('location', $query->createNamedParameter($targetLocation)) |
|
| 205 | + ->setValue('user', $query->createNamedParameter($user)) |
|
| 206 | + ->setValue('deleted_by', $query->createNamedParameter($user)); |
|
| 207 | + $result = $query->executeStatement(); |
|
| 208 | + if (!$result) { |
|
| 209 | + Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']); |
|
| 210 | + } |
|
| 211 | + } |
|
| 212 | + } |
|
| 213 | + |
|
| 214 | + |
|
| 215 | + /** |
|
| 216 | + * move file to the trash bin |
|
| 217 | + * |
|
| 218 | + * @param string $file_path path to the deleted file/directory relative to the files root directory |
|
| 219 | + * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder) |
|
| 220 | + * |
|
| 221 | + * @return bool |
|
| 222 | + */ |
|
| 223 | + public static function move2trash($file_path, $ownerOnly = false) { |
|
| 224 | + // get the user for which the filesystem is setup |
|
| 225 | + $root = Filesystem::getRoot(); |
|
| 226 | + [, $user] = explode('/', $root); |
|
| 227 | + [$owner, $ownerPath] = self::getUidAndFilename($file_path); |
|
| 228 | + |
|
| 229 | + // if no owner found (ex: ext storage + share link), will use the current user's trashbin then |
|
| 230 | + if (is_null($owner)) { |
|
| 231 | + $owner = $user; |
|
| 232 | + $ownerPath = $file_path; |
|
| 233 | + } |
|
| 234 | + |
|
| 235 | + $ownerView = new View('/' . $owner); |
|
| 236 | + |
|
| 237 | + // file has been deleted in between |
|
| 238 | + if (is_null($ownerPath) || $ownerPath === '') { |
|
| 239 | + return true; |
|
| 240 | + } |
|
| 241 | + |
|
| 242 | + $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath); |
|
| 243 | + |
|
| 244 | + if ($sourceInfo === false) { |
|
| 245 | + return true; |
|
| 246 | + } |
|
| 247 | + |
|
| 248 | + self::setUpTrash($user); |
|
| 249 | + if ($owner !== $user) { |
|
| 250 | + // also setup for owner |
|
| 251 | + self::setUpTrash($owner); |
|
| 252 | + } |
|
| 253 | + |
|
| 254 | + $path_parts = pathinfo($ownerPath); |
|
| 255 | + |
|
| 256 | + $filename = $path_parts['basename']; |
|
| 257 | + $location = $path_parts['dirname']; |
|
| 258 | + /** @var ITimeFactory $timeFactory */ |
|
| 259 | + $timeFactory = Server::get(ITimeFactory::class); |
|
| 260 | + $timestamp = $timeFactory->getTime(); |
|
| 261 | + |
|
| 262 | + $lockingProvider = Server::get(ILockingProvider::class); |
|
| 263 | + |
|
| 264 | + // disable proxy to prevent recursive calls |
|
| 265 | + $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 266 | + $gotLock = false; |
|
| 267 | + |
|
| 268 | + do { |
|
| 269 | + /** @var ILockingStorage & IStorage $trashStorage */ |
|
| 270 | + [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath); |
|
| 271 | + try { |
|
| 272 | + $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); |
|
| 273 | + $gotLock = true; |
|
| 274 | + } catch (LockedException $e) { |
|
| 275 | + // a file with the same name is being deleted concurrently |
|
| 276 | + // nudge the timestamp a bit to resolve the conflict |
|
| 277 | + |
|
| 278 | + $timestamp = $timestamp + 1; |
|
| 279 | + |
|
| 280 | + $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 281 | + } |
|
| 282 | + } while (!$gotLock); |
|
| 283 | + |
|
| 284 | + $sourceStorage = $sourceInfo->getStorage(); |
|
| 285 | + $sourceInternalPath = $sourceInfo->getInternalPath(); |
|
| 286 | + |
|
| 287 | + if ($trashStorage->file_exists($trashInternalPath)) { |
|
| 288 | + $trashStorage->unlink($trashInternalPath); |
|
| 289 | + } |
|
| 290 | + |
|
| 291 | + $configuredTrashbinSize = static::getConfiguredTrashbinSize($owner); |
|
| 292 | + if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) { |
|
| 293 | + return false; |
|
| 294 | + } |
|
| 295 | + |
|
| 296 | + try { |
|
| 297 | + $moveSuccessful = true; |
|
| 298 | + |
|
| 299 | + $inCache = $sourceStorage->getCache()->inCache($sourceInternalPath); |
|
| 300 | + $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); |
|
| 301 | + if ($inCache) { |
|
| 302 | + $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); |
|
| 303 | + } |
|
| 304 | + } catch (CopyRecursiveException $e) { |
|
| 305 | + $moveSuccessful = false; |
|
| 306 | + if ($trashStorage->file_exists($trashInternalPath)) { |
|
| 307 | + $trashStorage->unlink($trashInternalPath); |
|
| 308 | + } |
|
| 309 | + Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']); |
|
| 310 | + } |
|
| 311 | + |
|
| 312 | + if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort |
|
| 313 | + if ($sourceStorage->is_dir($sourceInternalPath)) { |
|
| 314 | + $sourceStorage->rmdir($sourceInternalPath); |
|
| 315 | + } else { |
|
| 316 | + $sourceStorage->unlink($sourceInternalPath); |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + if ($sourceStorage->file_exists($sourceInternalPath)) { |
|
| 320 | + // undo the cache move |
|
| 321 | + $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath); |
|
| 322 | + } else { |
|
| 323 | + $trashStorage->getUpdater()->remove($trashInternalPath); |
|
| 324 | + } |
|
| 325 | + return false; |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + if ($moveSuccessful) { |
|
| 329 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 330 | + $query->insert('files_trash') |
|
| 331 | + ->setValue('id', $query->createNamedParameter($filename)) |
|
| 332 | + ->setValue('timestamp', $query->createNamedParameter($timestamp)) |
|
| 333 | + ->setValue('location', $query->createNamedParameter($location)) |
|
| 334 | + ->setValue('user', $query->createNamedParameter($owner)) |
|
| 335 | + ->setValue('deleted_by', $query->createNamedParameter($user)); |
|
| 336 | + $result = $query->executeStatement(); |
|
| 337 | + if (!$result) { |
|
| 338 | + Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']); |
|
| 339 | + } |
|
| 340 | + Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), |
|
| 341 | + 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); |
|
| 342 | + |
|
| 343 | + self::retainVersions($filename, $owner, $ownerPath, $timestamp); |
|
| 344 | + |
|
| 345 | + // if owner !== user we need to also add a copy to the users trash |
|
| 346 | + if ($user !== $owner && $ownerOnly === false) { |
|
| 347 | + self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); |
|
| 348 | + } |
|
| 349 | + } |
|
| 350 | + |
|
| 351 | + $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); |
|
| 352 | + |
|
| 353 | + self::scheduleExpire($user); |
|
| 354 | + |
|
| 355 | + // if owner !== user we also need to update the owners trash size |
|
| 356 | + if ($owner !== $user) { |
|
| 357 | + self::scheduleExpire($owner); |
|
| 358 | + } |
|
| 359 | + |
|
| 360 | + return $moveSuccessful; |
|
| 361 | + } |
|
| 362 | + |
|
| 363 | + private static function getConfiguredTrashbinSize(string $user): int|float { |
|
| 364 | + $config = Server::get(IConfig::class); |
|
| 365 | + $userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1'); |
|
| 366 | + if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) { |
|
| 367 | + return Util::numericToNumber($userTrashbinSize); |
|
| 368 | + } |
|
| 369 | + $systemTrashbinSize = $config->getAppValue('files_trashbin', 'trashbin_size', '-1'); |
|
| 370 | + if (is_numeric($systemTrashbinSize)) { |
|
| 371 | + return Util::numericToNumber($systemTrashbinSize); |
|
| 372 | + } |
|
| 373 | + return -1; |
|
| 374 | + } |
|
| 375 | + |
|
| 376 | + /** |
|
| 377 | + * Move file versions to trash so that they can be restored later |
|
| 378 | + * |
|
| 379 | + * @param string $filename of deleted file |
|
| 380 | + * @param string $owner owner user id |
|
| 381 | + * @param string $ownerPath path relative to the owner's home storage |
|
| 382 | + * @param int $timestamp when the file was deleted |
|
| 383 | + */ |
|
| 384 | + private static function retainVersions($filename, $owner, $ownerPath, $timestamp) { |
|
| 385 | + if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) { |
|
| 386 | + $user = OC_User::getUser(); |
|
| 387 | + $rootView = new View('/'); |
|
| 388 | + |
|
| 389 | + if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { |
|
| 390 | + if ($owner !== $user) { |
|
| 391 | + self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView); |
|
| 392 | + } |
|
| 393 | + self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp)); |
|
| 394 | + } elseif ($versions = Storage::getVersions($owner, $ownerPath)) { |
|
| 395 | + foreach ($versions as $v) { |
|
| 396 | + if ($owner !== $user) { |
|
| 397 | + self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp)); |
|
| 398 | + } |
|
| 399 | + self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp)); |
|
| 400 | + } |
|
| 401 | + } |
|
| 402 | + } |
|
| 403 | + } |
|
| 404 | + |
|
| 405 | + /** |
|
| 406 | + * Move a file or folder on storage level |
|
| 407 | + * |
|
| 408 | + * @param View $view |
|
| 409 | + * @param string $source |
|
| 410 | + * @param string $target |
|
| 411 | + * @return bool |
|
| 412 | + */ |
|
| 413 | + private static function move(View $view, $source, $target) { |
|
| 414 | + /** @var \OC\Files\Storage\Storage $sourceStorage */ |
|
| 415 | + [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source); |
|
| 416 | + /** @var \OC\Files\Storage\Storage $targetStorage */ |
|
| 417 | + [$targetStorage, $targetInternalPath] = $view->resolvePath($target); |
|
| 418 | + /** @var \OC\Files\Storage\Storage $ownerTrashStorage */ |
|
| 419 | + |
|
| 420 | + $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 421 | + if ($result) { |
|
| 422 | + $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 423 | + } |
|
| 424 | + return $result; |
|
| 425 | + } |
|
| 426 | + |
|
| 427 | + /** |
|
| 428 | + * Copy a file or folder on storage level |
|
| 429 | + * |
|
| 430 | + * @param View $view |
|
| 431 | + * @param string $source |
|
| 432 | + * @param string $target |
|
| 433 | + * @return bool |
|
| 434 | + */ |
|
| 435 | + private static function copy(View $view, $source, $target) { |
|
| 436 | + /** @var \OC\Files\Storage\Storage $sourceStorage */ |
|
| 437 | + [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source); |
|
| 438 | + /** @var \OC\Files\Storage\Storage $targetStorage */ |
|
| 439 | + [$targetStorage, $targetInternalPath] = $view->resolvePath($target); |
|
| 440 | + /** @var \OC\Files\Storage\Storage $ownerTrashStorage */ |
|
| 441 | + |
|
| 442 | + $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
| 443 | + if ($result) { |
|
| 444 | + $targetStorage->getUpdater()->update($targetInternalPath); |
|
| 445 | + } |
|
| 446 | + return $result; |
|
| 447 | + } |
|
| 448 | + |
|
| 449 | + /** |
|
| 450 | + * Restore a file or folder from trash bin |
|
| 451 | + * |
|
| 452 | + * @param string $file path to the deleted file/folder relative to "files_trashbin/files/", |
|
| 453 | + * including the timestamp suffix ".d12345678" |
|
| 454 | + * @param string $filename name of the file/folder |
|
| 455 | + * @param int $timestamp time when the file/folder was deleted |
|
| 456 | + * |
|
| 457 | + * @return bool true on success, false otherwise |
|
| 458 | + */ |
|
| 459 | + public static function restore($file, $filename, $timestamp) { |
|
| 460 | + $user = OC_User::getUser(); |
|
| 461 | + if (!$user) { |
|
| 462 | + throw new \Exception('Tried to restore a file while not logged in'); |
|
| 463 | + } |
|
| 464 | + $view = new View('/' . $user); |
|
| 465 | + |
|
| 466 | + $location = ''; |
|
| 467 | + if ($timestamp) { |
|
| 468 | + $location = self::getLocation($user, $filename, $timestamp); |
|
| 469 | + if ($location === false) { |
|
| 470 | + Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']); |
|
| 471 | + } else { |
|
| 472 | + // if location no longer exists, restore file in the root directory |
|
| 473 | + if ($location !== '/' |
|
| 474 | + && (!$view->is_dir('files/' . $location) |
|
| 475 | + || !$view->isCreatable('files/' . $location)) |
|
| 476 | + ) { |
|
| 477 | + $location = ''; |
|
| 478 | + } |
|
| 479 | + } |
|
| 480 | + } |
|
| 481 | + |
|
| 482 | + // we need a extension in case a file/dir with the same name already exists |
|
| 483 | + $uniqueFilename = self::getUniqueFilename($location, $filename, $view); |
|
| 484 | + |
|
| 485 | + $source = Filesystem::normalizePath('files_trashbin/files/' . $file); |
|
| 486 | + $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename); |
|
| 487 | + if (!$view->file_exists($source)) { |
|
| 488 | + return false; |
|
| 489 | + } |
|
| 490 | + $mtime = $view->filemtime($source); |
|
| 491 | + |
|
| 492 | + // restore file |
|
| 493 | + if (!$view->isCreatable(dirname($target))) { |
|
| 494 | + throw new NotPermittedException("Can't restore trash item because the target folder is not writable"); |
|
| 495 | + } |
|
| 496 | + |
|
| 497 | + $sourcePath = Filesystem::normalizePath($file); |
|
| 498 | + $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 499 | + |
|
| 500 | + $sourceNode = self::getNodeForPath($user, $sourcePath); |
|
| 501 | + $targetNode = self::getNodeForPath($user, $targetPath, 'files'); |
|
| 502 | + $run = true; |
|
| 503 | + $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run); |
|
| 504 | + $dispatcher = Server::get(IEventDispatcher::class); |
|
| 505 | + $dispatcher->dispatchTyped($event); |
|
| 506 | + |
|
| 507 | + if (!$run) { |
|
| 508 | + return false; |
|
| 509 | + } |
|
| 510 | + |
|
| 511 | + $restoreResult = $view->rename($source, $target); |
|
| 512 | + |
|
| 513 | + // handle the restore result |
|
| 514 | + if ($restoreResult) { |
|
| 515 | + $fakeRoot = $view->getRoot(); |
|
| 516 | + $view->chroot('/' . $user . '/files'); |
|
| 517 | + $view->touch('/' . $location . '/' . $uniqueFilename, $mtime); |
|
| 518 | + $view->chroot($fakeRoot); |
|
| 519 | + Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]); |
|
| 520 | + |
|
| 521 | + $sourceNode = self::getNodeForPath($user, $sourcePath); |
|
| 522 | + $targetNode = self::getNodeForPath($user, $targetPath, 'files'); |
|
| 523 | + $event = new NodeRestoredEvent($sourceNode, $targetNode); |
|
| 524 | + $dispatcher = Server::get(IEventDispatcher::class); |
|
| 525 | + $dispatcher->dispatchTyped($event); |
|
| 526 | + |
|
| 527 | + self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp); |
|
| 528 | + |
|
| 529 | + if ($timestamp) { |
|
| 530 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 531 | + $query->delete('files_trash') |
|
| 532 | + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 533 | + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 534 | + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 535 | + $query->executeStatement(); |
|
| 536 | + } |
|
| 537 | + |
|
| 538 | + return true; |
|
| 539 | + } |
|
| 540 | + |
|
| 541 | + return false; |
|
| 542 | + } |
|
| 543 | + |
|
| 544 | + /** |
|
| 545 | + * restore versions from trash bin |
|
| 546 | + * |
|
| 547 | + * @param View $view file view |
|
| 548 | + * @param string $file complete path to file |
|
| 549 | + * @param string $filename name of file once it was deleted |
|
| 550 | + * @param string $uniqueFilename new file name to restore the file without overwriting existing files |
|
| 551 | + * @param string $location location if file |
|
| 552 | + * @param int $timestamp deletion time |
|
| 553 | + * @return false|null |
|
| 554 | + */ |
|
| 555 | + private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) { |
|
| 556 | + if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) { |
|
| 557 | + $user = OC_User::getUser(); |
|
| 558 | + $rootView = new View('/'); |
|
| 559 | + |
|
| 560 | + $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 561 | + |
|
| 562 | + [$owner, $ownerPath] = self::getUidAndFilename($target); |
|
| 563 | + |
|
| 564 | + // file has been deleted in between |
|
| 565 | + if (empty($ownerPath)) { |
|
| 566 | + return false; |
|
| 567 | + } |
|
| 568 | + |
|
| 569 | + if ($timestamp) { |
|
| 570 | + $versionedFile = $filename; |
|
| 571 | + } else { |
|
| 572 | + $versionedFile = $file; |
|
| 573 | + } |
|
| 574 | + |
|
| 575 | + if ($view->is_dir('/files_trashbin/versions/' . $file)) { |
|
| 576 | + $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath)); |
|
| 577 | + } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) { |
|
| 578 | + foreach ($versions as $v) { |
|
| 579 | + if ($timestamp) { |
|
| 580 | + $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 581 | + } else { |
|
| 582 | + $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 583 | + } |
|
| 584 | + } |
|
| 585 | + } |
|
| 586 | + } |
|
| 587 | + } |
|
| 588 | + |
|
| 589 | + /** |
|
| 590 | + * delete all files from the trash |
|
| 591 | + */ |
|
| 592 | + public static function deleteAll() { |
|
| 593 | + $user = OC_User::getUser(); |
|
| 594 | + $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
|
| 595 | + $view = new View('/' . $user); |
|
| 596 | + $fileInfos = $view->getDirectoryContent('files_trashbin/files'); |
|
| 597 | + |
|
| 598 | + try { |
|
| 599 | + $trash = $userRoot->get('files_trashbin'); |
|
| 600 | + } catch (NotFoundException $e) { |
|
| 601 | + return false; |
|
| 602 | + } |
|
| 603 | + |
|
| 604 | + // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore) |
|
| 605 | + $filePaths = []; |
|
| 606 | + foreach ($fileInfos as $fileInfo) { |
|
| 607 | + $filePaths[] = $view->getRelativePath($fileInfo->getPath()); |
|
| 608 | + } |
|
| 609 | + unset($fileInfos); // save memory |
|
| 610 | + |
|
| 611 | + // Bulk PreDelete-Hook |
|
| 612 | + \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]); |
|
| 613 | + |
|
| 614 | + // Single-File Hooks |
|
| 615 | + foreach ($filePaths as $path) { |
|
| 616 | + self::emitTrashbinPreDelete($path); |
|
| 617 | + } |
|
| 618 | + |
|
| 619 | + // actual file deletion |
|
| 620 | + $trash->delete(); |
|
| 621 | + |
|
| 622 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 623 | + $query->delete('files_trash') |
|
| 624 | + ->where($query->expr()->eq('user', $query->createNamedParameter($user))); |
|
| 625 | + $query->executeStatement(); |
|
| 626 | + |
|
| 627 | + // Bulk PostDelete-Hook |
|
| 628 | + \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]); |
|
| 629 | + |
|
| 630 | + // Single-File Hooks |
|
| 631 | + foreach ($filePaths as $path) { |
|
| 632 | + self::emitTrashbinPostDelete($path); |
|
| 633 | + } |
|
| 634 | + |
|
| 635 | + $trash = $userRoot->newFolder('files_trashbin'); |
|
| 636 | + $trash->newFolder('files'); |
|
| 637 | + |
|
| 638 | + return true; |
|
| 639 | + } |
|
| 640 | + |
|
| 641 | + /** |
|
| 642 | + * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted |
|
| 643 | + * |
|
| 644 | + * @param string $path |
|
| 645 | + */ |
|
| 646 | + protected static function emitTrashbinPreDelete($path) { |
|
| 647 | + \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]); |
|
| 648 | + } |
|
| 649 | + |
|
| 650 | + /** |
|
| 651 | + * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted |
|
| 652 | + * |
|
| 653 | + * @param string $path |
|
| 654 | + */ |
|
| 655 | + protected static function emitTrashbinPostDelete($path) { |
|
| 656 | + \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]); |
|
| 657 | + } |
|
| 658 | + |
|
| 659 | + /** |
|
| 660 | + * delete file from trash bin permanently |
|
| 661 | + * |
|
| 662 | + * @param string $filename path to the file |
|
| 663 | + * @param string $user |
|
| 664 | + * @param int $timestamp of deletion time |
|
| 665 | + * |
|
| 666 | + * @return int|float size of deleted files |
|
| 667 | + */ |
|
| 668 | + public static function delete($filename, $user, $timestamp = null) { |
|
| 669 | + $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
|
| 670 | + $view = new View('/' . $user); |
|
| 671 | + $size = 0; |
|
| 672 | + |
|
| 673 | + if ($timestamp) { |
|
| 674 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 675 | + $query->delete('files_trash') |
|
| 676 | + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) |
|
| 677 | + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) |
|
| 678 | + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); |
|
| 679 | + $query->executeStatement(); |
|
| 680 | + |
|
| 681 | + $file = static::getTrashFilename($filename, $timestamp); |
|
| 682 | + } else { |
|
| 683 | + $file = $filename; |
|
| 684 | + } |
|
| 685 | + |
|
| 686 | + $size += self::deleteVersions($view, $file, $filename, $timestamp, $user); |
|
| 687 | + |
|
| 688 | + try { |
|
| 689 | + $node = $userRoot->get('/files_trashbin/files/' . $file); |
|
| 690 | + } catch (NotFoundException $e) { |
|
| 691 | + return $size; |
|
| 692 | + } |
|
| 693 | + |
|
| 694 | + if ($node instanceof Folder) { |
|
| 695 | + $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file)); |
|
| 696 | + } elseif ($node instanceof File) { |
|
| 697 | + $size += $view->filesize('/files_trashbin/files/' . $file); |
|
| 698 | + } |
|
| 699 | + |
|
| 700 | + self::emitTrashbinPreDelete('/files_trashbin/files/' . $file); |
|
| 701 | + $node->delete(); |
|
| 702 | + self::emitTrashbinPostDelete('/files_trashbin/files/' . $file); |
|
| 703 | + |
|
| 704 | + return $size; |
|
| 705 | + } |
|
| 706 | + |
|
| 707 | + /** |
|
| 708 | + * @param string $file |
|
| 709 | + * @param string $filename |
|
| 710 | + * @param ?int $timestamp |
|
| 711 | + */ |
|
| 712 | + private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float { |
|
| 713 | + $size = 0; |
|
| 714 | + if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) { |
|
| 715 | + if ($view->is_dir('files_trashbin/versions/' . $file)) { |
|
| 716 | + $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file)); |
|
| 717 | + $view->unlink('files_trashbin/versions/' . $file); |
|
| 718 | + } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) { |
|
| 719 | + foreach ($versions as $v) { |
|
| 720 | + if ($timestamp) { |
|
| 721 | + $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 722 | + $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 723 | + } else { |
|
| 724 | + $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 725 | + $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 726 | + } |
|
| 727 | + } |
|
| 728 | + } |
|
| 729 | + } |
|
| 730 | + return $size; |
|
| 731 | + } |
|
| 732 | + |
|
| 733 | + /** |
|
| 734 | + * check to see whether a file exists in trashbin |
|
| 735 | + * |
|
| 736 | + * @param string $filename path to the file |
|
| 737 | + * @param int $timestamp of deletion time |
|
| 738 | + * @return bool true if file exists, otherwise false |
|
| 739 | + */ |
|
| 740 | + public static function file_exists($filename, $timestamp = null) { |
|
| 741 | + $user = OC_User::getUser(); |
|
| 742 | + $view = new View('/' . $user); |
|
| 743 | + |
|
| 744 | + if ($timestamp) { |
|
| 745 | + $filename = static::getTrashFilename($filename, $timestamp); |
|
| 746 | + } |
|
| 747 | + |
|
| 748 | + $target = Filesystem::normalizePath('files_trashbin/files/' . $filename); |
|
| 749 | + return $view->file_exists($target); |
|
| 750 | + } |
|
| 751 | + |
|
| 752 | + /** |
|
| 753 | + * deletes used space for trash bin in db if user was deleted |
|
| 754 | + * |
|
| 755 | + * @param string $uid id of deleted user |
|
| 756 | + * @return bool result of db delete operation |
|
| 757 | + */ |
|
| 758 | + public static function deleteUser($uid) { |
|
| 759 | + $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 760 | + $query->delete('files_trash') |
|
| 761 | + ->where($query->expr()->eq('user', $query->createNamedParameter($uid))); |
|
| 762 | + return (bool)$query->executeStatement(); |
|
| 763 | + } |
|
| 764 | + |
|
| 765 | + /** |
|
| 766 | + * calculate remaining free space for trash bin |
|
| 767 | + * |
|
| 768 | + * @param int|float $trashbinSize current size of the trash bin |
|
| 769 | + * @param string $user |
|
| 770 | + * @return int|float available free space for trash bin |
|
| 771 | + */ |
|
| 772 | + private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float { |
|
| 773 | + $configuredTrashbinSize = static::getConfiguredTrashbinSize($user); |
|
| 774 | + if ($configuredTrashbinSize > -1) { |
|
| 775 | + return $configuredTrashbinSize - $trashbinSize; |
|
| 776 | + } |
|
| 777 | + |
|
| 778 | + $userObject = Server::get(IUserManager::class)->get($user); |
|
| 779 | + if (is_null($userObject)) { |
|
| 780 | + return 0; |
|
| 781 | + } |
|
| 782 | + $softQuota = true; |
|
| 783 | + $quota = $userObject->getQuota(); |
|
| 784 | + if ($quota === null || $quota === 'none') { |
|
| 785 | + $quota = Filesystem::free_space('/'); |
|
| 786 | + $softQuota = false; |
|
| 787 | + // inf or unknown free space |
|
| 788 | + if ($quota < 0) { |
|
| 789 | + $quota = PHP_INT_MAX; |
|
| 790 | + } |
|
| 791 | + } else { |
|
| 792 | + $quota = Util::computerFileSize($quota); |
|
| 793 | + // invalid quota |
|
| 794 | + if ($quota === false) { |
|
| 795 | + $quota = PHP_INT_MAX; |
|
| 796 | + } |
|
| 797 | + } |
|
| 798 | + |
|
| 799 | + // calculate available space for trash bin |
|
| 800 | + // subtract size of files and current trash bin size from quota |
|
| 801 | + if ($softQuota) { |
|
| 802 | + $userFolder = \OC::$server->getUserFolder($user); |
|
| 803 | + if (is_null($userFolder)) { |
|
| 804 | + return 0; |
|
| 805 | + } |
|
| 806 | + $free = $quota - $userFolder->getSize(false); // remaining free space for user |
|
| 807 | + if ($free > 0) { |
|
| 808 | + $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions |
|
| 809 | + } else { |
|
| 810 | + $availableSpace = $free - $trashbinSize; |
|
| 811 | + } |
|
| 812 | + } else { |
|
| 813 | + $availableSpace = $quota; |
|
| 814 | + } |
|
| 815 | + |
|
| 816 | + return Util::numericToNumber($availableSpace); |
|
| 817 | + } |
|
| 818 | + |
|
| 819 | + /** |
|
| 820 | + * resize trash bin if necessary after a new file was added to Nextcloud |
|
| 821 | + * |
|
| 822 | + * @param string $user user id |
|
| 823 | + */ |
|
| 824 | + public static function resizeTrash($user) { |
|
| 825 | + $size = self::getTrashbinSize($user); |
|
| 826 | + |
|
| 827 | + $freeSpace = self::calculateFreeSpace($size, $user); |
|
| 828 | + |
|
| 829 | + if ($freeSpace < 0) { |
|
| 830 | + self::scheduleExpire($user); |
|
| 831 | + } |
|
| 832 | + } |
|
| 833 | + |
|
| 834 | + /** |
|
| 835 | + * clean up the trash bin |
|
| 836 | + * |
|
| 837 | + * @param string $user |
|
| 838 | + */ |
|
| 839 | + public static function expire($user) { |
|
| 840 | + $trashBinSize = self::getTrashbinSize($user); |
|
| 841 | + $availableSpace = self::calculateFreeSpace($trashBinSize, $user); |
|
| 842 | + |
|
| 843 | + $dirContent = Helper::getTrashFiles('/', $user, 'mtime'); |
|
| 844 | + |
|
| 845 | + // delete all files older then $retention_obligation |
|
| 846 | + [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user); |
|
| 847 | + |
|
| 848 | + $availableSpace += $delSize; |
|
| 849 | + |
|
| 850 | + // delete files from trash until we meet the trash bin size limit again |
|
| 851 | + self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace); |
|
| 852 | + } |
|
| 853 | + |
|
| 854 | + /** |
|
| 855 | + * @param string $user |
|
| 856 | + */ |
|
| 857 | + private static function scheduleExpire($user) { |
|
| 858 | + // let the admin disable auto expire |
|
| 859 | + $expiration = Server::get(Expiration::class); |
|
| 860 | + if ($expiration->isEnabled()) { |
|
| 861 | + Server::get(IBus::class)->push(new Expire($user)); |
|
| 862 | + } |
|
| 863 | + } |
|
| 864 | + |
|
| 865 | + /** |
|
| 866 | + * if the size limit for the trash bin is reached, we delete the oldest |
|
| 867 | + * files in the trash bin until we meet the limit again |
|
| 868 | + * |
|
| 869 | + * @param array $files |
|
| 870 | + * @param string $user |
|
| 871 | + * @param int|float $availableSpace available disc space |
|
| 872 | + * @return int|float size of deleted files |
|
| 873 | + */ |
|
| 874 | + protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float { |
|
| 875 | + $expiration = Server::get(Expiration::class); |
|
| 876 | + $size = 0; |
|
| 877 | + |
|
| 878 | + if ($availableSpace <= 0) { |
|
| 879 | + foreach ($files as $file) { |
|
| 880 | + if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) { |
|
| 881 | + $tmp = self::delete($file['name'], $user, $file['mtime']); |
|
| 882 | + Server::get(LoggerInterface::class)->info( |
|
| 883 | + 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"', |
|
| 884 | + [ |
|
| 885 | + 'app' => 'files_trashbin', |
|
| 886 | + 'user' => $user, |
|
| 887 | + ] |
|
| 888 | + ); |
|
| 889 | + $availableSpace += $tmp; |
|
| 890 | + $size += $tmp; |
|
| 891 | + } else { |
|
| 892 | + break; |
|
| 893 | + } |
|
| 894 | + } |
|
| 895 | + } |
|
| 896 | + return $size; |
|
| 897 | + } |
|
| 898 | + |
|
| 899 | + /** |
|
| 900 | + * delete files older then max storage time |
|
| 901 | + * |
|
| 902 | + * @param array $files list of files sorted by mtime |
|
| 903 | + * @param string $user |
|
| 904 | + * @return array{int|float, int} size of deleted files and number of deleted files |
|
| 905 | + */ |
|
| 906 | + public static function deleteExpiredFiles($files, $user) { |
|
| 907 | + $expiration = Server::get(Expiration::class); |
|
| 908 | + $size = 0; |
|
| 909 | + $count = 0; |
|
| 910 | + foreach ($files as $file) { |
|
| 911 | + $timestamp = $file['mtime']; |
|
| 912 | + $filename = $file['name']; |
|
| 913 | + if ($expiration->isExpired($timestamp)) { |
|
| 914 | + try { |
|
| 915 | + $size += self::delete($filename, $user, $timestamp); |
|
| 916 | + $count++; |
|
| 917 | + } catch (NotPermittedException $e) { |
|
| 918 | + Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"', |
|
| 919 | + [ |
|
| 920 | + 'exception' => $e, |
|
| 921 | + 'app' => 'files_trashbin', |
|
| 922 | + 'user' => $user, |
|
| 923 | + ] |
|
| 924 | + ); |
|
| 925 | + } |
|
| 926 | + Server::get(LoggerInterface::class)->info( |
|
| 927 | + 'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.', |
|
| 928 | + [ |
|
| 929 | + 'app' => 'files_trashbin', |
|
| 930 | + 'user' => $user, |
|
| 931 | + ], |
|
| 932 | + ); |
|
| 933 | + } else { |
|
| 934 | + break; |
|
| 935 | + } |
|
| 936 | + } |
|
| 937 | + |
|
| 938 | + return [$size, $count]; |
|
| 939 | + } |
|
| 940 | + |
|
| 941 | + /** |
|
| 942 | + * recursive copy to copy a whole directory |
|
| 943 | + * |
|
| 944 | + * @param string $source source path, relative to the users files directory |
|
| 945 | + * @param string $destination destination path relative to the users root directory |
|
| 946 | + * @param View $view file view for the users root directory |
|
| 947 | + * @return int|float |
|
| 948 | + * @throws Exceptions\CopyRecursiveException |
|
| 949 | + */ |
|
| 950 | + private static function copy_recursive($source, $destination, View $view): int|float { |
|
| 951 | + $size = 0; |
|
| 952 | + if ($view->is_dir($source)) { |
|
| 953 | + $view->mkdir($destination); |
|
| 954 | + $view->touch($destination, $view->filemtime($source)); |
|
| 955 | + foreach ($view->getDirectoryContent($source) as $i) { |
|
| 956 | + $pathDir = $source . '/' . $i['name']; |
|
| 957 | + if ($view->is_dir($pathDir)) { |
|
| 958 | + $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view); |
|
| 959 | + } else { |
|
| 960 | + $size += $view->filesize($pathDir); |
|
| 961 | + $result = $view->copy($pathDir, $destination . '/' . $i['name']); |
|
| 962 | + if (!$result) { |
|
| 963 | + throw new CopyRecursiveException(); |
|
| 964 | + } |
|
| 965 | + $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir)); |
|
| 966 | + } |
|
| 967 | + } |
|
| 968 | + } else { |
|
| 969 | + $size += $view->filesize($source); |
|
| 970 | + $result = $view->copy($source, $destination); |
|
| 971 | + if (!$result) { |
|
| 972 | + throw new CopyRecursiveException(); |
|
| 973 | + } |
|
| 974 | + $view->touch($destination, $view->filemtime($source)); |
|
| 975 | + } |
|
| 976 | + return $size; |
|
| 977 | + } |
|
| 978 | + |
|
| 979 | + /** |
|
| 980 | + * find all versions which belong to the file we want to restore |
|
| 981 | + * |
|
| 982 | + * @param string $filename name of the file which should be restored |
|
| 983 | + * @param int $timestamp timestamp when the file was deleted |
|
| 984 | + */ |
|
| 985 | + private static function getVersionsFromTrash($filename, $timestamp, string $user): array { |
|
| 986 | + $view = new View('/' . $user . '/files_trashbin/versions'); |
|
| 987 | + $versions = []; |
|
| 988 | + |
|
| 989 | + /** @var \OC\Files\Storage\Storage $storage */ |
|
| 990 | + [$storage,] = $view->resolvePath('/'); |
|
| 991 | + |
|
| 992 | + $pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename)); |
|
| 993 | + if ($timestamp) { |
|
| 994 | + // fetch for old versions |
|
| 995 | + $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp); |
|
| 996 | + $pattern .= '.v%.d' . $escapedTimestamp; |
|
| 997 | + $offset = -strlen($escapedTimestamp) - 2; |
|
| 998 | + } else { |
|
| 999 | + $pattern .= '.v%'; |
|
| 1000 | + } |
|
| 1001 | + |
|
| 1002 | + // Manually fetch all versions from the file cache to be able to filter them by their parent |
|
| 1003 | + $cache = $storage->getCache(''); |
|
| 1004 | + $query = new CacheQueryBuilder( |
|
| 1005 | + Server::get(IDBConnection::class)->getQueryBuilder(), |
|
| 1006 | + Server::get(IFilesMetadataManager::class), |
|
| 1007 | + ); |
|
| 1008 | + $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/'); |
|
| 1009 | + $parentId = $cache->getId($normalizedParentPath); |
|
| 1010 | + if ($parentId === -1) { |
|
| 1011 | + return []; |
|
| 1012 | + } |
|
| 1013 | + |
|
| 1014 | + $query->selectFileCache() |
|
| 1015 | + ->whereStorageId($cache->getNumericStorageId()) |
|
| 1016 | + ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId))) |
|
| 1017 | + ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern))); |
|
| 1018 | + |
|
| 1019 | + $result = $query->executeQuery(); |
|
| 1020 | + $entries = $result->fetchAllAssociative(); |
|
| 1021 | + $result->closeCursor(); |
|
| 1022 | + |
|
| 1023 | + /** @var CacheEntry[] $matches */ |
|
| 1024 | + $matches = array_map(function (array $data) { |
|
| 1025 | + return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class)); |
|
| 1026 | + }, $entries); |
|
| 1027 | + |
|
| 1028 | + foreach ($matches as $ma) { |
|
| 1029 | + if ($timestamp) { |
|
| 1030 | + $parts = explode('.v', substr($ma['path'], 0, $offset)); |
|
| 1031 | + $versions[] = end($parts); |
|
| 1032 | + } else { |
|
| 1033 | + $parts = explode('.v', $ma['path']); |
|
| 1034 | + $versions[] = end($parts); |
|
| 1035 | + } |
|
| 1036 | + } |
|
| 1037 | + |
|
| 1038 | + return $versions; |
|
| 1039 | + } |
|
| 1040 | + |
|
| 1041 | + /** |
|
| 1042 | + * find unique extension for restored file if a file with the same name already exists |
|
| 1043 | + * |
|
| 1044 | + * @param string $location where the file should be restored |
|
| 1045 | + * @param string $filename name of the file |
|
| 1046 | + * @param View $view filesystem view relative to users root directory |
|
| 1047 | + * @return string with unique extension |
|
| 1048 | + */ |
|
| 1049 | + private static function getUniqueFilename($location, $filename, View $view) { |
|
| 1050 | + $ext = pathinfo($filename, PATHINFO_EXTENSION); |
|
| 1051 | + $name = pathinfo($filename, PATHINFO_FILENAME); |
|
| 1052 | + $l = Util::getL10N('files_trashbin'); |
|
| 1053 | + |
|
| 1054 | + $location = '/' . trim($location, '/'); |
|
| 1055 | + |
|
| 1056 | + // if extension is not empty we set a dot in front of it |
|
| 1057 | + if ($ext !== '') { |
|
| 1058 | + $ext = '.' . $ext; |
|
| 1059 | + } |
|
| 1060 | + |
|
| 1061 | + if ($view->file_exists('files' . $location . '/' . $filename)) { |
|
| 1062 | + $i = 2; |
|
| 1063 | + $uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext; |
|
| 1064 | + while ($view->file_exists('files' . $location . '/' . $uniqueName)) { |
|
| 1065 | + $uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext; |
|
| 1066 | + $i++; |
|
| 1067 | + } |
|
| 1068 | + |
|
| 1069 | + return $uniqueName; |
|
| 1070 | + } |
|
| 1071 | + |
|
| 1072 | + return $filename; |
|
| 1073 | + } |
|
| 1074 | + |
|
| 1075 | + /** |
|
| 1076 | + * get the size from a given root folder |
|
| 1077 | + * |
|
| 1078 | + * @param View $view file view on the root folder |
|
| 1079 | + * @return int|float size of the folder |
|
| 1080 | + */ |
|
| 1081 | + private static function calculateSize(View $view): int|float { |
|
| 1082 | + $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath(''); |
|
| 1083 | + if (!file_exists($root)) { |
|
| 1084 | + return 0; |
|
| 1085 | + } |
|
| 1086 | + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST); |
|
| 1087 | + $size = 0; |
|
| 1088 | + |
|
| 1089 | + /** |
|
| 1090 | + * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach |
|
| 1091 | + * This bug is fixed in PHP 5.5.9 or before |
|
| 1092 | + * See #8376 |
|
| 1093 | + */ |
|
| 1094 | + $iterator->rewind(); |
|
| 1095 | + while ($iterator->valid()) { |
|
| 1096 | + $path = $iterator->current(); |
|
| 1097 | + $relpath = substr($path, strlen($root) - 1); |
|
| 1098 | + if (!$view->is_dir($relpath)) { |
|
| 1099 | + $size += $view->filesize($relpath); |
|
| 1100 | + } |
|
| 1101 | + $iterator->next(); |
|
| 1102 | + } |
|
| 1103 | + return $size; |
|
| 1104 | + } |
|
| 1105 | + |
|
| 1106 | + /** |
|
| 1107 | + * get current size of trash bin from a given user |
|
| 1108 | + * |
|
| 1109 | + * @param string $user user who owns the trash bin |
|
| 1110 | + * @return int|float trash bin size |
|
| 1111 | + */ |
|
| 1112 | + private static function getTrashbinSize(string $user): int|float { |
|
| 1113 | + $view = new View('/' . $user); |
|
| 1114 | + $fileInfo = $view->getFileInfo('/files_trashbin'); |
|
| 1115 | + return isset($fileInfo['size']) ? $fileInfo['size'] : 0; |
|
| 1116 | + } |
|
| 1117 | + |
|
| 1118 | + /** |
|
| 1119 | + * check if trash bin is empty for a given user |
|
| 1120 | + * |
|
| 1121 | + * @param string $user |
|
| 1122 | + * @return bool |
|
| 1123 | + */ |
|
| 1124 | + public static function isEmpty($user) { |
|
| 1125 | + $view = new View('/' . $user . '/files_trashbin'); |
|
| 1126 | + if ($view->is_dir('/files') && $dh = $view->opendir('/files')) { |
|
| 1127 | + while (($file = readdir($dh)) !== false) { |
|
| 1128 | + if (!Filesystem::isIgnoredDir($file)) { |
|
| 1129 | + return false; |
|
| 1130 | + } |
|
| 1131 | + } |
|
| 1132 | + } |
|
| 1133 | + return true; |
|
| 1134 | + } |
|
| 1135 | + |
|
| 1136 | + /** |
|
| 1137 | + * @param $path |
|
| 1138 | + * @return string |
|
| 1139 | + */ |
|
| 1140 | + public static function preview_icon($path) { |
|
| 1141 | + return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]); |
|
| 1142 | + } |
|
| 1143 | + |
|
| 1144 | + /** |
|
| 1145 | + * Return the filename used in the trash bin |
|
| 1146 | + */ |
|
| 1147 | + public static function getTrashFilename(string $filename, int $timestamp): string { |
|
| 1148 | + $trashFilename = $filename . '.d' . $timestamp; |
|
| 1149 | + $length = strlen($trashFilename); |
|
| 1150 | + // oc_filecache `name` column has a limit of 250 chars |
|
| 1151 | + $maxLength = 250; |
|
| 1152 | + if ($length > $maxLength) { |
|
| 1153 | + $trashFilename = substr_replace( |
|
| 1154 | + $trashFilename, |
|
| 1155 | + '', |
|
| 1156 | + $maxLength / 2, |
|
| 1157 | + $length - $maxLength |
|
| 1158 | + ); |
|
| 1159 | + } |
|
| 1160 | + return $trashFilename; |
|
| 1161 | + } |
|
| 1162 | + |
|
| 1163 | + private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node { |
|
| 1164 | + $rootFolder = Server::get(IRootFolder::class); |
|
| 1165 | + $path = ltrim($path, '/'); |
|
| 1166 | + |
|
| 1167 | + $userFolder = $rootFolder->getUserFolder($user); |
|
| 1168 | + /** @var Folder $trashFolder */ |
|
| 1169 | + $trashFolder = $userFolder->getParent()->get($baseDir); |
|
| 1170 | + try { |
|
| 1171 | + return $trashFolder->get($path); |
|
| 1172 | + } catch (NotFoundException $ex) { |
|
| 1173 | + } |
|
| 1174 | + |
|
| 1175 | + $view = Server::get(View::class); |
|
| 1176 | + $fullPath = '/' . $user . '/' . $baseDir . '/' . $path; |
|
| 1177 | + |
|
| 1178 | + if (Filesystem::is_dir($path)) { |
|
| 1179 | + return new NonExistingFolder($rootFolder, $view, $fullPath); |
|
| 1180 | + } else { |
|
| 1181 | + return new NonExistingFile($rootFolder, $view, $fullPath); |
|
| 1182 | + } |
|
| 1183 | + } |
|
| 1184 | + |
|
| 1185 | + public function handle(Event $event): void { |
|
| 1186 | + if ($event instanceof BeforeNodeDeletedEvent) { |
|
| 1187 | + self::ensureFileScannedHook($event->getNode()); |
|
| 1188 | + } |
|
| 1189 | + } |
|
| 1190 | 1190 | } |
@@ -89,7 +89,7 @@ discard block |
||
| 89 | 89 | Filesystem::initMountPoints($uid); |
| 90 | 90 | if ($uid !== OC_User::getUser()) { |
| 91 | 91 | $info = Filesystem::getFileInfo($filename); |
| 92 | - $ownerView = new View('/' . $uid . '/files'); |
|
| 92 | + $ownerView = new View('/'.$uid.'/files'); |
|
| 93 | 93 | try { |
| 94 | 94 | $filename = $ownerView->getPath($info['fileid']); |
| 95 | 95 | } catch (NotFoundException $e) { |
@@ -114,8 +114,8 @@ discard block |
||
| 114 | 114 | $array = []; |
| 115 | 115 | foreach ($result->iterateAssociative() as $row) { |
| 116 | 116 | $array[$row['id']][$row['timestamp']] = [ |
| 117 | - 'location' => (string)$row['location'], |
|
| 118 | - 'deletedBy' => (string)$row['deleted_by'], |
|
| 117 | + 'location' => (string) $row['location'], |
|
| 118 | + 'deletedBy' => (string) $row['deleted_by'], |
|
| 119 | 119 | ]; |
| 120 | 120 | } |
| 121 | 121 | $result->closeCursor(); |
@@ -151,7 +151,7 @@ discard block |
||
| 151 | 151 | |
| 152 | 152 | /** @param string $user */ |
| 153 | 153 | private static function setUpTrash($user): void { |
| 154 | - $view = new View('/' . $user); |
|
| 154 | + $view = new View('/'.$user); |
|
| 155 | 155 | if (!$view->is_dir('files_trashbin')) { |
| 156 | 156 | $view->mkdir('files_trashbin'); |
| 157 | 157 | } |
@@ -186,8 +186,8 @@ discard block |
||
| 186 | 186 | |
| 187 | 187 | $view = new View('/'); |
| 188 | 188 | |
| 189 | - $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp); |
|
| 190 | - $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp); |
|
| 189 | + $target = $user.'/files_trashbin/files/'.static::getTrashFilename($targetFilename, $timestamp); |
|
| 190 | + $source = $owner.'/files_trashbin/files/'.static::getTrashFilename($sourceFilename, $timestamp); |
|
| 191 | 191 | $free = $view->free_space($target); |
| 192 | 192 | $isUnknownOrUnlimitedFreeSpace = $free < 0; |
| 193 | 193 | $isEnoughFreeSpaceLeft = $view->filesize($source) < $free; |
@@ -232,14 +232,14 @@ discard block |
||
| 232 | 232 | $ownerPath = $file_path; |
| 233 | 233 | } |
| 234 | 234 | |
| 235 | - $ownerView = new View('/' . $owner); |
|
| 235 | + $ownerView = new View('/'.$owner); |
|
| 236 | 236 | |
| 237 | 237 | // file has been deleted in between |
| 238 | 238 | if (is_null($ownerPath) || $ownerPath === '') { |
| 239 | 239 | return true; |
| 240 | 240 | } |
| 241 | 241 | |
| 242 | - $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath); |
|
| 242 | + $sourceInfo = $ownerView->getFileInfo('/files/'.$ownerPath); |
|
| 243 | 243 | |
| 244 | 244 | if ($sourceInfo === false) { |
| 245 | 245 | return true; |
@@ -262,7 +262,7 @@ discard block |
||
| 262 | 262 | $lockingProvider = Server::get(ILockingProvider::class); |
| 263 | 263 | |
| 264 | 264 | // disable proxy to prevent recursive calls |
| 265 | - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 265 | + $trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp); |
|
| 266 | 266 | $gotLock = false; |
| 267 | 267 | |
| 268 | 268 | do { |
@@ -277,7 +277,7 @@ discard block |
||
| 277 | 277 | |
| 278 | 278 | $timestamp = $timestamp + 1; |
| 279 | 279 | |
| 280 | - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); |
|
| 280 | + $trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp); |
|
| 281 | 281 | } |
| 282 | 282 | } while (!$gotLock); |
| 283 | 283 | |
@@ -306,7 +306,7 @@ discard block |
||
| 306 | 306 | if ($trashStorage->file_exists($trashInternalPath)) { |
| 307 | 307 | $trashStorage->unlink($trashInternalPath); |
| 308 | 308 | } |
| 309 | - Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']); |
|
| 309 | + Server::get(LoggerInterface::class)->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']); |
|
| 310 | 310 | } |
| 311 | 311 | |
| 312 | 312 | if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort |
@@ -360,7 +360,7 @@ discard block |
||
| 360 | 360 | return $moveSuccessful; |
| 361 | 361 | } |
| 362 | 362 | |
| 363 | - private static function getConfiguredTrashbinSize(string $user): int|float { |
|
| 363 | + private static function getConfiguredTrashbinSize(string $user): int | float { |
|
| 364 | 364 | $config = Server::get(IConfig::class); |
| 365 | 365 | $userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1'); |
| 366 | 366 | if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) { |
@@ -386,17 +386,17 @@ discard block |
||
| 386 | 386 | $user = OC_User::getUser(); |
| 387 | 387 | $rootView = new View('/'); |
| 388 | 388 | |
| 389 | - if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { |
|
| 389 | + if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) { |
|
| 390 | 390 | if ($owner !== $user) { |
| 391 | - self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView); |
|
| 391 | + self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.static::getTrashFilename(basename($ownerPath), $timestamp), $rootView); |
|
| 392 | 392 | } |
| 393 | - self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp)); |
|
| 393 | + self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.static::getTrashFilename($filename, $timestamp)); |
|
| 394 | 394 | } elseif ($versions = Storage::getVersions($owner, $ownerPath)) { |
| 395 | 395 | foreach ($versions as $v) { |
| 396 | 396 | if ($owner !== $user) { |
| 397 | - self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp)); |
|
| 397 | + self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.static::getTrashFilename($v['name'].'.v'.$v['version'], $timestamp)); |
|
| 398 | 398 | } |
| 399 | - self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp)); |
|
| 399 | + self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v['version'], $timestamp)); |
|
| 400 | 400 | } |
| 401 | 401 | } |
| 402 | 402 | } |
@@ -461,18 +461,18 @@ discard block |
||
| 461 | 461 | if (!$user) { |
| 462 | 462 | throw new \Exception('Tried to restore a file while not logged in'); |
| 463 | 463 | } |
| 464 | - $view = new View('/' . $user); |
|
| 464 | + $view = new View('/'.$user); |
|
| 465 | 465 | |
| 466 | 466 | $location = ''; |
| 467 | 467 | if ($timestamp) { |
| 468 | 468 | $location = self::getLocation($user, $filename, $timestamp); |
| 469 | 469 | if ($location === false) { |
| 470 | - Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']); |
|
| 470 | + Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']); |
|
| 471 | 471 | } else { |
| 472 | 472 | // if location no longer exists, restore file in the root directory |
| 473 | 473 | if ($location !== '/' |
| 474 | - && (!$view->is_dir('files/' . $location) |
|
| 475 | - || !$view->isCreatable('files/' . $location)) |
|
| 474 | + && (!$view->is_dir('files/'.$location) |
|
| 475 | + || !$view->isCreatable('files/'.$location)) |
|
| 476 | 476 | ) { |
| 477 | 477 | $location = ''; |
| 478 | 478 | } |
@@ -482,8 +482,8 @@ discard block |
||
| 482 | 482 | // we need a extension in case a file/dir with the same name already exists |
| 483 | 483 | $uniqueFilename = self::getUniqueFilename($location, $filename, $view); |
| 484 | 484 | |
| 485 | - $source = Filesystem::normalizePath('files_trashbin/files/' . $file); |
|
| 486 | - $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename); |
|
| 485 | + $source = Filesystem::normalizePath('files_trashbin/files/'.$file); |
|
| 486 | + $target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename); |
|
| 487 | 487 | if (!$view->file_exists($source)) { |
| 488 | 488 | return false; |
| 489 | 489 | } |
@@ -495,7 +495,7 @@ discard block |
||
| 495 | 495 | } |
| 496 | 496 | |
| 497 | 497 | $sourcePath = Filesystem::normalizePath($file); |
| 498 | - $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 498 | + $targetPath = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename); |
|
| 499 | 499 | |
| 500 | 500 | $sourceNode = self::getNodeForPath($user, $sourcePath); |
| 501 | 501 | $targetNode = self::getNodeForPath($user, $targetPath, 'files'); |
@@ -513,8 +513,8 @@ discard block |
||
| 513 | 513 | // handle the restore result |
| 514 | 514 | if ($restoreResult) { |
| 515 | 515 | $fakeRoot = $view->getRoot(); |
| 516 | - $view->chroot('/' . $user . '/files'); |
|
| 517 | - $view->touch('/' . $location . '/' . $uniqueFilename, $mtime); |
|
| 516 | + $view->chroot('/'.$user.'/files'); |
|
| 517 | + $view->touch('/'.$location.'/'.$uniqueFilename, $mtime); |
|
| 518 | 518 | $view->chroot($fakeRoot); |
| 519 | 519 | Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]); |
| 520 | 520 | |
@@ -557,7 +557,7 @@ discard block |
||
| 557 | 557 | $user = OC_User::getUser(); |
| 558 | 558 | $rootView = new View('/'); |
| 559 | 559 | |
| 560 | - $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); |
|
| 560 | + $target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename); |
|
| 561 | 561 | |
| 562 | 562 | [$owner, $ownerPath] = self::getUidAndFilename($target); |
| 563 | 563 | |
@@ -572,14 +572,14 @@ discard block |
||
| 572 | 572 | $versionedFile = $file; |
| 573 | 573 | } |
| 574 | 574 | |
| 575 | - if ($view->is_dir('/files_trashbin/versions/' . $file)) { |
|
| 576 | - $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath)); |
|
| 575 | + if ($view->is_dir('/files_trashbin/versions/'.$file)) { |
|
| 576 | + $rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath)); |
|
| 577 | 577 | } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) { |
| 578 | 578 | foreach ($versions as $v) { |
| 579 | 579 | if ($timestamp) { |
| 580 | - $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 580 | + $rootView->rename($user.'/files_trashbin/versions/'.static::getTrashFilename($versionedFile.'.v'.$v, $timestamp), $owner.'/files_versions/'.$ownerPath.'.v'.$v); |
|
| 581 | 581 | } else { |
| 582 | - $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v); |
|
| 582 | + $rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v); |
|
| 583 | 583 | } |
| 584 | 584 | } |
| 585 | 585 | } |
@@ -592,7 +592,7 @@ discard block |
||
| 592 | 592 | public static function deleteAll() { |
| 593 | 593 | $user = OC_User::getUser(); |
| 594 | 594 | $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
| 595 | - $view = new View('/' . $user); |
|
| 595 | + $view = new View('/'.$user); |
|
| 596 | 596 | $fileInfos = $view->getDirectoryContent('files_trashbin/files'); |
| 597 | 597 | |
| 598 | 598 | try { |
@@ -667,7 +667,7 @@ discard block |
||
| 667 | 667 | */ |
| 668 | 668 | public static function delete($filename, $user, $timestamp = null) { |
| 669 | 669 | $userRoot = \OC::$server->getUserFolder($user)->getParent(); |
| 670 | - $view = new View('/' . $user); |
|
| 670 | + $view = new View('/'.$user); |
|
| 671 | 671 | $size = 0; |
| 672 | 672 | |
| 673 | 673 | if ($timestamp) { |
@@ -686,20 +686,20 @@ discard block |
||
| 686 | 686 | $size += self::deleteVersions($view, $file, $filename, $timestamp, $user); |
| 687 | 687 | |
| 688 | 688 | try { |
| 689 | - $node = $userRoot->get('/files_trashbin/files/' . $file); |
|
| 689 | + $node = $userRoot->get('/files_trashbin/files/'.$file); |
|
| 690 | 690 | } catch (NotFoundException $e) { |
| 691 | 691 | return $size; |
| 692 | 692 | } |
| 693 | 693 | |
| 694 | 694 | if ($node instanceof Folder) { |
| 695 | - $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file)); |
|
| 695 | + $size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file)); |
|
| 696 | 696 | } elseif ($node instanceof File) { |
| 697 | - $size += $view->filesize('/files_trashbin/files/' . $file); |
|
| 697 | + $size += $view->filesize('/files_trashbin/files/'.$file); |
|
| 698 | 698 | } |
| 699 | 699 | |
| 700 | - self::emitTrashbinPreDelete('/files_trashbin/files/' . $file); |
|
| 700 | + self::emitTrashbinPreDelete('/files_trashbin/files/'.$file); |
|
| 701 | 701 | $node->delete(); |
| 702 | - self::emitTrashbinPostDelete('/files_trashbin/files/' . $file); |
|
| 702 | + self::emitTrashbinPostDelete('/files_trashbin/files/'.$file); |
|
| 703 | 703 | |
| 704 | 704 | return $size; |
| 705 | 705 | } |
@@ -709,20 +709,20 @@ discard block |
||
| 709 | 709 | * @param string $filename |
| 710 | 710 | * @param ?int $timestamp |
| 711 | 711 | */ |
| 712 | - private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float { |
|
| 712 | + private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int | float { |
|
| 713 | 713 | $size = 0; |
| 714 | 714 | if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) { |
| 715 | - if ($view->is_dir('files_trashbin/versions/' . $file)) { |
|
| 716 | - $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file)); |
|
| 717 | - $view->unlink('files_trashbin/versions/' . $file); |
|
| 715 | + if ($view->is_dir('files_trashbin/versions/'.$file)) { |
|
| 716 | + $size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file)); |
|
| 717 | + $view->unlink('files_trashbin/versions/'.$file); |
|
| 718 | 718 | } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) { |
| 719 | 719 | foreach ($versions as $v) { |
| 720 | 720 | if ($timestamp) { |
| 721 | - $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 722 | - $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp)); |
|
| 721 | + $size += $view->filesize('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp)); |
|
| 722 | + $view->unlink('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp)); |
|
| 723 | 723 | } else { |
| 724 | - $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 725 | - $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v); |
|
| 724 | + $size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v); |
|
| 725 | + $view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v); |
|
| 726 | 726 | } |
| 727 | 727 | } |
| 728 | 728 | } |
@@ -739,13 +739,13 @@ discard block |
||
| 739 | 739 | */ |
| 740 | 740 | public static function file_exists($filename, $timestamp = null) { |
| 741 | 741 | $user = OC_User::getUser(); |
| 742 | - $view = new View('/' . $user); |
|
| 742 | + $view = new View('/'.$user); |
|
| 743 | 743 | |
| 744 | 744 | if ($timestamp) { |
| 745 | 745 | $filename = static::getTrashFilename($filename, $timestamp); |
| 746 | 746 | } |
| 747 | 747 | |
| 748 | - $target = Filesystem::normalizePath('files_trashbin/files/' . $filename); |
|
| 748 | + $target = Filesystem::normalizePath('files_trashbin/files/'.$filename); |
|
| 749 | 749 | return $view->file_exists($target); |
| 750 | 750 | } |
| 751 | 751 | |
@@ -759,7 +759,7 @@ discard block |
||
| 759 | 759 | $query = Server::get(IDBConnection::class)->getQueryBuilder(); |
| 760 | 760 | $query->delete('files_trash') |
| 761 | 761 | ->where($query->expr()->eq('user', $query->createNamedParameter($uid))); |
| 762 | - return (bool)$query->executeStatement(); |
|
| 762 | + return (bool) $query->executeStatement(); |
|
| 763 | 763 | } |
| 764 | 764 | |
| 765 | 765 | /** |
@@ -769,7 +769,7 @@ discard block |
||
| 769 | 769 | * @param string $user |
| 770 | 770 | * @return int|float available free space for trash bin |
| 771 | 771 | */ |
| 772 | - private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float { |
|
| 772 | + private static function calculateFreeSpace(int | float $trashbinSize, string $user): int | float { |
|
| 773 | 773 | $configuredTrashbinSize = static::getConfiguredTrashbinSize($user); |
| 774 | 774 | if ($configuredTrashbinSize > -1) { |
| 775 | 775 | return $configuredTrashbinSize - $trashbinSize; |
@@ -871,7 +871,7 @@ discard block |
||
| 871 | 871 | * @param int|float $availableSpace available disc space |
| 872 | 872 | * @return int|float size of deleted files |
| 873 | 873 | */ |
| 874 | - protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float { |
|
| 874 | + protected static function deleteFiles(array $files, string $user, int | float $availableSpace): int | float { |
|
| 875 | 875 | $expiration = Server::get(Expiration::class); |
| 876 | 876 | $size = 0; |
| 877 | 877 | |
@@ -880,7 +880,7 @@ discard block |
||
| 880 | 880 | if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) { |
| 881 | 881 | $tmp = self::delete($file['name'], $user, $file['mtime']); |
| 882 | 882 | Server::get(LoggerInterface::class)->info( |
| 883 | - 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"', |
|
| 883 | + 'remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"', |
|
| 884 | 884 | [ |
| 885 | 885 | 'app' => 'files_trashbin', |
| 886 | 886 | 'user' => $user, |
@@ -915,7 +915,7 @@ discard block |
||
| 915 | 915 | $size += self::delete($filename, $user, $timestamp); |
| 916 | 916 | $count++; |
| 917 | 917 | } catch (NotPermittedException $e) { |
| 918 | - Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"', |
|
| 918 | + Server::get(LoggerInterface::class)->warning('Removing "'.$filename.'" from trashbin failed for user "{user}"', |
|
| 919 | 919 | [ |
| 920 | 920 | 'exception' => $e, |
| 921 | 921 | 'app' => 'files_trashbin', |
@@ -924,7 +924,7 @@ discard block |
||
| 924 | 924 | ); |
| 925 | 925 | } |
| 926 | 926 | Server::get(LoggerInterface::class)->info( |
| 927 | - 'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.', |
|
| 927 | + 'Remove "'.$filename.'" from trashbin for user "{user}" because it exceeds max retention obligation term.', |
|
| 928 | 928 | [ |
| 929 | 929 | 'app' => 'files_trashbin', |
| 930 | 930 | 'user' => $user, |
@@ -947,22 +947,22 @@ discard block |
||
| 947 | 947 | * @return int|float |
| 948 | 948 | * @throws Exceptions\CopyRecursiveException |
| 949 | 949 | */ |
| 950 | - private static function copy_recursive($source, $destination, View $view): int|float { |
|
| 950 | + private static function copy_recursive($source, $destination, View $view): int | float { |
|
| 951 | 951 | $size = 0; |
| 952 | 952 | if ($view->is_dir($source)) { |
| 953 | 953 | $view->mkdir($destination); |
| 954 | 954 | $view->touch($destination, $view->filemtime($source)); |
| 955 | 955 | foreach ($view->getDirectoryContent($source) as $i) { |
| 956 | - $pathDir = $source . '/' . $i['name']; |
|
| 956 | + $pathDir = $source.'/'.$i['name']; |
|
| 957 | 957 | if ($view->is_dir($pathDir)) { |
| 958 | - $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view); |
|
| 958 | + $size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view); |
|
| 959 | 959 | } else { |
| 960 | 960 | $size += $view->filesize($pathDir); |
| 961 | - $result = $view->copy($pathDir, $destination . '/' . $i['name']); |
|
| 961 | + $result = $view->copy($pathDir, $destination.'/'.$i['name']); |
|
| 962 | 962 | if (!$result) { |
| 963 | 963 | throw new CopyRecursiveException(); |
| 964 | 964 | } |
| 965 | - $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir)); |
|
| 965 | + $view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir)); |
|
| 966 | 966 | } |
| 967 | 967 | } |
| 968 | 968 | } else { |
@@ -983,17 +983,17 @@ discard block |
||
| 983 | 983 | * @param int $timestamp timestamp when the file was deleted |
| 984 | 984 | */ |
| 985 | 985 | private static function getVersionsFromTrash($filename, $timestamp, string $user): array { |
| 986 | - $view = new View('/' . $user . '/files_trashbin/versions'); |
|
| 986 | + $view = new View('/'.$user.'/files_trashbin/versions'); |
|
| 987 | 987 | $versions = []; |
| 988 | 988 | |
| 989 | 989 | /** @var \OC\Files\Storage\Storage $storage */ |
| 990 | - [$storage,] = $view->resolvePath('/'); |
|
| 990 | + [$storage, ] = $view->resolvePath('/'); |
|
| 991 | 991 | |
| 992 | 992 | $pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename)); |
| 993 | 993 | if ($timestamp) { |
| 994 | 994 | // fetch for old versions |
| 995 | - $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp); |
|
| 996 | - $pattern .= '.v%.d' . $escapedTimestamp; |
|
| 995 | + $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string) $timestamp); |
|
| 996 | + $pattern .= '.v%.d'.$escapedTimestamp; |
|
| 997 | 997 | $offset = -strlen($escapedTimestamp) - 2; |
| 998 | 998 | } else { |
| 999 | 999 | $pattern .= '.v%'; |
@@ -1005,7 +1005,7 @@ discard block |
||
| 1005 | 1005 | Server::get(IDBConnection::class)->getQueryBuilder(), |
| 1006 | 1006 | Server::get(IFilesMetadataManager::class), |
| 1007 | 1007 | ); |
| 1008 | - $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/'); |
|
| 1008 | + $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/'); |
|
| 1009 | 1009 | $parentId = $cache->getId($normalizedParentPath); |
| 1010 | 1010 | if ($parentId === -1) { |
| 1011 | 1011 | return []; |
@@ -1021,7 +1021,7 @@ discard block |
||
| 1021 | 1021 | $result->closeCursor(); |
| 1022 | 1022 | |
| 1023 | 1023 | /** @var CacheEntry[] $matches */ |
| 1024 | - $matches = array_map(function (array $data) { |
|
| 1024 | + $matches = array_map(function(array $data) { |
|
| 1025 | 1025 | return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class)); |
| 1026 | 1026 | }, $entries); |
| 1027 | 1027 | |
@@ -1051,18 +1051,18 @@ discard block |
||
| 1051 | 1051 | $name = pathinfo($filename, PATHINFO_FILENAME); |
| 1052 | 1052 | $l = Util::getL10N('files_trashbin'); |
| 1053 | 1053 | |
| 1054 | - $location = '/' . trim($location, '/'); |
|
| 1054 | + $location = '/'.trim($location, '/'); |
|
| 1055 | 1055 | |
| 1056 | 1056 | // if extension is not empty we set a dot in front of it |
| 1057 | 1057 | if ($ext !== '') { |
| 1058 | - $ext = '.' . $ext; |
|
| 1058 | + $ext = '.'.$ext; |
|
| 1059 | 1059 | } |
| 1060 | 1060 | |
| 1061 | - if ($view->file_exists('files' . $location . '/' . $filename)) { |
|
| 1061 | + if ($view->file_exists('files'.$location.'/'.$filename)) { |
|
| 1062 | 1062 | $i = 2; |
| 1063 | - $uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext; |
|
| 1064 | - while ($view->file_exists('files' . $location . '/' . $uniqueName)) { |
|
| 1065 | - $uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext; |
|
| 1063 | + $uniqueName = $name.' ('.$l->t('restored').')'.$ext; |
|
| 1064 | + while ($view->file_exists('files'.$location.'/'.$uniqueName)) { |
|
| 1065 | + $uniqueName = $name.' ('.$l->t('restored').' '.$i.')'.$ext; |
|
| 1066 | 1066 | $i++; |
| 1067 | 1067 | } |
| 1068 | 1068 | |
@@ -1078,8 +1078,8 @@ discard block |
||
| 1078 | 1078 | * @param View $view file view on the root folder |
| 1079 | 1079 | * @return int|float size of the folder |
| 1080 | 1080 | */ |
| 1081 | - private static function calculateSize(View $view): int|float { |
|
| 1082 | - $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath(''); |
|
| 1081 | + private static function calculateSize(View $view): int | float { |
|
| 1082 | + $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath(''); |
|
| 1083 | 1083 | if (!file_exists($root)) { |
| 1084 | 1084 | return 0; |
| 1085 | 1085 | } |
@@ -1109,8 +1109,8 @@ discard block |
||
| 1109 | 1109 | * @param string $user user who owns the trash bin |
| 1110 | 1110 | * @return int|float trash bin size |
| 1111 | 1111 | */ |
| 1112 | - private static function getTrashbinSize(string $user): int|float { |
|
| 1113 | - $view = new View('/' . $user); |
|
| 1112 | + private static function getTrashbinSize(string $user): int | float { |
|
| 1113 | + $view = new View('/'.$user); |
|
| 1114 | 1114 | $fileInfo = $view->getFileInfo('/files_trashbin'); |
| 1115 | 1115 | return isset($fileInfo['size']) ? $fileInfo['size'] : 0; |
| 1116 | 1116 | } |
@@ -1122,7 +1122,7 @@ discard block |
||
| 1122 | 1122 | * @return bool |
| 1123 | 1123 | */ |
| 1124 | 1124 | public static function isEmpty($user) { |
| 1125 | - $view = new View('/' . $user . '/files_trashbin'); |
|
| 1125 | + $view = new View('/'.$user.'/files_trashbin'); |
|
| 1126 | 1126 | if ($view->is_dir('/files') && $dh = $view->opendir('/files')) { |
| 1127 | 1127 | while (($file = readdir($dh)) !== false) { |
| 1128 | 1128 | if (!Filesystem::isIgnoredDir($file)) { |
@@ -1145,7 +1145,7 @@ discard block |
||
| 1145 | 1145 | * Return the filename used in the trash bin |
| 1146 | 1146 | */ |
| 1147 | 1147 | public static function getTrashFilename(string $filename, int $timestamp): string { |
| 1148 | - $trashFilename = $filename . '.d' . $timestamp; |
|
| 1148 | + $trashFilename = $filename.'.d'.$timestamp; |
|
| 1149 | 1149 | $length = strlen($trashFilename); |
| 1150 | 1150 | // oc_filecache `name` column has a limit of 250 chars |
| 1151 | 1151 | $maxLength = 250; |
@@ -1173,7 +1173,7 @@ discard block |
||
| 1173 | 1173 | } |
| 1174 | 1174 | |
| 1175 | 1175 | $view = Server::get(View::class); |
| 1176 | - $fullPath = '/' . $user . '/' . $baseDir . '/' . $path; |
|
| 1176 | + $fullPath = '/'.$user.'/'.$baseDir.'/'.$path; |
|
| 1177 | 1177 | |
| 1178 | 1178 | if (Filesystem::is_dir($path)) { |
| 1179 | 1179 | return new NonExistingFolder($rootFolder, $view, $fullPath); |
@@ -23,183 +23,183 @@ |
||
| 23 | 23 | |
| 24 | 24 | class RestoreBackgroundImageColor extends QueuedJob { |
| 25 | 25 | |
| 26 | - public const STAGE_PREPARE = 'prepare'; |
|
| 27 | - public const STAGE_EXECUTE = 'execute'; |
|
| 28 | - // will be saved in appdata/theming/global/ |
|
| 29 | - protected const STATE_FILE_NAME = '30_background_image_color_restoration.json'; |
|
| 30 | - |
|
| 31 | - public function __construct( |
|
| 32 | - ITimeFactory $time, |
|
| 33 | - private IConfig $config, |
|
| 34 | - private IAppData $appData, |
|
| 35 | - private IJobList $jobList, |
|
| 36 | - private IDBConnection $dbc, |
|
| 37 | - private LoggerInterface $logger, |
|
| 38 | - private BackgroundService $service, |
|
| 39 | - ) { |
|
| 40 | - parent::__construct($time); |
|
| 41 | - } |
|
| 42 | - |
|
| 43 | - protected function run(mixed $argument): void { |
|
| 44 | - if (!is_array($argument) || !isset($argument['stage'])) { |
|
| 45 | - throw new \Exception('Job ' . self::class . ' called with wrong argument'); |
|
| 46 | - } |
|
| 47 | - |
|
| 48 | - switch ($argument['stage']) { |
|
| 49 | - case self::STAGE_PREPARE: |
|
| 50 | - $this->runPreparation(); |
|
| 51 | - break; |
|
| 52 | - case self::STAGE_EXECUTE: |
|
| 53 | - $this->runMigration(); |
|
| 54 | - break; |
|
| 55 | - default: |
|
| 56 | - break; |
|
| 57 | - } |
|
| 58 | - } |
|
| 59 | - |
|
| 60 | - protected function runPreparation(): void { |
|
| 61 | - try { |
|
| 62 | - $qb = $this->dbc->getQueryBuilder(); |
|
| 63 | - $qb2 = $this->dbc->getQueryBuilder(); |
|
| 64 | - |
|
| 65 | - $innerSQL = $qb2->select('userid') |
|
| 66 | - ->from('preferences') |
|
| 67 | - ->where($qb2->expr()->eq('configkey', $qb->createNamedParameter('background_color'))); |
|
| 68 | - |
|
| 69 | - // Get those users, that have a background_image set - not the default, but no background_color. |
|
| 70 | - $result = $qb->selectDistinct('a.userid') |
|
| 71 | - ->from('preferences', 'a') |
|
| 72 | - ->leftJoin('a', $qb->createFunction('(' . $innerSQL->getSQL() . ')'), 'b', 'a.userid = b.userid') |
|
| 73 | - ->where($qb2->expr()->eq('a.configkey', $qb->createNamedParameter('background_image'))) |
|
| 74 | - ->andWhere($qb2->expr()->neq('a.configvalue', $qb->createNamedParameter(BackgroundService::BACKGROUND_DEFAULT))) |
|
| 75 | - ->andWhere($qb2->expr()->isNull('b.userid')) |
|
| 76 | - ->executeQuery(); |
|
| 77 | - |
|
| 78 | - $userIds = $result->fetchFirstColumn(); |
|
| 79 | - $this->logger->info('Prepare to restore background information for {users} users', ['users' => count($userIds)]); |
|
| 80 | - $this->storeUserIdsToProcess($userIds); |
|
| 81 | - } catch (\Throwable $t) { |
|
| 82 | - $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); |
|
| 83 | - throw $t; |
|
| 84 | - } |
|
| 85 | - $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - /** |
|
| 89 | - * @throws NotPermittedException |
|
| 90 | - * @throws NotFoundException |
|
| 91 | - */ |
|
| 92 | - protected function runMigration(): void { |
|
| 93 | - $allUserIds = $this->readUserIdsToProcess(); |
|
| 94 | - $notSoFastMode = count($allUserIds) > 1000; |
|
| 95 | - |
|
| 96 | - $userIds = array_slice($allUserIds, 0, 1000); |
|
| 97 | - foreach ($userIds as $userId) { |
|
| 98 | - $backgroundColor = $this->config->getUserValue($userId, Application::APP_ID, 'background_color'); |
|
| 99 | - if ($backgroundColor !== '') { |
|
| 100 | - continue; |
|
| 101 | - } |
|
| 102 | - |
|
| 103 | - $background = $this->config->getUserValue($userId, Application::APP_ID, 'background_image'); |
|
| 104 | - switch ($background) { |
|
| 105 | - case BackgroundService::BACKGROUND_DEFAULT: |
|
| 106 | - $this->service->setDefaultBackground($userId); |
|
| 107 | - break; |
|
| 108 | - case BackgroundService::BACKGROUND_COLOR: |
|
| 109 | - break; |
|
| 110 | - case BackgroundService::BACKGROUND_CUSTOM: |
|
| 111 | - $this->service->recalculateMeanColor($userId); |
|
| 112 | - break; |
|
| 113 | - default: |
|
| 114 | - // shipped backgrounds |
|
| 115 | - // do not alter primary color |
|
| 116 | - $primary = $this->config->getUserValue($userId, Application::APP_ID, 'primary_color'); |
|
| 117 | - if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$background])) { |
|
| 118 | - $this->service->setShippedBackground($background, $userId); |
|
| 119 | - } else { |
|
| 120 | - $this->service->setDefaultBackground($userId); |
|
| 121 | - } |
|
| 122 | - // Restore primary |
|
| 123 | - if ($primary !== '') { |
|
| 124 | - $this->config->setUserValue($userId, Application::APP_ID, 'primary_color', $primary); |
|
| 125 | - } |
|
| 126 | - } |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - if ($notSoFastMode) { |
|
| 130 | - $remainingUserIds = array_slice($allUserIds, 1000); |
|
| 131 | - $this->storeUserIdsToProcess($remainingUserIds); |
|
| 132 | - $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 133 | - } else { |
|
| 134 | - $this->deleteStateFile(); |
|
| 135 | - } |
|
| 136 | - } |
|
| 137 | - |
|
| 138 | - /** |
|
| 139 | - * @throws NotPermittedException |
|
| 140 | - * @throws NotFoundException |
|
| 141 | - */ |
|
| 142 | - protected function readUserIdsToProcess(): array { |
|
| 143 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 144 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 145 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 146 | - try { |
|
| 147 | - $userIds = \json_decode($file->getContent(), true); |
|
| 148 | - } catch (NotFoundException $e) { |
|
| 149 | - $userIds = []; |
|
| 150 | - } |
|
| 151 | - if ($userIds === null) { |
|
| 152 | - $userIds = []; |
|
| 153 | - } |
|
| 154 | - } else { |
|
| 155 | - $userIds = []; |
|
| 156 | - } |
|
| 157 | - return $userIds; |
|
| 158 | - } |
|
| 159 | - |
|
| 160 | - /** |
|
| 161 | - * @throws NotFoundException |
|
| 162 | - */ |
|
| 163 | - protected function storeUserIdsToProcess(array $userIds): void { |
|
| 164 | - $storableUserIds = \json_encode($userIds); |
|
| 165 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 166 | - try { |
|
| 167 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 168 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 169 | - } else { |
|
| 170 | - $file = $globalFolder->newFile(self::STATE_FILE_NAME); |
|
| 171 | - } |
|
| 172 | - $file->putContent($storableUserIds); |
|
| 173 | - } catch (NotFoundException $e) { |
|
| 174 | - } catch (NotPermittedException $e) { |
|
| 175 | - $this->logger->warning('Lacking permissions to create {file}', |
|
| 176 | - [ |
|
| 177 | - 'app' => 'theming', |
|
| 178 | - 'file' => self::STATE_FILE_NAME, |
|
| 179 | - 'exception' => $e, |
|
| 180 | - ] |
|
| 181 | - ); |
|
| 182 | - } |
|
| 183 | - } |
|
| 184 | - |
|
| 185 | - /** |
|
| 186 | - * @throws NotFoundException |
|
| 187 | - */ |
|
| 188 | - protected function deleteStateFile(): void { |
|
| 189 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 190 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 191 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 192 | - try { |
|
| 193 | - $file->delete(); |
|
| 194 | - } catch (NotPermittedException $e) { |
|
| 195 | - $this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.', |
|
| 196 | - [ |
|
| 197 | - 'app' => 'theming', |
|
| 198 | - 'file' => $file->getName(), |
|
| 199 | - 'exception' => $e, |
|
| 200 | - ] |
|
| 201 | - ); |
|
| 202 | - } |
|
| 203 | - } |
|
| 204 | - } |
|
| 26 | + public const STAGE_PREPARE = 'prepare'; |
|
| 27 | + public const STAGE_EXECUTE = 'execute'; |
|
| 28 | + // will be saved in appdata/theming/global/ |
|
| 29 | + protected const STATE_FILE_NAME = '30_background_image_color_restoration.json'; |
|
| 30 | + |
|
| 31 | + public function __construct( |
|
| 32 | + ITimeFactory $time, |
|
| 33 | + private IConfig $config, |
|
| 34 | + private IAppData $appData, |
|
| 35 | + private IJobList $jobList, |
|
| 36 | + private IDBConnection $dbc, |
|
| 37 | + private LoggerInterface $logger, |
|
| 38 | + private BackgroundService $service, |
|
| 39 | + ) { |
|
| 40 | + parent::__construct($time); |
|
| 41 | + } |
|
| 42 | + |
|
| 43 | + protected function run(mixed $argument): void { |
|
| 44 | + if (!is_array($argument) || !isset($argument['stage'])) { |
|
| 45 | + throw new \Exception('Job ' . self::class . ' called with wrong argument'); |
|
| 46 | + } |
|
| 47 | + |
|
| 48 | + switch ($argument['stage']) { |
|
| 49 | + case self::STAGE_PREPARE: |
|
| 50 | + $this->runPreparation(); |
|
| 51 | + break; |
|
| 52 | + case self::STAGE_EXECUTE: |
|
| 53 | + $this->runMigration(); |
|
| 54 | + break; |
|
| 55 | + default: |
|
| 56 | + break; |
|
| 57 | + } |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + protected function runPreparation(): void { |
|
| 61 | + try { |
|
| 62 | + $qb = $this->dbc->getQueryBuilder(); |
|
| 63 | + $qb2 = $this->dbc->getQueryBuilder(); |
|
| 64 | + |
|
| 65 | + $innerSQL = $qb2->select('userid') |
|
| 66 | + ->from('preferences') |
|
| 67 | + ->where($qb2->expr()->eq('configkey', $qb->createNamedParameter('background_color'))); |
|
| 68 | + |
|
| 69 | + // Get those users, that have a background_image set - not the default, but no background_color. |
|
| 70 | + $result = $qb->selectDistinct('a.userid') |
|
| 71 | + ->from('preferences', 'a') |
|
| 72 | + ->leftJoin('a', $qb->createFunction('(' . $innerSQL->getSQL() . ')'), 'b', 'a.userid = b.userid') |
|
| 73 | + ->where($qb2->expr()->eq('a.configkey', $qb->createNamedParameter('background_image'))) |
|
| 74 | + ->andWhere($qb2->expr()->neq('a.configvalue', $qb->createNamedParameter(BackgroundService::BACKGROUND_DEFAULT))) |
|
| 75 | + ->andWhere($qb2->expr()->isNull('b.userid')) |
|
| 76 | + ->executeQuery(); |
|
| 77 | + |
|
| 78 | + $userIds = $result->fetchFirstColumn(); |
|
| 79 | + $this->logger->info('Prepare to restore background information for {users} users', ['users' => count($userIds)]); |
|
| 80 | + $this->storeUserIdsToProcess($userIds); |
|
| 81 | + } catch (\Throwable $t) { |
|
| 82 | + $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); |
|
| 83 | + throw $t; |
|
| 84 | + } |
|
| 85 | + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + /** |
|
| 89 | + * @throws NotPermittedException |
|
| 90 | + * @throws NotFoundException |
|
| 91 | + */ |
|
| 92 | + protected function runMigration(): void { |
|
| 93 | + $allUserIds = $this->readUserIdsToProcess(); |
|
| 94 | + $notSoFastMode = count($allUserIds) > 1000; |
|
| 95 | + |
|
| 96 | + $userIds = array_slice($allUserIds, 0, 1000); |
|
| 97 | + foreach ($userIds as $userId) { |
|
| 98 | + $backgroundColor = $this->config->getUserValue($userId, Application::APP_ID, 'background_color'); |
|
| 99 | + if ($backgroundColor !== '') { |
|
| 100 | + continue; |
|
| 101 | + } |
|
| 102 | + |
|
| 103 | + $background = $this->config->getUserValue($userId, Application::APP_ID, 'background_image'); |
|
| 104 | + switch ($background) { |
|
| 105 | + case BackgroundService::BACKGROUND_DEFAULT: |
|
| 106 | + $this->service->setDefaultBackground($userId); |
|
| 107 | + break; |
|
| 108 | + case BackgroundService::BACKGROUND_COLOR: |
|
| 109 | + break; |
|
| 110 | + case BackgroundService::BACKGROUND_CUSTOM: |
|
| 111 | + $this->service->recalculateMeanColor($userId); |
|
| 112 | + break; |
|
| 113 | + default: |
|
| 114 | + // shipped backgrounds |
|
| 115 | + // do not alter primary color |
|
| 116 | + $primary = $this->config->getUserValue($userId, Application::APP_ID, 'primary_color'); |
|
| 117 | + if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$background])) { |
|
| 118 | + $this->service->setShippedBackground($background, $userId); |
|
| 119 | + } else { |
|
| 120 | + $this->service->setDefaultBackground($userId); |
|
| 121 | + } |
|
| 122 | + // Restore primary |
|
| 123 | + if ($primary !== '') { |
|
| 124 | + $this->config->setUserValue($userId, Application::APP_ID, 'primary_color', $primary); |
|
| 125 | + } |
|
| 126 | + } |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + if ($notSoFastMode) { |
|
| 130 | + $remainingUserIds = array_slice($allUserIds, 1000); |
|
| 131 | + $this->storeUserIdsToProcess($remainingUserIds); |
|
| 132 | + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 133 | + } else { |
|
| 134 | + $this->deleteStateFile(); |
|
| 135 | + } |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + /** |
|
| 139 | + * @throws NotPermittedException |
|
| 140 | + * @throws NotFoundException |
|
| 141 | + */ |
|
| 142 | + protected function readUserIdsToProcess(): array { |
|
| 143 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 144 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 145 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 146 | + try { |
|
| 147 | + $userIds = \json_decode($file->getContent(), true); |
|
| 148 | + } catch (NotFoundException $e) { |
|
| 149 | + $userIds = []; |
|
| 150 | + } |
|
| 151 | + if ($userIds === null) { |
|
| 152 | + $userIds = []; |
|
| 153 | + } |
|
| 154 | + } else { |
|
| 155 | + $userIds = []; |
|
| 156 | + } |
|
| 157 | + return $userIds; |
|
| 158 | + } |
|
| 159 | + |
|
| 160 | + /** |
|
| 161 | + * @throws NotFoundException |
|
| 162 | + */ |
|
| 163 | + protected function storeUserIdsToProcess(array $userIds): void { |
|
| 164 | + $storableUserIds = \json_encode($userIds); |
|
| 165 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 166 | + try { |
|
| 167 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 168 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 169 | + } else { |
|
| 170 | + $file = $globalFolder->newFile(self::STATE_FILE_NAME); |
|
| 171 | + } |
|
| 172 | + $file->putContent($storableUserIds); |
|
| 173 | + } catch (NotFoundException $e) { |
|
| 174 | + } catch (NotPermittedException $e) { |
|
| 175 | + $this->logger->warning('Lacking permissions to create {file}', |
|
| 176 | + [ |
|
| 177 | + 'app' => 'theming', |
|
| 178 | + 'file' => self::STATE_FILE_NAME, |
|
| 179 | + 'exception' => $e, |
|
| 180 | + ] |
|
| 181 | + ); |
|
| 182 | + } |
|
| 183 | + } |
|
| 184 | + |
|
| 185 | + /** |
|
| 186 | + * @throws NotFoundException |
|
| 187 | + */ |
|
| 188 | + protected function deleteStateFile(): void { |
|
| 189 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 190 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 191 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 192 | + try { |
|
| 193 | + $file->delete(); |
|
| 194 | + } catch (NotPermittedException $e) { |
|
| 195 | + $this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.', |
|
| 196 | + [ |
|
| 197 | + 'app' => 'theming', |
|
| 198 | + 'file' => $file->getName(), |
|
| 199 | + 'exception' => $e, |
|
| 200 | + ] |
|
| 201 | + ); |
|
| 202 | + } |
|
| 203 | + } |
|
| 204 | + } |
|
| 205 | 205 | } |
@@ -23,177 +23,177 @@ |
||
| 23 | 23 | use Psr\Log\LoggerInterface; |
| 24 | 24 | |
| 25 | 25 | class MigrateBackgroundImages extends QueuedJob { |
| 26 | - public const TIME_SENSITIVE = 0; |
|
| 27 | - |
|
| 28 | - public const STAGE_PREPARE = 'prepare'; |
|
| 29 | - public const STAGE_EXECUTE = 'execute'; |
|
| 30 | - // will be saved in appdata/theming/global/ |
|
| 31 | - protected const STATE_FILE_NAME = '25_dashboard_to_theming_migration_users.json'; |
|
| 32 | - |
|
| 33 | - public function __construct( |
|
| 34 | - ITimeFactory $time, |
|
| 35 | - private IAppDataFactory $appDataFactory, |
|
| 36 | - private IJobList $jobList, |
|
| 37 | - private IDBConnection $dbc, |
|
| 38 | - private IAppData $appData, |
|
| 39 | - private LoggerInterface $logger, |
|
| 40 | - ) { |
|
| 41 | - parent::__construct($time); |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - protected function run(mixed $argument): void { |
|
| 45 | - if (!is_array($argument) || !isset($argument['stage'])) { |
|
| 46 | - throw new \Exception('Job ' . self::class . ' called with wrong argument'); |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - switch ($argument['stage']) { |
|
| 50 | - case self::STAGE_PREPARE: |
|
| 51 | - $this->runPreparation(); |
|
| 52 | - break; |
|
| 53 | - case self::STAGE_EXECUTE: |
|
| 54 | - $this->runMigration(); |
|
| 55 | - break; |
|
| 56 | - default: |
|
| 57 | - break; |
|
| 58 | - } |
|
| 59 | - } |
|
| 60 | - |
|
| 61 | - protected function runPreparation(): void { |
|
| 62 | - try { |
|
| 63 | - $selector = $this->dbc->getQueryBuilder(); |
|
| 64 | - $result = $selector->select('userid') |
|
| 65 | - ->from('preferences') |
|
| 66 | - ->where($selector->expr()->eq('appid', $selector->createNamedParameter('theming'))) |
|
| 67 | - ->andWhere($selector->expr()->eq('configkey', $selector->createNamedParameter('background'))) |
|
| 68 | - ->andWhere($selector->expr()->eq('configvalue', $selector->createNamedParameter('custom', IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR)) |
|
| 69 | - ->executeQuery(); |
|
| 70 | - |
|
| 71 | - $userIds = $result->fetchFirstColumn(); |
|
| 72 | - $this->storeUserIdsToProcess($userIds); |
|
| 73 | - } catch (\Throwable $t) { |
|
| 74 | - $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); |
|
| 75 | - throw $t; |
|
| 76 | - } |
|
| 77 | - $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - /** |
|
| 81 | - * @throws NotPermittedException |
|
| 82 | - * @throws NotFoundException |
|
| 83 | - */ |
|
| 84 | - protected function runMigration(): void { |
|
| 85 | - $allUserIds = $this->readUserIdsToProcess(); |
|
| 86 | - $notSoFastMode = count($allUserIds) > 5000; |
|
| 87 | - $dashboardData = $this->appDataFactory->get('dashboard'); |
|
| 88 | - |
|
| 89 | - $userIds = $notSoFastMode ? array_slice($allUserIds, 0, 5000) : $allUserIds; |
|
| 90 | - foreach ($userIds as $userId) { |
|
| 91 | - try { |
|
| 92 | - // migration |
|
| 93 | - $file = $dashboardData->getFolder($userId)->getFile('background.jpg'); |
|
| 94 | - $targetDir = $this->getUserFolder($userId); |
|
| 95 | - |
|
| 96 | - if (!$targetDir->fileExists('background.jpg')) { |
|
| 97 | - $targetDir->newFile('background.jpg', $file->getContent()); |
|
| 98 | - } |
|
| 99 | - $file->delete(); |
|
| 100 | - } catch (NotFoundException|NotPermittedException $e) { |
|
| 101 | - } |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - if ($notSoFastMode) { |
|
| 105 | - $remainingUserIds = array_slice($allUserIds, 5000); |
|
| 106 | - $this->storeUserIdsToProcess($remainingUserIds); |
|
| 107 | - $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 108 | - } else { |
|
| 109 | - $this->deleteStateFile(); |
|
| 110 | - } |
|
| 111 | - } |
|
| 112 | - |
|
| 113 | - /** |
|
| 114 | - * @throws NotPermittedException |
|
| 115 | - * @throws NotFoundException |
|
| 116 | - */ |
|
| 117 | - protected function readUserIdsToProcess(): array { |
|
| 118 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 119 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 120 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 121 | - try { |
|
| 122 | - $userIds = \json_decode($file->getContent(), true); |
|
| 123 | - } catch (NotFoundException $e) { |
|
| 124 | - $userIds = []; |
|
| 125 | - } |
|
| 126 | - if ($userIds === null) { |
|
| 127 | - $userIds = []; |
|
| 128 | - } |
|
| 129 | - } else { |
|
| 130 | - $userIds = []; |
|
| 131 | - } |
|
| 132 | - return $userIds; |
|
| 133 | - } |
|
| 134 | - |
|
| 135 | - /** |
|
| 136 | - * @throws NotFoundException |
|
| 137 | - */ |
|
| 138 | - protected function storeUserIdsToProcess(array $userIds): void { |
|
| 139 | - $storableUserIds = \json_encode($userIds); |
|
| 140 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 141 | - try { |
|
| 142 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 143 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 144 | - } else { |
|
| 145 | - $file = $globalFolder->newFile(self::STATE_FILE_NAME); |
|
| 146 | - } |
|
| 147 | - $file->putContent($storableUserIds); |
|
| 148 | - } catch (NotFoundException $e) { |
|
| 149 | - } catch (NotPermittedException $e) { |
|
| 150 | - $this->logger->warning('Lacking permissions to create {file}', |
|
| 151 | - [ |
|
| 152 | - 'app' => 'theming', |
|
| 153 | - 'file' => self::STATE_FILE_NAME, |
|
| 154 | - 'exception' => $e, |
|
| 155 | - ] |
|
| 156 | - ); |
|
| 157 | - } |
|
| 158 | - } |
|
| 159 | - |
|
| 160 | - /** |
|
| 161 | - * @throws NotFoundException |
|
| 162 | - */ |
|
| 163 | - protected function deleteStateFile(): void { |
|
| 164 | - $globalFolder = $this->appData->getFolder('global'); |
|
| 165 | - if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 166 | - $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 167 | - try { |
|
| 168 | - $file->delete(); |
|
| 169 | - } catch (NotPermittedException $e) { |
|
| 170 | - $this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.', |
|
| 171 | - [ |
|
| 172 | - 'app' => 'theming', |
|
| 173 | - 'file' => $file->getName(), |
|
| 174 | - 'exception' => $e, |
|
| 175 | - ] |
|
| 176 | - ); |
|
| 177 | - } |
|
| 178 | - } |
|
| 179 | - } |
|
| 180 | - |
|
| 181 | - /** |
|
| 182 | - * Get the root location for users theming data |
|
| 183 | - */ |
|
| 184 | - protected function getUserFolder(string $userId): ISimpleFolder { |
|
| 185 | - $themingData = $this->appDataFactory->get(Application::APP_ID); |
|
| 186 | - |
|
| 187 | - try { |
|
| 188 | - $rootFolder = $themingData->getFolder('users'); |
|
| 189 | - } catch (NotFoundException $e) { |
|
| 190 | - $rootFolder = $themingData->newFolder('users'); |
|
| 191 | - } |
|
| 192 | - |
|
| 193 | - try { |
|
| 194 | - return $rootFolder->getFolder($userId); |
|
| 195 | - } catch (NotFoundException $e) { |
|
| 196 | - return $rootFolder->newFolder($userId); |
|
| 197 | - } |
|
| 198 | - } |
|
| 26 | + public const TIME_SENSITIVE = 0; |
|
| 27 | + |
|
| 28 | + public const STAGE_PREPARE = 'prepare'; |
|
| 29 | + public const STAGE_EXECUTE = 'execute'; |
|
| 30 | + // will be saved in appdata/theming/global/ |
|
| 31 | + protected const STATE_FILE_NAME = '25_dashboard_to_theming_migration_users.json'; |
|
| 32 | + |
|
| 33 | + public function __construct( |
|
| 34 | + ITimeFactory $time, |
|
| 35 | + private IAppDataFactory $appDataFactory, |
|
| 36 | + private IJobList $jobList, |
|
| 37 | + private IDBConnection $dbc, |
|
| 38 | + private IAppData $appData, |
|
| 39 | + private LoggerInterface $logger, |
|
| 40 | + ) { |
|
| 41 | + parent::__construct($time); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + protected function run(mixed $argument): void { |
|
| 45 | + if (!is_array($argument) || !isset($argument['stage'])) { |
|
| 46 | + throw new \Exception('Job ' . self::class . ' called with wrong argument'); |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + switch ($argument['stage']) { |
|
| 50 | + case self::STAGE_PREPARE: |
|
| 51 | + $this->runPreparation(); |
|
| 52 | + break; |
|
| 53 | + case self::STAGE_EXECUTE: |
|
| 54 | + $this->runMigration(); |
|
| 55 | + break; |
|
| 56 | + default: |
|
| 57 | + break; |
|
| 58 | + } |
|
| 59 | + } |
|
| 60 | + |
|
| 61 | + protected function runPreparation(): void { |
|
| 62 | + try { |
|
| 63 | + $selector = $this->dbc->getQueryBuilder(); |
|
| 64 | + $result = $selector->select('userid') |
|
| 65 | + ->from('preferences') |
|
| 66 | + ->where($selector->expr()->eq('appid', $selector->createNamedParameter('theming'))) |
|
| 67 | + ->andWhere($selector->expr()->eq('configkey', $selector->createNamedParameter('background'))) |
|
| 68 | + ->andWhere($selector->expr()->eq('configvalue', $selector->createNamedParameter('custom', IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR)) |
|
| 69 | + ->executeQuery(); |
|
| 70 | + |
|
| 71 | + $userIds = $result->fetchFirstColumn(); |
|
| 72 | + $this->storeUserIdsToProcess($userIds); |
|
| 73 | + } catch (\Throwable $t) { |
|
| 74 | + $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); |
|
| 75 | + throw $t; |
|
| 76 | + } |
|
| 77 | + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + /** |
|
| 81 | + * @throws NotPermittedException |
|
| 82 | + * @throws NotFoundException |
|
| 83 | + */ |
|
| 84 | + protected function runMigration(): void { |
|
| 85 | + $allUserIds = $this->readUserIdsToProcess(); |
|
| 86 | + $notSoFastMode = count($allUserIds) > 5000; |
|
| 87 | + $dashboardData = $this->appDataFactory->get('dashboard'); |
|
| 88 | + |
|
| 89 | + $userIds = $notSoFastMode ? array_slice($allUserIds, 0, 5000) : $allUserIds; |
|
| 90 | + foreach ($userIds as $userId) { |
|
| 91 | + try { |
|
| 92 | + // migration |
|
| 93 | + $file = $dashboardData->getFolder($userId)->getFile('background.jpg'); |
|
| 94 | + $targetDir = $this->getUserFolder($userId); |
|
| 95 | + |
|
| 96 | + if (!$targetDir->fileExists('background.jpg')) { |
|
| 97 | + $targetDir->newFile('background.jpg', $file->getContent()); |
|
| 98 | + } |
|
| 99 | + $file->delete(); |
|
| 100 | + } catch (NotFoundException|NotPermittedException $e) { |
|
| 101 | + } |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + if ($notSoFastMode) { |
|
| 105 | + $remainingUserIds = array_slice($allUserIds, 5000); |
|
| 106 | + $this->storeUserIdsToProcess($remainingUserIds); |
|
| 107 | + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); |
|
| 108 | + } else { |
|
| 109 | + $this->deleteStateFile(); |
|
| 110 | + } |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + /** |
|
| 114 | + * @throws NotPermittedException |
|
| 115 | + * @throws NotFoundException |
|
| 116 | + */ |
|
| 117 | + protected function readUserIdsToProcess(): array { |
|
| 118 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 119 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 120 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 121 | + try { |
|
| 122 | + $userIds = \json_decode($file->getContent(), true); |
|
| 123 | + } catch (NotFoundException $e) { |
|
| 124 | + $userIds = []; |
|
| 125 | + } |
|
| 126 | + if ($userIds === null) { |
|
| 127 | + $userIds = []; |
|
| 128 | + } |
|
| 129 | + } else { |
|
| 130 | + $userIds = []; |
|
| 131 | + } |
|
| 132 | + return $userIds; |
|
| 133 | + } |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * @throws NotFoundException |
|
| 137 | + */ |
|
| 138 | + protected function storeUserIdsToProcess(array $userIds): void { |
|
| 139 | + $storableUserIds = \json_encode($userIds); |
|
| 140 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 141 | + try { |
|
| 142 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 143 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 144 | + } else { |
|
| 145 | + $file = $globalFolder->newFile(self::STATE_FILE_NAME); |
|
| 146 | + } |
|
| 147 | + $file->putContent($storableUserIds); |
|
| 148 | + } catch (NotFoundException $e) { |
|
| 149 | + } catch (NotPermittedException $e) { |
|
| 150 | + $this->logger->warning('Lacking permissions to create {file}', |
|
| 151 | + [ |
|
| 152 | + 'app' => 'theming', |
|
| 153 | + 'file' => self::STATE_FILE_NAME, |
|
| 154 | + 'exception' => $e, |
|
| 155 | + ] |
|
| 156 | + ); |
|
| 157 | + } |
|
| 158 | + } |
|
| 159 | + |
|
| 160 | + /** |
|
| 161 | + * @throws NotFoundException |
|
| 162 | + */ |
|
| 163 | + protected function deleteStateFile(): void { |
|
| 164 | + $globalFolder = $this->appData->getFolder('global'); |
|
| 165 | + if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { |
|
| 166 | + $file = $globalFolder->getFile(self::STATE_FILE_NAME); |
|
| 167 | + try { |
|
| 168 | + $file->delete(); |
|
| 169 | + } catch (NotPermittedException $e) { |
|
| 170 | + $this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.', |
|
| 171 | + [ |
|
| 172 | + 'app' => 'theming', |
|
| 173 | + 'file' => $file->getName(), |
|
| 174 | + 'exception' => $e, |
|
| 175 | + ] |
|
| 176 | + ); |
|
| 177 | + } |
|
| 178 | + } |
|
| 179 | + } |
|
| 180 | + |
|
| 181 | + /** |
|
| 182 | + * Get the root location for users theming data |
|
| 183 | + */ |
|
| 184 | + protected function getUserFolder(string $userId): ISimpleFolder { |
|
| 185 | + $themingData = $this->appDataFactory->get(Application::APP_ID); |
|
| 186 | + |
|
| 187 | + try { |
|
| 188 | + $rootFolder = $themingData->getFolder('users'); |
|
| 189 | + } catch (NotFoundException $e) { |
|
| 190 | + $rootFolder = $themingData->newFolder('users'); |
|
| 191 | + } |
|
| 192 | + |
|
| 193 | + try { |
|
| 194 | + return $rootFolder->getFolder($userId); |
|
| 195 | + } catch (NotFoundException $e) { |
|
| 196 | + return $rootFolder->newFolder($userId); |
|
| 197 | + } |
|
| 198 | + } |
|
| 199 | 199 | } |
@@ -50,1861 +50,1861 @@ |
||
| 50 | 50 | */ |
| 51 | 51 | #[\PHPUnit\Framework\Attributes\Group('DB')] |
| 52 | 52 | class ShareByMailProviderTest extends TestCase { |
| 53 | - use EmailValidatorTrait; |
|
| 54 | - |
|
| 55 | - private IDBConnection $connection; |
|
| 56 | - |
|
| 57 | - private IL10N&MockObject $l; |
|
| 58 | - private IShare&MockObject $share; |
|
| 59 | - private IConfig&MockObject $config; |
|
| 60 | - private IMailer&MockObject $mailer; |
|
| 61 | - private IHasher&MockObject $hasher; |
|
| 62 | - private Defaults&MockObject $defaults; |
|
| 63 | - private IManager&MockObject $shareManager; |
|
| 64 | - private LoggerInterface&MockObject $logger; |
|
| 65 | - private IRootFolder&MockObject $rootFolder; |
|
| 66 | - private IUserManager&MockObject $userManager; |
|
| 67 | - private ISecureRandom&MockObject $secureRandom; |
|
| 68 | - private IURLGenerator&MockObject $urlGenerator; |
|
| 69 | - private SettingsManager&MockObject $settingsManager; |
|
| 70 | - private IActivityManager&MockObject $activityManager; |
|
| 71 | - private IEventDispatcher&MockObject $eventDispatcher; |
|
| 72 | - |
|
| 73 | - protected function setUp(): void { |
|
| 74 | - parent::setUp(); |
|
| 75 | - |
|
| 76 | - $this->connection = Server::get(IDBConnection::class); |
|
| 77 | - |
|
| 78 | - $this->l = $this->createMock(IL10N::class); |
|
| 79 | - $this->l->method('t') |
|
| 80 | - ->willReturnCallback(function ($text, $parameters = []) { |
|
| 81 | - return vsprintf($text, $parameters); |
|
| 82 | - }); |
|
| 83 | - $this->config = $this->createMock(IConfig::class); |
|
| 84 | - $this->logger = $this->createMock(LoggerInterface::class); |
|
| 85 | - $this->rootFolder = $this->createMock('OCP\Files\IRootFolder'); |
|
| 86 | - $this->userManager = $this->createMock(IUserManager::class); |
|
| 87 | - $this->secureRandom = $this->createMock('\OCP\Security\ISecureRandom'); |
|
| 88 | - $this->mailer = $this->createMock('\OCP\Mail\IMailer'); |
|
| 89 | - $this->urlGenerator = $this->createMock(IURLGenerator::class); |
|
| 90 | - $this->share = $this->createMock(IShare::class); |
|
| 91 | - $this->activityManager = $this->createMock('OCP\Activity\IManager'); |
|
| 92 | - $this->settingsManager = $this->createMock(SettingsManager::class); |
|
| 93 | - $this->defaults = $this->createMock(Defaults::class); |
|
| 94 | - $this->hasher = $this->createMock(IHasher::class); |
|
| 95 | - $this->eventDispatcher = $this->createMock(IEventDispatcher::class); |
|
| 96 | - $this->shareManager = $this->createMock(IManager::class); |
|
| 97 | - |
|
| 98 | - $this->userManager->expects($this->any())->method('userExists')->willReturn(true); |
|
| 99 | - $this->config->expects($this->any())->method('getAppValue')->with('core', 'enforce_strict_email_check')->willReturn('yes'); |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - /** |
|
| 103 | - * get instance of Mocked ShareByMailProvider |
|
| 104 | - * |
|
| 105 | - * @param array $mockedMethods internal methods which should be mocked |
|
| 106 | - * @return \PHPUnit\Framework\MockObject\MockObject | ShareByMailProvider |
|
| 107 | - */ |
|
| 108 | - private function getInstance(array $mockedMethods = []) { |
|
| 109 | - if (!empty($mockedMethods)) { |
|
| 110 | - return $this->getMockBuilder(ShareByMailProvider::class) |
|
| 111 | - ->setConstructorArgs([ |
|
| 112 | - $this->config, |
|
| 113 | - $this->connection, |
|
| 114 | - $this->secureRandom, |
|
| 115 | - $this->userManager, |
|
| 116 | - $this->rootFolder, |
|
| 117 | - $this->l, |
|
| 118 | - $this->logger, |
|
| 119 | - $this->mailer, |
|
| 120 | - $this->urlGenerator, |
|
| 121 | - $this->activityManager, |
|
| 122 | - $this->settingsManager, |
|
| 123 | - $this->defaults, |
|
| 124 | - $this->hasher, |
|
| 125 | - $this->eventDispatcher, |
|
| 126 | - $this->shareManager, |
|
| 127 | - $this->getEmailValidatorWithStrictEmailCheck(), |
|
| 128 | - ]) |
|
| 129 | - ->onlyMethods($mockedMethods) |
|
| 130 | - ->getMock(); |
|
| 131 | - } |
|
| 132 | - |
|
| 133 | - return new ShareByMailProvider( |
|
| 134 | - $this->config, |
|
| 135 | - $this->connection, |
|
| 136 | - $this->secureRandom, |
|
| 137 | - $this->userManager, |
|
| 138 | - $this->rootFolder, |
|
| 139 | - $this->l, |
|
| 140 | - $this->logger, |
|
| 141 | - $this->mailer, |
|
| 142 | - $this->urlGenerator, |
|
| 143 | - $this->activityManager, |
|
| 144 | - $this->settingsManager, |
|
| 145 | - $this->defaults, |
|
| 146 | - $this->hasher, |
|
| 147 | - $this->eventDispatcher, |
|
| 148 | - $this->shareManager, |
|
| 149 | - $this->getEmailValidatorWithStrictEmailCheck(), |
|
| 150 | - ); |
|
| 151 | - } |
|
| 152 | - |
|
| 153 | - protected function tearDown(): void { |
|
| 154 | - $this->connection |
|
| 155 | - ->getQueryBuilder() |
|
| 156 | - ->delete('share') |
|
| 157 | - ->executeStatement(); |
|
| 158 | - |
|
| 159 | - parent::tearDown(); |
|
| 160 | - } |
|
| 161 | - |
|
| 162 | - public function testCreate(): void { |
|
| 163 | - $expectedShare = $this->createMock(IShare::class); |
|
| 164 | - |
|
| 165 | - $share = $this->createMock(IShare::class); |
|
| 166 | - $share->expects($this->any())->method('getSharedWith')->willReturn('user1'); |
|
| 167 | - |
|
| 168 | - $node = $this->createMock(File::class); |
|
| 169 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 170 | - |
|
| 171 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'sendEmail', 'sendPassword']); |
|
| 172 | - |
|
| 173 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 174 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 175 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 176 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']); |
|
| 177 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare); |
|
| 178 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 179 | - |
|
| 180 | - // As share api link password is not enforced, the password will not be generated. |
|
| 181 | - $this->shareManager->expects($this->once())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 182 | - $this->settingsManager->expects($this->never())->method('sendPasswordByMail'); |
|
| 183 | - |
|
| 184 | - // Mail notification is triggered by the share manager. |
|
| 185 | - $instance->expects($this->never())->method('sendEmail'); |
|
| 186 | - $instance->expects($this->never())->method('sendPassword'); |
|
| 187 | - |
|
| 188 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 189 | - } |
|
| 190 | - |
|
| 191 | - public function testCreateSendPasswordByMailWithoutEnforcedPasswordProtection(): void { |
|
| 192 | - $expectedShare = $this->createMock(IShare::class); |
|
| 193 | - |
|
| 194 | - $node = $this->createMock(File::class); |
|
| 195 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 196 | - |
|
| 197 | - $share = $this->createMock(IShare::class); |
|
| 198 | - $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@examplelölöl.com'); |
|
| 199 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 200 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 201 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 202 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 203 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 204 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 205 | - |
|
| 206 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 207 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 208 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 209 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 210 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']); |
|
| 211 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare); |
|
| 212 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 213 | - |
|
| 214 | - // The autogenerated password should not be mailed. |
|
| 215 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 216 | - $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 217 | - $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 218 | - |
|
| 219 | - // No password is set and no password sent via talk is requested |
|
| 220 | - $instance->expects($this->once())->method('sendEmail')->with($share, ['receiver@examplelölöl.com']); |
|
| 221 | - $instance->expects($this->never())->method('sendPassword'); |
|
| 222 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 223 | - |
|
| 224 | - // The manager sends the mail notification. |
|
| 225 | - // For the sake of testing simplicity, we will handle it ourselves. |
|
| 226 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 227 | - $instance->sendMailNotification($share); |
|
| 228 | - } |
|
| 229 | - |
|
| 230 | - public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 231 | - $expectedShare = $this->createMock(IShare::class); |
|
| 232 | - |
|
| 233 | - $node = $this->createMock(File::class); |
|
| 234 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 235 | - |
|
| 236 | - $share = $this->createMock(IShare::class); |
|
| 237 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 238 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 239 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 240 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 241 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 242 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 243 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 244 | - |
|
| 245 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 246 | - |
|
| 247 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 248 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 249 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 250 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 251 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 252 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 253 | - |
|
| 254 | - $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 255 | - $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 256 | - $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 257 | - |
|
| 258 | - // The given password (but not the autogenerated password) should not be |
|
| 259 | - // mailed to the receiver of the share because permanent passwords are not enforced. |
|
| 260 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 261 | - $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 262 | - $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 263 | - |
|
| 264 | - // A password is set but no password sent via talk has been requested |
|
| 265 | - $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']); |
|
| 266 | - $instance->expects($this->once())->method('sendPassword')->with($share, 'password'); |
|
| 267 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 268 | - |
|
| 269 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 270 | - $instance->sendMailNotification($share); |
|
| 271 | - } |
|
| 272 | - |
|
| 273 | - public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithoutPermanentPassword(): void { |
|
| 274 | - $expectedShare = $this->createMock(IShare::class); |
|
| 275 | - |
|
| 276 | - $node = $this->createMock(File::class); |
|
| 277 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 278 | - |
|
| 279 | - $share = $this->createMock(IShare::class); |
|
| 280 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 281 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 282 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 283 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 284 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 285 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 286 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 287 | - |
|
| 288 | - $instance = $this->getInstance([ |
|
| 289 | - 'getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', |
|
| 290 | - 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', |
|
| 291 | - 'sendEmail', 'sendPassword', 'sendPasswordToOwner', |
|
| 292 | - ]); |
|
| 293 | - |
|
| 294 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 295 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 296 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 297 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 298 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 299 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 300 | - |
|
| 301 | - $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 302 | - $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 303 | - $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 304 | - |
|
| 305 | - // No password is generated, so no emails need to be sent |
|
| 306 | - // aside from the main email notification. |
|
| 307 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 308 | - $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 309 | - $this->config->expects($this->once())->method('getSystemValue') |
|
| 310 | - ->with('sharing.enable_mail_link_password_expiration') |
|
| 311 | - ->willReturn(true); |
|
| 312 | - |
|
| 313 | - // No password has been set and no password sent via talk has been requested, |
|
| 314 | - // but password has been enforced for the whole instance and will be generated. |
|
| 315 | - $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']); |
|
| 316 | - $instance->expects($this->never())->method('sendPassword'); |
|
| 317 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 318 | - |
|
| 319 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 320 | - $instance->sendMailNotification($share); |
|
| 321 | - } |
|
| 322 | - |
|
| 323 | - public function testCreateSendPasswordByMailWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 324 | - $expectedShare = $this->createMock(IShare::class); |
|
| 325 | - |
|
| 326 | - $node = $this->createMock(File::class); |
|
| 327 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 328 | - |
|
| 329 | - $share = $this->createMock(IShare::class); |
|
| 330 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 331 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 332 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 333 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 334 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 335 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 336 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 337 | - |
|
| 338 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 339 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 340 | - ->willReturn('https://example.com/file.txt'); |
|
| 341 | - |
|
| 342 | - $this->secureRandom->expects($this->once()) |
|
| 343 | - ->method('generate') |
|
| 344 | - ->with(8, ISecureRandom::CHAR_HUMAN_READABLE) |
|
| 345 | - ->willReturn('autogeneratedPassword'); |
|
| 346 | - $this->eventDispatcher->expects($this->once()) |
|
| 347 | - ->method('dispatchTyped') |
|
| 348 | - ->with(new GenerateSecurePasswordEvent(PasswordContext::SHARING)); |
|
| 349 | - |
|
| 350 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'createPasswordSendActivity', 'sendPasswordToOwner']); |
|
| 351 | - |
|
| 352 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 353 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 354 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 355 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']); |
|
| 356 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare); |
|
| 357 | - |
|
| 358 | - // Initially not set, but will be set by the autoGeneratePassword method. |
|
| 359 | - $share->expects($this->exactly(3))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword'); |
|
| 360 | - $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); |
|
| 361 | - $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); |
|
| 362 | - |
|
| 363 | - // The autogenerated password should be mailed to the receiver of the share because permanent passwords are enforced. |
|
| 364 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 365 | - $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 366 | - $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 367 | - |
|
| 368 | - $message = $this->createMock(IMessage::class); |
|
| 369 | - $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']); |
|
| 370 | - $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 371 | - $calls = [ |
|
| 372 | - [ |
|
| 373 | - 'sharebymail.RecipientNotification', |
|
| 374 | - [ |
|
| 375 | - 'filename' => 'filename', |
|
| 376 | - 'link' => 'https://example.com/file.txt', |
|
| 377 | - 'initiator' => 'owner', |
|
| 378 | - 'expiration' => null, |
|
| 379 | - 'shareWith' => '[email protected]', |
|
| 380 | - 'note' => '', |
|
| 381 | - ], |
|
| 382 | - ], |
|
| 383 | - [ |
|
| 384 | - 'sharebymail.RecipientPasswordNotification', |
|
| 385 | - [ |
|
| 386 | - 'filename' => 'filename', |
|
| 387 | - 'password' => 'autogeneratedPassword', |
|
| 388 | - 'initiator' => 'owner', |
|
| 389 | - 'initiatorEmail' => null, |
|
| 390 | - 'shareWith' => '[email protected]', |
|
| 391 | - ], |
|
| 392 | - ], |
|
| 393 | - ]; |
|
| 394 | - $this->mailer->expects($this->exactly(2)) |
|
| 395 | - ->method('createEMailTemplate') |
|
| 396 | - ->willReturnCallback(function () use (&$calls) { |
|
| 397 | - $expected = array_shift($calls); |
|
| 398 | - $this->assertEquals($expected, func_get_args()); |
|
| 399 | - return $this->createMock(IEMailTemplate::class); |
|
| 400 | - }); |
|
| 401 | - |
|
| 402 | - // Main email notification is sent as well as the password |
|
| 403 | - // to the recipient because shareApiLinkEnforcePassword is enabled. |
|
| 404 | - $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 405 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 406 | - |
|
| 407 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 408 | - $instance->sendMailNotification($share); |
|
| 409 | - } |
|
| 410 | - |
|
| 411 | - public function testCreateSendPasswordByMailWithPasswordAndWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 412 | - $expectedShare = $this->createMock(IShare::class); |
|
| 413 | - |
|
| 414 | - $node = $this->createMock(File::class); |
|
| 415 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 416 | - |
|
| 417 | - $share = $this->createMock(IShare::class); |
|
| 418 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 419 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 420 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 421 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 422 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 423 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 424 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 425 | - |
|
| 426 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 427 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 428 | - ->willReturn('https://example.com/file.txt'); |
|
| 429 | - |
|
| 430 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendPasswordToOwner']); |
|
| 431 | - |
|
| 432 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 433 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 434 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 435 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 436 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 437 | - |
|
| 438 | - $share->expects($this->exactly(3))->method('getPassword')->willReturn('password'); |
|
| 439 | - $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 440 | - $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 441 | - |
|
| 442 | - // The given password (but not the autogenerated password) should be |
|
| 443 | - // mailed to the receiver of the share. |
|
| 444 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 445 | - $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 446 | - $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 447 | - $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 448 | - |
|
| 449 | - $message = $this->createMock(IMessage::class); |
|
| 450 | - $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']); |
|
| 451 | - $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 452 | - |
|
| 453 | - $calls = [ |
|
| 454 | - [ |
|
| 455 | - 'sharebymail.RecipientNotification', |
|
| 456 | - [ |
|
| 457 | - 'filename' => 'filename', |
|
| 458 | - 'link' => 'https://example.com/file.txt', |
|
| 459 | - 'initiator' => 'owner', |
|
| 460 | - 'expiration' => null, |
|
| 461 | - 'shareWith' => '[email protected]', |
|
| 462 | - 'note' => '', |
|
| 463 | - ], |
|
| 464 | - ], |
|
| 465 | - [ |
|
| 466 | - 'sharebymail.RecipientPasswordNotification', |
|
| 467 | - [ |
|
| 468 | - 'filename' => 'filename', |
|
| 469 | - 'password' => 'password', |
|
| 470 | - 'initiator' => 'owner', |
|
| 471 | - 'initiatorEmail' => null, |
|
| 472 | - 'shareWith' => '[email protected]', |
|
| 473 | - ], |
|
| 474 | - ], |
|
| 475 | - ]; |
|
| 476 | - $this->mailer->expects($this->exactly(2)) |
|
| 477 | - ->method('createEMailTemplate') |
|
| 478 | - ->willReturnCallback(function () use (&$calls) { |
|
| 479 | - $expected = array_shift($calls); |
|
| 480 | - $this->assertEquals($expected, func_get_args()); |
|
| 481 | - return $this->createMock(IEMailTemplate::class); |
|
| 482 | - }); |
|
| 483 | - |
|
| 484 | - // Main email notification is sent as well as the password |
|
| 485 | - // to the recipient because the password is set. |
|
| 486 | - $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 487 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 488 | - |
|
| 489 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 490 | - $instance->sendMailNotification($share); |
|
| 491 | - } |
|
| 492 | - |
|
| 493 | - public function testCreateSendPasswordByTalkWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 494 | - $expectedShare = $this->createMock(IShare::class); |
|
| 495 | - |
|
| 496 | - // The owner of the share. |
|
| 497 | - $owner = $this->createMock(IUser::class); |
|
| 498 | - $this->userManager->expects($this->any())->method('get')->with('owner')->willReturn($owner); |
|
| 499 | - $owner->expects($this->any())->method('getEMailAddress')->willReturn('[email protected]'); |
|
| 500 | - $owner->expects($this->any())->method('getDisplayName')->willReturn('owner'); |
|
| 501 | - |
|
| 502 | - $node = $this->createMock(File::class); |
|
| 503 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 504 | - |
|
| 505 | - $share = $this->createMock(IShare::class); |
|
| 506 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 507 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(true); |
|
| 508 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 509 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 510 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 511 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 512 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 513 | - |
|
| 514 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 515 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 516 | - ->willReturn('https://example.com/file.txt'); |
|
| 517 | - |
|
| 518 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']); |
|
| 519 | - |
|
| 520 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 521 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 522 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 523 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']); |
|
| 524 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare); |
|
| 525 | - |
|
| 526 | - $share->expects($this->exactly(4))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword', 'autogeneratedPassword'); |
|
| 527 | - $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); |
|
| 528 | - $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); |
|
| 529 | - |
|
| 530 | - // The autogenerated password should be mailed to the owner of the share. |
|
| 531 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 532 | - $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 533 | - $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 534 | - $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword'); |
|
| 535 | - |
|
| 536 | - $message = $this->createMock(IMessage::class); |
|
| 537 | - $setToCalls = [ |
|
| 538 | - [['[email protected]']], |
|
| 539 | - [['[email protected]' => 'owner']], |
|
| 540 | - ]; |
|
| 541 | - $message->expects($this->exactly(2)) |
|
| 542 | - ->method('setTo') |
|
| 543 | - ->willReturnCallback(function () use (&$setToCalls, $message) { |
|
| 544 | - $expected = array_shift($setToCalls); |
|
| 545 | - $this->assertEquals($expected, func_get_args()); |
|
| 546 | - return $message; |
|
| 547 | - }); |
|
| 548 | - $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 549 | - |
|
| 550 | - $calls = [ |
|
| 551 | - [ |
|
| 552 | - 'sharebymail.RecipientNotification', |
|
| 553 | - [ |
|
| 554 | - 'filename' => 'filename', |
|
| 555 | - 'link' => 'https://example.com/file.txt', |
|
| 556 | - 'initiator' => 'owner', |
|
| 557 | - 'expiration' => null, |
|
| 558 | - 'shareWith' => '[email protected]', |
|
| 559 | - 'note' => '', |
|
| 560 | - ], |
|
| 561 | - ], |
|
| 562 | - [ |
|
| 563 | - 'sharebymail.OwnerPasswordNotification', |
|
| 564 | - [ |
|
| 565 | - 'filename' => 'filename', |
|
| 566 | - 'password' => 'autogeneratedPassword', |
|
| 567 | - 'initiator' => 'owner', |
|
| 568 | - 'initiatorEmail' => '[email protected]', |
|
| 569 | - 'shareWith' => '[email protected]', |
|
| 570 | - ], |
|
| 571 | - ], |
|
| 572 | - ]; |
|
| 573 | - $this->mailer->expects($this->exactly(2)) |
|
| 574 | - ->method('createEMailTemplate') |
|
| 575 | - ->willReturnCallback(function () use (&$calls) { |
|
| 576 | - $expected = array_shift($calls); |
|
| 577 | - $this->assertEquals($expected, func_get_args()); |
|
| 578 | - return $this->createMock(IEMailTemplate::class); |
|
| 579 | - }); |
|
| 580 | - |
|
| 581 | - // Main email notification is sent as well as the password to owner |
|
| 582 | - // because the password is set and SendPasswordByTalk is enabled. |
|
| 583 | - $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 584 | - |
|
| 585 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 586 | - $instance->sendMailNotification($share); |
|
| 587 | - } |
|
| 588 | - |
|
| 589 | - // If attributes is set to multiple emails, use them as BCC |
|
| 590 | - public function sendNotificationToMultipleEmails() { |
|
| 591 | - $expectedShare = $this->createMock(IShare::class); |
|
| 592 | - |
|
| 593 | - $node = $this->createMock(File::class); |
|
| 594 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 595 | - |
|
| 596 | - $share = $this->createMock(IShare::class); |
|
| 597 | - $share->expects($this->any())->method('getSharedWith')->willReturn(''); |
|
| 598 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 599 | - $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 600 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 601 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 602 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 603 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 604 | - |
|
| 605 | - $attributes = $this->createMock(IAttributes::class); |
|
| 606 | - $share->expects($this->any())->method('getAttributes')->willReturn($attributes); |
|
| 607 | - $attributes->expects($this->any())->method('getAttribute')->with('shareWith', 'emails')->willReturn([ |
|
| 608 | - '[email protected]', |
|
| 609 | - '[email protected]', |
|
| 610 | - '[email protected]', |
|
| 611 | - ]); |
|
| 612 | - |
|
| 613 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 614 | - |
|
| 615 | - $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 616 | - $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 617 | - $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 618 | - $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 619 | - $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 620 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 621 | - |
|
| 622 | - $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 623 | - $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 624 | - $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 625 | - |
|
| 626 | - // The given password (but not the autogenerated password) should not be |
|
| 627 | - // mailed to the receiver of the share because permanent passwords are not enforced. |
|
| 628 | - $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 629 | - $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 630 | - $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 631 | - |
|
| 632 | - // A password is set but no password sent via talk has been requested |
|
| 633 | - $instance->expects($this->once())->method('sendEmail') |
|
| 634 | - ->with($share, ['[email protected]', '[email protected]', '[email protected]']); |
|
| 635 | - $instance->expects($this->once())->method('sendPassword')->with($share, 'password'); |
|
| 636 | - $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 637 | - |
|
| 638 | - |
|
| 639 | - $message = $this->createMock(IMessage::class); |
|
| 640 | - $message->expects($this->never())->method('setTo'); |
|
| 641 | - $message->expects($this->exactly(2))->method('setBcc')->with(['[email protected]', '[email protected]', '[email protected]']); |
|
| 642 | - $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 643 | - |
|
| 644 | - // Main email notification is sent as well as the password |
|
| 645 | - // to recipients because the password is set. |
|
| 646 | - $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 647 | - |
|
| 648 | - $this->assertSame($expectedShare, $instance->create($share)); |
|
| 649 | - $instance->sendMailNotification($share); |
|
| 650 | - } |
|
| 651 | - |
|
| 652 | - public function testCreateFailed(): void { |
|
| 653 | - $this->expectException(\Exception::class); |
|
| 654 | - |
|
| 655 | - $this->share->expects($this->once())->method('getSharedWith')->willReturn('user1'); |
|
| 656 | - $node = $this->createMock('OCP\Files\Node'); |
|
| 657 | - $node->expects($this->any())->method('getName')->willReturn('fileName'); |
|
| 658 | - $this->share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 659 | - |
|
| 660 | - $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject']); |
|
| 661 | - |
|
| 662 | - $instance->expects($this->once())->method('getSharedWith')->willReturn(['found']); |
|
| 663 | - $instance->expects($this->never())->method('createMailShare'); |
|
| 664 | - $instance->expects($this->never())->method('getRawShare'); |
|
| 665 | - $instance->expects($this->never())->method('createShareObject'); |
|
| 666 | - |
|
| 667 | - $this->assertSame('shareObject', |
|
| 668 | - $instance->create($this->share) |
|
| 669 | - ); |
|
| 670 | - } |
|
| 671 | - |
|
| 672 | - public function testCreateMailShare(): void { |
|
| 673 | - $this->share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 674 | - $this->share->expects($this->once())->method('setToken')->with('token'); |
|
| 675 | - $this->share->expects($this->any())->method('getSharedBy')->willReturn('[email protected]'); |
|
| 676 | - $this->share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 677 | - $this->share->expects($this->any())->method('getNote')->willReturn('Check this!'); |
|
| 678 | - $this->share->expects($this->any())->method('getMailSend')->willReturn(true); |
|
| 679 | - |
|
| 680 | - $node = $this->createMock('OCP\Files\Node'); |
|
| 681 | - $node->expects($this->any())->method('getName')->willReturn('fileName'); |
|
| 682 | - $this->share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 683 | - |
|
| 684 | - $instance = $this->getInstance(['generateToken', 'addShareToDB', 'sendMailNotification']); |
|
| 685 | - |
|
| 686 | - $instance->expects($this->once())->method('generateToken')->willReturn('token'); |
|
| 687 | - $instance->expects($this->once())->method('addShareToDB')->willReturn(42); |
|
| 688 | - |
|
| 689 | - // The manager handle the mail sending |
|
| 690 | - $instance->expects($this->never())->method('sendMailNotification'); |
|
| 691 | - |
|
| 692 | - $this->assertSame(42, |
|
| 693 | - $this->invokePrivate($instance, 'createMailShare', [$this->share]) |
|
| 694 | - ); |
|
| 695 | - } |
|
| 696 | - |
|
| 697 | - public function testGenerateToken(): void { |
|
| 698 | - $instance = $this->getInstance(); |
|
| 699 | - |
|
| 700 | - $this->secureRandom->expects($this->once())->method('generate')->willReturn('token'); |
|
| 701 | - |
|
| 702 | - $this->assertSame('token', |
|
| 703 | - $this->invokePrivate($instance, 'generateToken') |
|
| 704 | - ); |
|
| 705 | - } |
|
| 706 | - |
|
| 707 | - public function testAddShareToDB(): void { |
|
| 708 | - $itemSource = 11; |
|
| 709 | - $itemType = 'file'; |
|
| 710 | - $shareWith = '[email protected]'; |
|
| 711 | - $sharedBy = 'user1'; |
|
| 712 | - $uidOwner = 'user2'; |
|
| 713 | - $permissions = 1; |
|
| 714 | - $token = 'token'; |
|
| 715 | - $password = 'password'; |
|
| 716 | - $sendPasswordByTalk = true; |
|
| 717 | - $hideDownload = true; |
|
| 718 | - $label = 'label'; |
|
| 719 | - $expiration = new \DateTime(); |
|
| 720 | - $passwordExpirationTime = new \DateTime(); |
|
| 721 | - |
|
| 722 | - |
|
| 723 | - $instance = $this->getInstance(); |
|
| 724 | - $id = $this->invokePrivate( |
|
| 725 | - $instance, |
|
| 726 | - 'addShareToDB', |
|
| 727 | - [ |
|
| 728 | - $itemSource, |
|
| 729 | - $itemType, |
|
| 730 | - $shareWith, |
|
| 731 | - $sharedBy, |
|
| 732 | - $uidOwner, |
|
| 733 | - $permissions, |
|
| 734 | - $token, |
|
| 735 | - $password, |
|
| 736 | - $passwordExpirationTime, |
|
| 737 | - $sendPasswordByTalk, |
|
| 738 | - $hideDownload, |
|
| 739 | - $label, |
|
| 740 | - $expiration |
|
| 741 | - ] |
|
| 742 | - ); |
|
| 743 | - |
|
| 744 | - $qb = $this->connection->getQueryBuilder(); |
|
| 745 | - $qb->select('*') |
|
| 746 | - ->from('share') |
|
| 747 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 748 | - |
|
| 749 | - $qResult = $qb->executeQuery(); |
|
| 750 | - $result = $qResult->fetchAllAssociative(); |
|
| 751 | - $qResult->closeCursor(); |
|
| 752 | - |
|
| 753 | - $this->assertSame(1, count($result)); |
|
| 754 | - |
|
| 755 | - $this->assertSame($itemSource, (int)$result[0]['item_source']); |
|
| 756 | - $this->assertSame($itemType, $result[0]['item_type']); |
|
| 757 | - $this->assertSame($shareWith, $result[0]['share_with']); |
|
| 758 | - $this->assertSame($sharedBy, $result[0]['uid_initiator']); |
|
| 759 | - $this->assertSame($uidOwner, $result[0]['uid_owner']); |
|
| 760 | - $this->assertSame($permissions, (int)$result[0]['permissions']); |
|
| 761 | - $this->assertSame($token, $result[0]['token']); |
|
| 762 | - $this->assertSame($password, $result[0]['password']); |
|
| 763 | - $this->assertSame($passwordExpirationTime->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['password_expiration_time'])->getTimestamp()); |
|
| 764 | - $this->assertSame($sendPasswordByTalk, (bool)$result[0]['password_by_talk']); |
|
| 765 | - $this->assertSame($hideDownload, (bool)$result[0]['hide_download']); |
|
| 766 | - $this->assertSame($label, $result[0]['label']); |
|
| 767 | - $this->assertSame($expiration->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['expiration'])->getTimestamp()); |
|
| 768 | - } |
|
| 769 | - |
|
| 770 | - public function testUpdate(): void { |
|
| 771 | - $itemSource = 11; |
|
| 772 | - $itemType = 'file'; |
|
| 773 | - $shareWith = '[email protected]'; |
|
| 774 | - $sharedBy = 'user1'; |
|
| 775 | - $uidOwner = 'user2'; |
|
| 776 | - $permissions = 1; |
|
| 777 | - $token = 'token'; |
|
| 778 | - $note = 'personal note'; |
|
| 779 | - |
|
| 780 | - |
|
| 781 | - $instance = $this->getInstance(); |
|
| 782 | - |
|
| 783 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note); |
|
| 784 | - |
|
| 785 | - $this->share->expects($this->once())->method('getPermissions')->willReturn($permissions + 1); |
|
| 786 | - $this->share->expects($this->once())->method('getShareOwner')->willReturn($uidOwner); |
|
| 787 | - $this->share->expects($this->once())->method('getSharedBy')->willReturn($sharedBy); |
|
| 788 | - $this->share->expects($this->any())->method('getNote')->willReturn($note); |
|
| 789 | - $this->share->expects($this->atLeastOnce())->method('getId')->willReturn($id); |
|
| 790 | - $this->share->expects($this->atLeastOnce())->method('getNodeId')->willReturn($itemSource); |
|
| 791 | - $this->share->expects($this->once())->method('getSharedWith')->willReturn($shareWith); |
|
| 792 | - |
|
| 793 | - $this->assertSame($this->share, |
|
| 794 | - $instance->update($this->share) |
|
| 795 | - ); |
|
| 796 | - |
|
| 797 | - $qb = $this->connection->getQueryBuilder(); |
|
| 798 | - $qb->select('*') |
|
| 799 | - ->from('share') |
|
| 800 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 801 | - |
|
| 802 | - $qResult = $qb->executeQuery(); |
|
| 803 | - $result = $qResult->fetchAllAssociative(); |
|
| 804 | - $qResult->closeCursor(); |
|
| 805 | - |
|
| 806 | - $this->assertSame(1, count($result)); |
|
| 807 | - |
|
| 808 | - $this->assertSame($itemSource, (int)$result[0]['item_source']); |
|
| 809 | - $this->assertSame($itemType, $result[0]['item_type']); |
|
| 810 | - $this->assertSame($shareWith, $result[0]['share_with']); |
|
| 811 | - $this->assertSame($sharedBy, $result[0]['uid_initiator']); |
|
| 812 | - $this->assertSame($uidOwner, $result[0]['uid_owner']); |
|
| 813 | - $this->assertSame($permissions + 1, (int)$result[0]['permissions']); |
|
| 814 | - $this->assertSame($token, $result[0]['token']); |
|
| 815 | - $this->assertSame($note, $result[0]['note']); |
|
| 816 | - } |
|
| 817 | - |
|
| 818 | - public static function dataUpdateSendPassword(): array { |
|
| 819 | - return [ |
|
| 820 | - ['password', 'hashed', 'hashed new', false, false, true], |
|
| 821 | - ['', 'hashed', 'hashed new', false, false, false], |
|
| 822 | - [null, 'hashed', 'hashed new', false, false, false], |
|
| 823 | - ['password', 'hashed', 'hashed', false, false, false], |
|
| 824 | - ['password', 'hashed', 'hashed new', false, true, false], |
|
| 825 | - ['password', 'hashed', 'hashed new', true, false, true], |
|
| 826 | - ['password', 'hashed', 'hashed', true, false, true], |
|
| 827 | - ]; |
|
| 828 | - } |
|
| 829 | - |
|
| 830 | - #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateSendPassword')] |
|
| 831 | - public function testUpdateSendPassword(?string $plainTextPassword, string $originalPassword, string $newPassword, bool $originalSendPasswordByTalk, bool $newSendPasswordByTalk, bool $sendMail): void { |
|
| 832 | - $node = $this->createMock(File::class); |
|
| 833 | - $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 834 | - |
|
| 835 | - $this->settingsManager->method('sendPasswordByMail')->willReturn(true); |
|
| 836 | - |
|
| 837 | - $originalShare = $this->createMock(IShare::class); |
|
| 838 | - $originalShare->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 839 | - $originalShare->expects($this->any())->method('getNode')->willReturn($node); |
|
| 840 | - $originalShare->expects($this->any())->method('getId')->willReturn(42); |
|
| 841 | - $originalShare->expects($this->any())->method('getPassword')->willReturn($originalPassword); |
|
| 842 | - $originalShare->expects($this->any())->method('getSendPasswordByTalk')->willReturn($originalSendPasswordByTalk); |
|
| 843 | - |
|
| 844 | - $share = $this->createMock(IShare::class); |
|
| 845 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 846 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 847 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 848 | - $share->expects($this->any())->method('getPassword')->willReturn($newPassword); |
|
| 849 | - $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn($newSendPasswordByTalk); |
|
| 850 | - |
|
| 851 | - if ($sendMail) { |
|
| 852 | - $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ |
|
| 853 | - 'filename' => 'filename', |
|
| 854 | - 'password' => $plainTextPassword, |
|
| 855 | - 'initiator' => null, |
|
| 856 | - 'initiatorEmail' => null, |
|
| 857 | - 'shareWith' => '[email protected]', |
|
| 858 | - ]); |
|
| 859 | - $this->mailer->expects($this->once())->method('send'); |
|
| 860 | - } else { |
|
| 861 | - $this->mailer->expects($this->never())->method('send'); |
|
| 862 | - } |
|
| 863 | - |
|
| 864 | - $instance = $this->getInstance(['getShareById', 'createPasswordSendActivity']); |
|
| 865 | - $instance->expects($this->once())->method('getShareById')->willReturn($originalShare); |
|
| 866 | - |
|
| 867 | - $this->assertSame($share, |
|
| 868 | - $instance->update($share, $plainTextPassword) |
|
| 869 | - ); |
|
| 870 | - } |
|
| 871 | - |
|
| 872 | - public function testDelete(): void { |
|
| 873 | - $instance = $this->getInstance(['removeShareFromTable', 'createShareActivity']); |
|
| 874 | - $this->share->expects($this->once())->method('getId')->willReturn(42); |
|
| 875 | - $instance->expects($this->once())->method('removeShareFromTable')->with(42); |
|
| 876 | - $instance->expects($this->once())->method('createShareActivity')->with($this->share, 'unshare'); |
|
| 877 | - $instance->delete($this->share); |
|
| 878 | - } |
|
| 879 | - |
|
| 880 | - public function testGetShareById(): void { |
|
| 881 | - $instance = $this->getInstance(['createShareObject']); |
|
| 882 | - |
|
| 883 | - $itemSource = 11; |
|
| 884 | - $itemType = 'file'; |
|
| 885 | - $shareWith = '[email protected]'; |
|
| 886 | - $sharedBy = 'user1'; |
|
| 887 | - $uidOwner = 'user2'; |
|
| 888 | - $permissions = 1; |
|
| 889 | - $token = 'token'; |
|
| 890 | - |
|
| 891 | - $this->createDummyShare($itemType, $itemSource, $shareWith, 'user1wrong', 'user2wrong', $permissions, $token); |
|
| 892 | - $id2 = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 893 | - |
|
| 894 | - $instance->expects($this->once())->method('createShareObject') |
|
| 895 | - ->willReturnCallback( |
|
| 896 | - function ($data) use ($uidOwner, $sharedBy, $id2) { |
|
| 897 | - $this->assertSame($uidOwner, $data['uid_owner']); |
|
| 898 | - $this->assertSame($sharedBy, $data['uid_initiator']); |
|
| 899 | - $this->assertSame($id2, (int)$data['id']); |
|
| 900 | - return $this->share; |
|
| 901 | - } |
|
| 902 | - ); |
|
| 903 | - |
|
| 904 | - $result = $instance->getShareById($id2); |
|
| 905 | - |
|
| 906 | - $this->assertInstanceOf('OCP\Share\IShare', $result); |
|
| 907 | - } |
|
| 908 | - |
|
| 909 | - |
|
| 910 | - public function testGetShareByIdFailed(): void { |
|
| 911 | - $this->expectException(ShareNotFound::class); |
|
| 912 | - |
|
| 913 | - $instance = $this->getInstance(['createShareObject']); |
|
| 914 | - |
|
| 915 | - $itemSource = 11; |
|
| 916 | - $itemType = 'file'; |
|
| 917 | - $shareWith = '[email protected]'; |
|
| 918 | - $sharedBy = 'user1'; |
|
| 919 | - $uidOwner = 'user2'; |
|
| 920 | - $permissions = 1; |
|
| 921 | - $token = 'token'; |
|
| 922 | - |
|
| 923 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 924 | - |
|
| 925 | - $instance->getShareById($id + 1); |
|
| 926 | - } |
|
| 927 | - |
|
| 928 | - public function testGetShareByPath(): void { |
|
| 929 | - $itemSource = 11; |
|
| 930 | - $itemType = 'file'; |
|
| 931 | - $shareWith = '[email protected]'; |
|
| 932 | - $sharedBy = 'user1'; |
|
| 933 | - $uidOwner = 'user2'; |
|
| 934 | - $permissions = 1; |
|
| 935 | - $token = 'token'; |
|
| 936 | - |
|
| 937 | - $node = $this->createMock(Node::class); |
|
| 938 | - $node->expects($this->once())->method('getId')->willReturn($itemSource); |
|
| 939 | - |
|
| 940 | - |
|
| 941 | - $instance = $this->getInstance(['createShareObject']); |
|
| 942 | - |
|
| 943 | - $this->createDummyShare($itemType, 111, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 944 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 945 | - |
|
| 946 | - $instance->expects($this->once())->method('createShareObject') |
|
| 947 | - ->willReturnCallback( |
|
| 948 | - function ($data) use ($uidOwner, $sharedBy, $id) { |
|
| 949 | - $this->assertSame($uidOwner, $data['uid_owner']); |
|
| 950 | - $this->assertSame($sharedBy, $data['uid_initiator']); |
|
| 951 | - $this->assertSame($id, (int)$data['id']); |
|
| 952 | - return $this->share; |
|
| 953 | - } |
|
| 954 | - ); |
|
| 955 | - |
|
| 956 | - $result = $instance->getSharesByPath($node); |
|
| 957 | - |
|
| 958 | - $this->assertTrue(is_array($result)); |
|
| 959 | - $this->assertSame(1, count($result)); |
|
| 960 | - $this->assertInstanceOf('OCP\Share\IShare', $result[0]); |
|
| 961 | - } |
|
| 962 | - |
|
| 963 | - public function testGetShareByToken(): void { |
|
| 964 | - $itemSource = 11; |
|
| 965 | - $itemType = 'file'; |
|
| 966 | - $shareWith = '[email protected]'; |
|
| 967 | - $sharedBy = 'user1'; |
|
| 968 | - $uidOwner = 'user2'; |
|
| 969 | - $permissions = 1; |
|
| 970 | - $token = 'token'; |
|
| 53 | + use EmailValidatorTrait; |
|
| 54 | + |
|
| 55 | + private IDBConnection $connection; |
|
| 56 | + |
|
| 57 | + private IL10N&MockObject $l; |
|
| 58 | + private IShare&MockObject $share; |
|
| 59 | + private IConfig&MockObject $config; |
|
| 60 | + private IMailer&MockObject $mailer; |
|
| 61 | + private IHasher&MockObject $hasher; |
|
| 62 | + private Defaults&MockObject $defaults; |
|
| 63 | + private IManager&MockObject $shareManager; |
|
| 64 | + private LoggerInterface&MockObject $logger; |
|
| 65 | + private IRootFolder&MockObject $rootFolder; |
|
| 66 | + private IUserManager&MockObject $userManager; |
|
| 67 | + private ISecureRandom&MockObject $secureRandom; |
|
| 68 | + private IURLGenerator&MockObject $urlGenerator; |
|
| 69 | + private SettingsManager&MockObject $settingsManager; |
|
| 70 | + private IActivityManager&MockObject $activityManager; |
|
| 71 | + private IEventDispatcher&MockObject $eventDispatcher; |
|
| 72 | + |
|
| 73 | + protected function setUp(): void { |
|
| 74 | + parent::setUp(); |
|
| 75 | + |
|
| 76 | + $this->connection = Server::get(IDBConnection::class); |
|
| 77 | + |
|
| 78 | + $this->l = $this->createMock(IL10N::class); |
|
| 79 | + $this->l->method('t') |
|
| 80 | + ->willReturnCallback(function ($text, $parameters = []) { |
|
| 81 | + return vsprintf($text, $parameters); |
|
| 82 | + }); |
|
| 83 | + $this->config = $this->createMock(IConfig::class); |
|
| 84 | + $this->logger = $this->createMock(LoggerInterface::class); |
|
| 85 | + $this->rootFolder = $this->createMock('OCP\Files\IRootFolder'); |
|
| 86 | + $this->userManager = $this->createMock(IUserManager::class); |
|
| 87 | + $this->secureRandom = $this->createMock('\OCP\Security\ISecureRandom'); |
|
| 88 | + $this->mailer = $this->createMock('\OCP\Mail\IMailer'); |
|
| 89 | + $this->urlGenerator = $this->createMock(IURLGenerator::class); |
|
| 90 | + $this->share = $this->createMock(IShare::class); |
|
| 91 | + $this->activityManager = $this->createMock('OCP\Activity\IManager'); |
|
| 92 | + $this->settingsManager = $this->createMock(SettingsManager::class); |
|
| 93 | + $this->defaults = $this->createMock(Defaults::class); |
|
| 94 | + $this->hasher = $this->createMock(IHasher::class); |
|
| 95 | + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); |
|
| 96 | + $this->shareManager = $this->createMock(IManager::class); |
|
| 97 | + |
|
| 98 | + $this->userManager->expects($this->any())->method('userExists')->willReturn(true); |
|
| 99 | + $this->config->expects($this->any())->method('getAppValue')->with('core', 'enforce_strict_email_check')->willReturn('yes'); |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + /** |
|
| 103 | + * get instance of Mocked ShareByMailProvider |
|
| 104 | + * |
|
| 105 | + * @param array $mockedMethods internal methods which should be mocked |
|
| 106 | + * @return \PHPUnit\Framework\MockObject\MockObject | ShareByMailProvider |
|
| 107 | + */ |
|
| 108 | + private function getInstance(array $mockedMethods = []) { |
|
| 109 | + if (!empty($mockedMethods)) { |
|
| 110 | + return $this->getMockBuilder(ShareByMailProvider::class) |
|
| 111 | + ->setConstructorArgs([ |
|
| 112 | + $this->config, |
|
| 113 | + $this->connection, |
|
| 114 | + $this->secureRandom, |
|
| 115 | + $this->userManager, |
|
| 116 | + $this->rootFolder, |
|
| 117 | + $this->l, |
|
| 118 | + $this->logger, |
|
| 119 | + $this->mailer, |
|
| 120 | + $this->urlGenerator, |
|
| 121 | + $this->activityManager, |
|
| 122 | + $this->settingsManager, |
|
| 123 | + $this->defaults, |
|
| 124 | + $this->hasher, |
|
| 125 | + $this->eventDispatcher, |
|
| 126 | + $this->shareManager, |
|
| 127 | + $this->getEmailValidatorWithStrictEmailCheck(), |
|
| 128 | + ]) |
|
| 129 | + ->onlyMethods($mockedMethods) |
|
| 130 | + ->getMock(); |
|
| 131 | + } |
|
| 132 | + |
|
| 133 | + return new ShareByMailProvider( |
|
| 134 | + $this->config, |
|
| 135 | + $this->connection, |
|
| 136 | + $this->secureRandom, |
|
| 137 | + $this->userManager, |
|
| 138 | + $this->rootFolder, |
|
| 139 | + $this->l, |
|
| 140 | + $this->logger, |
|
| 141 | + $this->mailer, |
|
| 142 | + $this->urlGenerator, |
|
| 143 | + $this->activityManager, |
|
| 144 | + $this->settingsManager, |
|
| 145 | + $this->defaults, |
|
| 146 | + $this->hasher, |
|
| 147 | + $this->eventDispatcher, |
|
| 148 | + $this->shareManager, |
|
| 149 | + $this->getEmailValidatorWithStrictEmailCheck(), |
|
| 150 | + ); |
|
| 151 | + } |
|
| 152 | + |
|
| 153 | + protected function tearDown(): void { |
|
| 154 | + $this->connection |
|
| 155 | + ->getQueryBuilder() |
|
| 156 | + ->delete('share') |
|
| 157 | + ->executeStatement(); |
|
| 158 | + |
|
| 159 | + parent::tearDown(); |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + public function testCreate(): void { |
|
| 163 | + $expectedShare = $this->createMock(IShare::class); |
|
| 164 | + |
|
| 165 | + $share = $this->createMock(IShare::class); |
|
| 166 | + $share->expects($this->any())->method('getSharedWith')->willReturn('user1'); |
|
| 167 | + |
|
| 168 | + $node = $this->createMock(File::class); |
|
| 169 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 170 | + |
|
| 171 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'sendEmail', 'sendPassword']); |
|
| 172 | + |
|
| 173 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 174 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 175 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 176 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']); |
|
| 177 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare); |
|
| 178 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 179 | + |
|
| 180 | + // As share api link password is not enforced, the password will not be generated. |
|
| 181 | + $this->shareManager->expects($this->once())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 182 | + $this->settingsManager->expects($this->never())->method('sendPasswordByMail'); |
|
| 183 | + |
|
| 184 | + // Mail notification is triggered by the share manager. |
|
| 185 | + $instance->expects($this->never())->method('sendEmail'); |
|
| 186 | + $instance->expects($this->never())->method('sendPassword'); |
|
| 187 | + |
|
| 188 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 189 | + } |
|
| 190 | + |
|
| 191 | + public function testCreateSendPasswordByMailWithoutEnforcedPasswordProtection(): void { |
|
| 192 | + $expectedShare = $this->createMock(IShare::class); |
|
| 193 | + |
|
| 194 | + $node = $this->createMock(File::class); |
|
| 195 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 196 | + |
|
| 197 | + $share = $this->createMock(IShare::class); |
|
| 198 | + $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@examplelölöl.com'); |
|
| 199 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 200 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 201 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 202 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 203 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 204 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 205 | + |
|
| 206 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 207 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 208 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 209 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 210 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']); |
|
| 211 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare); |
|
| 212 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 213 | + |
|
| 214 | + // The autogenerated password should not be mailed. |
|
| 215 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 216 | + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 217 | + $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 218 | + |
|
| 219 | + // No password is set and no password sent via talk is requested |
|
| 220 | + $instance->expects($this->once())->method('sendEmail')->with($share, ['receiver@examplelölöl.com']); |
|
| 221 | + $instance->expects($this->never())->method('sendPassword'); |
|
| 222 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 223 | + |
|
| 224 | + // The manager sends the mail notification. |
|
| 225 | + // For the sake of testing simplicity, we will handle it ourselves. |
|
| 226 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 227 | + $instance->sendMailNotification($share); |
|
| 228 | + } |
|
| 229 | + |
|
| 230 | + public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 231 | + $expectedShare = $this->createMock(IShare::class); |
|
| 232 | + |
|
| 233 | + $node = $this->createMock(File::class); |
|
| 234 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 235 | + |
|
| 236 | + $share = $this->createMock(IShare::class); |
|
| 237 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 238 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 239 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 240 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 241 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 242 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 243 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 244 | + |
|
| 245 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 246 | + |
|
| 247 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 248 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 249 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 250 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 251 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 252 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 253 | + |
|
| 254 | + $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 255 | + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 256 | + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 257 | + |
|
| 258 | + // The given password (but not the autogenerated password) should not be |
|
| 259 | + // mailed to the receiver of the share because permanent passwords are not enforced. |
|
| 260 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 261 | + $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 262 | + $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 263 | + |
|
| 264 | + // A password is set but no password sent via talk has been requested |
|
| 265 | + $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']); |
|
| 266 | + $instance->expects($this->once())->method('sendPassword')->with($share, 'password'); |
|
| 267 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 268 | + |
|
| 269 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 270 | + $instance->sendMailNotification($share); |
|
| 271 | + } |
|
| 272 | + |
|
| 273 | + public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithoutPermanentPassword(): void { |
|
| 274 | + $expectedShare = $this->createMock(IShare::class); |
|
| 275 | + |
|
| 276 | + $node = $this->createMock(File::class); |
|
| 277 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 278 | + |
|
| 279 | + $share = $this->createMock(IShare::class); |
|
| 280 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 281 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 282 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 283 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 284 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 285 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 286 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 287 | + |
|
| 288 | + $instance = $this->getInstance([ |
|
| 289 | + 'getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', |
|
| 290 | + 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', |
|
| 291 | + 'sendEmail', 'sendPassword', 'sendPasswordToOwner', |
|
| 292 | + ]); |
|
| 293 | + |
|
| 294 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 295 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 296 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 297 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 298 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 299 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 300 | + |
|
| 301 | + $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 302 | + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 303 | + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 304 | + |
|
| 305 | + // No password is generated, so no emails need to be sent |
|
| 306 | + // aside from the main email notification. |
|
| 307 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 308 | + $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 309 | + $this->config->expects($this->once())->method('getSystemValue') |
|
| 310 | + ->with('sharing.enable_mail_link_password_expiration') |
|
| 311 | + ->willReturn(true); |
|
| 312 | + |
|
| 313 | + // No password has been set and no password sent via talk has been requested, |
|
| 314 | + // but password has been enforced for the whole instance and will be generated. |
|
| 315 | + $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']); |
|
| 316 | + $instance->expects($this->never())->method('sendPassword'); |
|
| 317 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 318 | + |
|
| 319 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 320 | + $instance->sendMailNotification($share); |
|
| 321 | + } |
|
| 322 | + |
|
| 323 | + public function testCreateSendPasswordByMailWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 324 | + $expectedShare = $this->createMock(IShare::class); |
|
| 325 | + |
|
| 326 | + $node = $this->createMock(File::class); |
|
| 327 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 328 | + |
|
| 329 | + $share = $this->createMock(IShare::class); |
|
| 330 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 331 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 332 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 333 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 334 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 335 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 336 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 337 | + |
|
| 338 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 339 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 340 | + ->willReturn('https://example.com/file.txt'); |
|
| 341 | + |
|
| 342 | + $this->secureRandom->expects($this->once()) |
|
| 343 | + ->method('generate') |
|
| 344 | + ->with(8, ISecureRandom::CHAR_HUMAN_READABLE) |
|
| 345 | + ->willReturn('autogeneratedPassword'); |
|
| 346 | + $this->eventDispatcher->expects($this->once()) |
|
| 347 | + ->method('dispatchTyped') |
|
| 348 | + ->with(new GenerateSecurePasswordEvent(PasswordContext::SHARING)); |
|
| 349 | + |
|
| 350 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'createPasswordSendActivity', 'sendPasswordToOwner']); |
|
| 351 | + |
|
| 352 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 353 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 354 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 355 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']); |
|
| 356 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare); |
|
| 357 | + |
|
| 358 | + // Initially not set, but will be set by the autoGeneratePassword method. |
|
| 359 | + $share->expects($this->exactly(3))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword'); |
|
| 360 | + $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); |
|
| 361 | + $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); |
|
| 362 | + |
|
| 363 | + // The autogenerated password should be mailed to the receiver of the share because permanent passwords are enforced. |
|
| 364 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 365 | + $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 366 | + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 367 | + |
|
| 368 | + $message = $this->createMock(IMessage::class); |
|
| 369 | + $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']); |
|
| 370 | + $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 371 | + $calls = [ |
|
| 372 | + [ |
|
| 373 | + 'sharebymail.RecipientNotification', |
|
| 374 | + [ |
|
| 375 | + 'filename' => 'filename', |
|
| 376 | + 'link' => 'https://example.com/file.txt', |
|
| 377 | + 'initiator' => 'owner', |
|
| 378 | + 'expiration' => null, |
|
| 379 | + 'shareWith' => '[email protected]', |
|
| 380 | + 'note' => '', |
|
| 381 | + ], |
|
| 382 | + ], |
|
| 383 | + [ |
|
| 384 | + 'sharebymail.RecipientPasswordNotification', |
|
| 385 | + [ |
|
| 386 | + 'filename' => 'filename', |
|
| 387 | + 'password' => 'autogeneratedPassword', |
|
| 388 | + 'initiator' => 'owner', |
|
| 389 | + 'initiatorEmail' => null, |
|
| 390 | + 'shareWith' => '[email protected]', |
|
| 391 | + ], |
|
| 392 | + ], |
|
| 393 | + ]; |
|
| 394 | + $this->mailer->expects($this->exactly(2)) |
|
| 395 | + ->method('createEMailTemplate') |
|
| 396 | + ->willReturnCallback(function () use (&$calls) { |
|
| 397 | + $expected = array_shift($calls); |
|
| 398 | + $this->assertEquals($expected, func_get_args()); |
|
| 399 | + return $this->createMock(IEMailTemplate::class); |
|
| 400 | + }); |
|
| 401 | + |
|
| 402 | + // Main email notification is sent as well as the password |
|
| 403 | + // to the recipient because shareApiLinkEnforcePassword is enabled. |
|
| 404 | + $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 405 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 406 | + |
|
| 407 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 408 | + $instance->sendMailNotification($share); |
|
| 409 | + } |
|
| 410 | + |
|
| 411 | + public function testCreateSendPasswordByMailWithPasswordAndWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 412 | + $expectedShare = $this->createMock(IShare::class); |
|
| 413 | + |
|
| 414 | + $node = $this->createMock(File::class); |
|
| 415 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 416 | + |
|
| 417 | + $share = $this->createMock(IShare::class); |
|
| 418 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 419 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 420 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 421 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 422 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 423 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 424 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 425 | + |
|
| 426 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 427 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 428 | + ->willReturn('https://example.com/file.txt'); |
|
| 429 | + |
|
| 430 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendPasswordToOwner']); |
|
| 431 | + |
|
| 432 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 433 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 434 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 435 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 436 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 437 | + |
|
| 438 | + $share->expects($this->exactly(3))->method('getPassword')->willReturn('password'); |
|
| 439 | + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 440 | + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 441 | + |
|
| 442 | + // The given password (but not the autogenerated password) should be |
|
| 443 | + // mailed to the receiver of the share. |
|
| 444 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 445 | + $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 446 | + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 447 | + $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 448 | + |
|
| 449 | + $message = $this->createMock(IMessage::class); |
|
| 450 | + $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']); |
|
| 451 | + $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 452 | + |
|
| 453 | + $calls = [ |
|
| 454 | + [ |
|
| 455 | + 'sharebymail.RecipientNotification', |
|
| 456 | + [ |
|
| 457 | + 'filename' => 'filename', |
|
| 458 | + 'link' => 'https://example.com/file.txt', |
|
| 459 | + 'initiator' => 'owner', |
|
| 460 | + 'expiration' => null, |
|
| 461 | + 'shareWith' => '[email protected]', |
|
| 462 | + 'note' => '', |
|
| 463 | + ], |
|
| 464 | + ], |
|
| 465 | + [ |
|
| 466 | + 'sharebymail.RecipientPasswordNotification', |
|
| 467 | + [ |
|
| 468 | + 'filename' => 'filename', |
|
| 469 | + 'password' => 'password', |
|
| 470 | + 'initiator' => 'owner', |
|
| 471 | + 'initiatorEmail' => null, |
|
| 472 | + 'shareWith' => '[email protected]', |
|
| 473 | + ], |
|
| 474 | + ], |
|
| 475 | + ]; |
|
| 476 | + $this->mailer->expects($this->exactly(2)) |
|
| 477 | + ->method('createEMailTemplate') |
|
| 478 | + ->willReturnCallback(function () use (&$calls) { |
|
| 479 | + $expected = array_shift($calls); |
|
| 480 | + $this->assertEquals($expected, func_get_args()); |
|
| 481 | + return $this->createMock(IEMailTemplate::class); |
|
| 482 | + }); |
|
| 483 | + |
|
| 484 | + // Main email notification is sent as well as the password |
|
| 485 | + // to the recipient because the password is set. |
|
| 486 | + $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 487 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 488 | + |
|
| 489 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 490 | + $instance->sendMailNotification($share); |
|
| 491 | + } |
|
| 492 | + |
|
| 493 | + public function testCreateSendPasswordByTalkWithEnforcedPasswordProtectionWithPermanentPassword(): void { |
|
| 494 | + $expectedShare = $this->createMock(IShare::class); |
|
| 495 | + |
|
| 496 | + // The owner of the share. |
|
| 497 | + $owner = $this->createMock(IUser::class); |
|
| 498 | + $this->userManager->expects($this->any())->method('get')->with('owner')->willReturn($owner); |
|
| 499 | + $owner->expects($this->any())->method('getEMailAddress')->willReturn('[email protected]'); |
|
| 500 | + $owner->expects($this->any())->method('getDisplayName')->willReturn('owner'); |
|
| 501 | + |
|
| 502 | + $node = $this->createMock(File::class); |
|
| 503 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 504 | + |
|
| 505 | + $share = $this->createMock(IShare::class); |
|
| 506 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 507 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(true); |
|
| 508 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 509 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 510 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 511 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 512 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 513 | + |
|
| 514 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 515 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 516 | + ->willReturn('https://example.com/file.txt'); |
|
| 517 | + |
|
| 518 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']); |
|
| 519 | + |
|
| 520 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 521 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 522 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 523 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']); |
|
| 524 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare); |
|
| 525 | + |
|
| 526 | + $share->expects($this->exactly(4))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword', 'autogeneratedPassword'); |
|
| 527 | + $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); |
|
| 528 | + $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); |
|
| 529 | + |
|
| 530 | + // The autogenerated password should be mailed to the owner of the share. |
|
| 531 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true); |
|
| 532 | + $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 533 | + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); |
|
| 534 | + $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword'); |
|
| 535 | + |
|
| 536 | + $message = $this->createMock(IMessage::class); |
|
| 537 | + $setToCalls = [ |
|
| 538 | + [['[email protected]']], |
|
| 539 | + [['[email protected]' => 'owner']], |
|
| 540 | + ]; |
|
| 541 | + $message->expects($this->exactly(2)) |
|
| 542 | + ->method('setTo') |
|
| 543 | + ->willReturnCallback(function () use (&$setToCalls, $message) { |
|
| 544 | + $expected = array_shift($setToCalls); |
|
| 545 | + $this->assertEquals($expected, func_get_args()); |
|
| 546 | + return $message; |
|
| 547 | + }); |
|
| 548 | + $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 549 | + |
|
| 550 | + $calls = [ |
|
| 551 | + [ |
|
| 552 | + 'sharebymail.RecipientNotification', |
|
| 553 | + [ |
|
| 554 | + 'filename' => 'filename', |
|
| 555 | + 'link' => 'https://example.com/file.txt', |
|
| 556 | + 'initiator' => 'owner', |
|
| 557 | + 'expiration' => null, |
|
| 558 | + 'shareWith' => '[email protected]', |
|
| 559 | + 'note' => '', |
|
| 560 | + ], |
|
| 561 | + ], |
|
| 562 | + [ |
|
| 563 | + 'sharebymail.OwnerPasswordNotification', |
|
| 564 | + [ |
|
| 565 | + 'filename' => 'filename', |
|
| 566 | + 'password' => 'autogeneratedPassword', |
|
| 567 | + 'initiator' => 'owner', |
|
| 568 | + 'initiatorEmail' => '[email protected]', |
|
| 569 | + 'shareWith' => '[email protected]', |
|
| 570 | + ], |
|
| 571 | + ], |
|
| 572 | + ]; |
|
| 573 | + $this->mailer->expects($this->exactly(2)) |
|
| 574 | + ->method('createEMailTemplate') |
|
| 575 | + ->willReturnCallback(function () use (&$calls) { |
|
| 576 | + $expected = array_shift($calls); |
|
| 577 | + $this->assertEquals($expected, func_get_args()); |
|
| 578 | + return $this->createMock(IEMailTemplate::class); |
|
| 579 | + }); |
|
| 580 | + |
|
| 581 | + // Main email notification is sent as well as the password to owner |
|
| 582 | + // because the password is set and SendPasswordByTalk is enabled. |
|
| 583 | + $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 584 | + |
|
| 585 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 586 | + $instance->sendMailNotification($share); |
|
| 587 | + } |
|
| 588 | + |
|
| 589 | + // If attributes is set to multiple emails, use them as BCC |
|
| 590 | + public function sendNotificationToMultipleEmails() { |
|
| 591 | + $expectedShare = $this->createMock(IShare::class); |
|
| 592 | + |
|
| 593 | + $node = $this->createMock(File::class); |
|
| 594 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 595 | + |
|
| 596 | + $share = $this->createMock(IShare::class); |
|
| 597 | + $share->expects($this->any())->method('getSharedWith')->willReturn(''); |
|
| 598 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); |
|
| 599 | + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); |
|
| 600 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 601 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 602 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 603 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 604 | + |
|
| 605 | + $attributes = $this->createMock(IAttributes::class); |
|
| 606 | + $share->expects($this->any())->method('getAttributes')->willReturn($attributes); |
|
| 607 | + $attributes->expects($this->any())->method('getAttribute')->with('shareWith', 'emails')->willReturn([ |
|
| 608 | + '[email protected]', |
|
| 609 | + '[email protected]', |
|
| 610 | + '[email protected]', |
|
| 611 | + ]); |
|
| 612 | + |
|
| 613 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']); |
|
| 614 | + |
|
| 615 | + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); |
|
| 616 | + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); |
|
| 617 | + $instance->expects($this->once())->method('createShareActivity')->with($share); |
|
| 618 | + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']); |
|
| 619 | + $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare); |
|
| 620 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 621 | + |
|
| 622 | + $share->expects($this->any())->method('getPassword')->willReturn('password'); |
|
| 623 | + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); |
|
| 624 | + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); |
|
| 625 | + |
|
| 626 | + // The given password (but not the autogenerated password) should not be |
|
| 627 | + // mailed to the receiver of the share because permanent passwords are not enforced. |
|
| 628 | + $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false); |
|
| 629 | + $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false); |
|
| 630 | + $instance->expects($this->never())->method('autoGeneratePassword'); |
|
| 631 | + |
|
| 632 | + // A password is set but no password sent via talk has been requested |
|
| 633 | + $instance->expects($this->once())->method('sendEmail') |
|
| 634 | + ->with($share, ['[email protected]', '[email protected]', '[email protected]']); |
|
| 635 | + $instance->expects($this->once())->method('sendPassword')->with($share, 'password'); |
|
| 636 | + $instance->expects($this->never())->method('sendPasswordToOwner'); |
|
| 637 | + |
|
| 638 | + |
|
| 639 | + $message = $this->createMock(IMessage::class); |
|
| 640 | + $message->expects($this->never())->method('setTo'); |
|
| 641 | + $message->expects($this->exactly(2))->method('setBcc')->with(['[email protected]', '[email protected]', '[email protected]']); |
|
| 642 | + $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message); |
|
| 643 | + |
|
| 644 | + // Main email notification is sent as well as the password |
|
| 645 | + // to recipients because the password is set. |
|
| 646 | + $this->mailer->expects($this->exactly(2))->method('send'); |
|
| 647 | + |
|
| 648 | + $this->assertSame($expectedShare, $instance->create($share)); |
|
| 649 | + $instance->sendMailNotification($share); |
|
| 650 | + } |
|
| 651 | + |
|
| 652 | + public function testCreateFailed(): void { |
|
| 653 | + $this->expectException(\Exception::class); |
|
| 654 | + |
|
| 655 | + $this->share->expects($this->once())->method('getSharedWith')->willReturn('user1'); |
|
| 656 | + $node = $this->createMock('OCP\Files\Node'); |
|
| 657 | + $node->expects($this->any())->method('getName')->willReturn('fileName'); |
|
| 658 | + $this->share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 659 | + |
|
| 660 | + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject']); |
|
| 661 | + |
|
| 662 | + $instance->expects($this->once())->method('getSharedWith')->willReturn(['found']); |
|
| 663 | + $instance->expects($this->never())->method('createMailShare'); |
|
| 664 | + $instance->expects($this->never())->method('getRawShare'); |
|
| 665 | + $instance->expects($this->never())->method('createShareObject'); |
|
| 666 | + |
|
| 667 | + $this->assertSame('shareObject', |
|
| 668 | + $instance->create($this->share) |
|
| 669 | + ); |
|
| 670 | + } |
|
| 671 | + |
|
| 672 | + public function testCreateMailShare(): void { |
|
| 673 | + $this->share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 674 | + $this->share->expects($this->once())->method('setToken')->with('token'); |
|
| 675 | + $this->share->expects($this->any())->method('getSharedBy')->willReturn('[email protected]'); |
|
| 676 | + $this->share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 677 | + $this->share->expects($this->any())->method('getNote')->willReturn('Check this!'); |
|
| 678 | + $this->share->expects($this->any())->method('getMailSend')->willReturn(true); |
|
| 679 | + |
|
| 680 | + $node = $this->createMock('OCP\Files\Node'); |
|
| 681 | + $node->expects($this->any())->method('getName')->willReturn('fileName'); |
|
| 682 | + $this->share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 683 | + |
|
| 684 | + $instance = $this->getInstance(['generateToken', 'addShareToDB', 'sendMailNotification']); |
|
| 685 | + |
|
| 686 | + $instance->expects($this->once())->method('generateToken')->willReturn('token'); |
|
| 687 | + $instance->expects($this->once())->method('addShareToDB')->willReturn(42); |
|
| 688 | + |
|
| 689 | + // The manager handle the mail sending |
|
| 690 | + $instance->expects($this->never())->method('sendMailNotification'); |
|
| 691 | + |
|
| 692 | + $this->assertSame(42, |
|
| 693 | + $this->invokePrivate($instance, 'createMailShare', [$this->share]) |
|
| 694 | + ); |
|
| 695 | + } |
|
| 696 | + |
|
| 697 | + public function testGenerateToken(): void { |
|
| 698 | + $instance = $this->getInstance(); |
|
| 699 | + |
|
| 700 | + $this->secureRandom->expects($this->once())->method('generate')->willReturn('token'); |
|
| 701 | + |
|
| 702 | + $this->assertSame('token', |
|
| 703 | + $this->invokePrivate($instance, 'generateToken') |
|
| 704 | + ); |
|
| 705 | + } |
|
| 706 | + |
|
| 707 | + public function testAddShareToDB(): void { |
|
| 708 | + $itemSource = 11; |
|
| 709 | + $itemType = 'file'; |
|
| 710 | + $shareWith = '[email protected]'; |
|
| 711 | + $sharedBy = 'user1'; |
|
| 712 | + $uidOwner = 'user2'; |
|
| 713 | + $permissions = 1; |
|
| 714 | + $token = 'token'; |
|
| 715 | + $password = 'password'; |
|
| 716 | + $sendPasswordByTalk = true; |
|
| 717 | + $hideDownload = true; |
|
| 718 | + $label = 'label'; |
|
| 719 | + $expiration = new \DateTime(); |
|
| 720 | + $passwordExpirationTime = new \DateTime(); |
|
| 721 | + |
|
| 722 | + |
|
| 723 | + $instance = $this->getInstance(); |
|
| 724 | + $id = $this->invokePrivate( |
|
| 725 | + $instance, |
|
| 726 | + 'addShareToDB', |
|
| 727 | + [ |
|
| 728 | + $itemSource, |
|
| 729 | + $itemType, |
|
| 730 | + $shareWith, |
|
| 731 | + $sharedBy, |
|
| 732 | + $uidOwner, |
|
| 733 | + $permissions, |
|
| 734 | + $token, |
|
| 735 | + $password, |
|
| 736 | + $passwordExpirationTime, |
|
| 737 | + $sendPasswordByTalk, |
|
| 738 | + $hideDownload, |
|
| 739 | + $label, |
|
| 740 | + $expiration |
|
| 741 | + ] |
|
| 742 | + ); |
|
| 743 | + |
|
| 744 | + $qb = $this->connection->getQueryBuilder(); |
|
| 745 | + $qb->select('*') |
|
| 746 | + ->from('share') |
|
| 747 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 748 | + |
|
| 749 | + $qResult = $qb->executeQuery(); |
|
| 750 | + $result = $qResult->fetchAllAssociative(); |
|
| 751 | + $qResult->closeCursor(); |
|
| 752 | + |
|
| 753 | + $this->assertSame(1, count($result)); |
|
| 754 | + |
|
| 755 | + $this->assertSame($itemSource, (int)$result[0]['item_source']); |
|
| 756 | + $this->assertSame($itemType, $result[0]['item_type']); |
|
| 757 | + $this->assertSame($shareWith, $result[0]['share_with']); |
|
| 758 | + $this->assertSame($sharedBy, $result[0]['uid_initiator']); |
|
| 759 | + $this->assertSame($uidOwner, $result[0]['uid_owner']); |
|
| 760 | + $this->assertSame($permissions, (int)$result[0]['permissions']); |
|
| 761 | + $this->assertSame($token, $result[0]['token']); |
|
| 762 | + $this->assertSame($password, $result[0]['password']); |
|
| 763 | + $this->assertSame($passwordExpirationTime->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['password_expiration_time'])->getTimestamp()); |
|
| 764 | + $this->assertSame($sendPasswordByTalk, (bool)$result[0]['password_by_talk']); |
|
| 765 | + $this->assertSame($hideDownload, (bool)$result[0]['hide_download']); |
|
| 766 | + $this->assertSame($label, $result[0]['label']); |
|
| 767 | + $this->assertSame($expiration->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['expiration'])->getTimestamp()); |
|
| 768 | + } |
|
| 769 | + |
|
| 770 | + public function testUpdate(): void { |
|
| 771 | + $itemSource = 11; |
|
| 772 | + $itemType = 'file'; |
|
| 773 | + $shareWith = '[email protected]'; |
|
| 774 | + $sharedBy = 'user1'; |
|
| 775 | + $uidOwner = 'user2'; |
|
| 776 | + $permissions = 1; |
|
| 777 | + $token = 'token'; |
|
| 778 | + $note = 'personal note'; |
|
| 779 | + |
|
| 780 | + |
|
| 781 | + $instance = $this->getInstance(); |
|
| 782 | + |
|
| 783 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note); |
|
| 784 | + |
|
| 785 | + $this->share->expects($this->once())->method('getPermissions')->willReturn($permissions + 1); |
|
| 786 | + $this->share->expects($this->once())->method('getShareOwner')->willReturn($uidOwner); |
|
| 787 | + $this->share->expects($this->once())->method('getSharedBy')->willReturn($sharedBy); |
|
| 788 | + $this->share->expects($this->any())->method('getNote')->willReturn($note); |
|
| 789 | + $this->share->expects($this->atLeastOnce())->method('getId')->willReturn($id); |
|
| 790 | + $this->share->expects($this->atLeastOnce())->method('getNodeId')->willReturn($itemSource); |
|
| 791 | + $this->share->expects($this->once())->method('getSharedWith')->willReturn($shareWith); |
|
| 792 | + |
|
| 793 | + $this->assertSame($this->share, |
|
| 794 | + $instance->update($this->share) |
|
| 795 | + ); |
|
| 796 | + |
|
| 797 | + $qb = $this->connection->getQueryBuilder(); |
|
| 798 | + $qb->select('*') |
|
| 799 | + ->from('share') |
|
| 800 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 801 | + |
|
| 802 | + $qResult = $qb->executeQuery(); |
|
| 803 | + $result = $qResult->fetchAllAssociative(); |
|
| 804 | + $qResult->closeCursor(); |
|
| 805 | + |
|
| 806 | + $this->assertSame(1, count($result)); |
|
| 807 | + |
|
| 808 | + $this->assertSame($itemSource, (int)$result[0]['item_source']); |
|
| 809 | + $this->assertSame($itemType, $result[0]['item_type']); |
|
| 810 | + $this->assertSame($shareWith, $result[0]['share_with']); |
|
| 811 | + $this->assertSame($sharedBy, $result[0]['uid_initiator']); |
|
| 812 | + $this->assertSame($uidOwner, $result[0]['uid_owner']); |
|
| 813 | + $this->assertSame($permissions + 1, (int)$result[0]['permissions']); |
|
| 814 | + $this->assertSame($token, $result[0]['token']); |
|
| 815 | + $this->assertSame($note, $result[0]['note']); |
|
| 816 | + } |
|
| 817 | + |
|
| 818 | + public static function dataUpdateSendPassword(): array { |
|
| 819 | + return [ |
|
| 820 | + ['password', 'hashed', 'hashed new', false, false, true], |
|
| 821 | + ['', 'hashed', 'hashed new', false, false, false], |
|
| 822 | + [null, 'hashed', 'hashed new', false, false, false], |
|
| 823 | + ['password', 'hashed', 'hashed', false, false, false], |
|
| 824 | + ['password', 'hashed', 'hashed new', false, true, false], |
|
| 825 | + ['password', 'hashed', 'hashed new', true, false, true], |
|
| 826 | + ['password', 'hashed', 'hashed', true, false, true], |
|
| 827 | + ]; |
|
| 828 | + } |
|
| 829 | + |
|
| 830 | + #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateSendPassword')] |
|
| 831 | + public function testUpdateSendPassword(?string $plainTextPassword, string $originalPassword, string $newPassword, bool $originalSendPasswordByTalk, bool $newSendPasswordByTalk, bool $sendMail): void { |
|
| 832 | + $node = $this->createMock(File::class); |
|
| 833 | + $node->expects($this->any())->method('getName')->willReturn('filename'); |
|
| 834 | + |
|
| 835 | + $this->settingsManager->method('sendPasswordByMail')->willReturn(true); |
|
| 836 | + |
|
| 837 | + $originalShare = $this->createMock(IShare::class); |
|
| 838 | + $originalShare->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 839 | + $originalShare->expects($this->any())->method('getNode')->willReturn($node); |
|
| 840 | + $originalShare->expects($this->any())->method('getId')->willReturn(42); |
|
| 841 | + $originalShare->expects($this->any())->method('getPassword')->willReturn($originalPassword); |
|
| 842 | + $originalShare->expects($this->any())->method('getSendPasswordByTalk')->willReturn($originalSendPasswordByTalk); |
|
| 843 | + |
|
| 844 | + $share = $this->createMock(IShare::class); |
|
| 845 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 846 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 847 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 848 | + $share->expects($this->any())->method('getPassword')->willReturn($newPassword); |
|
| 849 | + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn($newSendPasswordByTalk); |
|
| 850 | + |
|
| 851 | + if ($sendMail) { |
|
| 852 | + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ |
|
| 853 | + 'filename' => 'filename', |
|
| 854 | + 'password' => $plainTextPassword, |
|
| 855 | + 'initiator' => null, |
|
| 856 | + 'initiatorEmail' => null, |
|
| 857 | + 'shareWith' => '[email protected]', |
|
| 858 | + ]); |
|
| 859 | + $this->mailer->expects($this->once())->method('send'); |
|
| 860 | + } else { |
|
| 861 | + $this->mailer->expects($this->never())->method('send'); |
|
| 862 | + } |
|
| 863 | + |
|
| 864 | + $instance = $this->getInstance(['getShareById', 'createPasswordSendActivity']); |
|
| 865 | + $instance->expects($this->once())->method('getShareById')->willReturn($originalShare); |
|
| 866 | + |
|
| 867 | + $this->assertSame($share, |
|
| 868 | + $instance->update($share, $plainTextPassword) |
|
| 869 | + ); |
|
| 870 | + } |
|
| 871 | + |
|
| 872 | + public function testDelete(): void { |
|
| 873 | + $instance = $this->getInstance(['removeShareFromTable', 'createShareActivity']); |
|
| 874 | + $this->share->expects($this->once())->method('getId')->willReturn(42); |
|
| 875 | + $instance->expects($this->once())->method('removeShareFromTable')->with(42); |
|
| 876 | + $instance->expects($this->once())->method('createShareActivity')->with($this->share, 'unshare'); |
|
| 877 | + $instance->delete($this->share); |
|
| 878 | + } |
|
| 879 | + |
|
| 880 | + public function testGetShareById(): void { |
|
| 881 | + $instance = $this->getInstance(['createShareObject']); |
|
| 882 | + |
|
| 883 | + $itemSource = 11; |
|
| 884 | + $itemType = 'file'; |
|
| 885 | + $shareWith = '[email protected]'; |
|
| 886 | + $sharedBy = 'user1'; |
|
| 887 | + $uidOwner = 'user2'; |
|
| 888 | + $permissions = 1; |
|
| 889 | + $token = 'token'; |
|
| 890 | + |
|
| 891 | + $this->createDummyShare($itemType, $itemSource, $shareWith, 'user1wrong', 'user2wrong', $permissions, $token); |
|
| 892 | + $id2 = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 893 | + |
|
| 894 | + $instance->expects($this->once())->method('createShareObject') |
|
| 895 | + ->willReturnCallback( |
|
| 896 | + function ($data) use ($uidOwner, $sharedBy, $id2) { |
|
| 897 | + $this->assertSame($uidOwner, $data['uid_owner']); |
|
| 898 | + $this->assertSame($sharedBy, $data['uid_initiator']); |
|
| 899 | + $this->assertSame($id2, (int)$data['id']); |
|
| 900 | + return $this->share; |
|
| 901 | + } |
|
| 902 | + ); |
|
| 903 | + |
|
| 904 | + $result = $instance->getShareById($id2); |
|
| 905 | + |
|
| 906 | + $this->assertInstanceOf('OCP\Share\IShare', $result); |
|
| 907 | + } |
|
| 908 | + |
|
| 909 | + |
|
| 910 | + public function testGetShareByIdFailed(): void { |
|
| 911 | + $this->expectException(ShareNotFound::class); |
|
| 912 | + |
|
| 913 | + $instance = $this->getInstance(['createShareObject']); |
|
| 914 | + |
|
| 915 | + $itemSource = 11; |
|
| 916 | + $itemType = 'file'; |
|
| 917 | + $shareWith = '[email protected]'; |
|
| 918 | + $sharedBy = 'user1'; |
|
| 919 | + $uidOwner = 'user2'; |
|
| 920 | + $permissions = 1; |
|
| 921 | + $token = 'token'; |
|
| 922 | + |
|
| 923 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 924 | + |
|
| 925 | + $instance->getShareById($id + 1); |
|
| 926 | + } |
|
| 927 | + |
|
| 928 | + public function testGetShareByPath(): void { |
|
| 929 | + $itemSource = 11; |
|
| 930 | + $itemType = 'file'; |
|
| 931 | + $shareWith = '[email protected]'; |
|
| 932 | + $sharedBy = 'user1'; |
|
| 933 | + $uidOwner = 'user2'; |
|
| 934 | + $permissions = 1; |
|
| 935 | + $token = 'token'; |
|
| 936 | + |
|
| 937 | + $node = $this->createMock(Node::class); |
|
| 938 | + $node->expects($this->once())->method('getId')->willReturn($itemSource); |
|
| 939 | + |
|
| 940 | + |
|
| 941 | + $instance = $this->getInstance(['createShareObject']); |
|
| 942 | + |
|
| 943 | + $this->createDummyShare($itemType, 111, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 944 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 945 | + |
|
| 946 | + $instance->expects($this->once())->method('createShareObject') |
|
| 947 | + ->willReturnCallback( |
|
| 948 | + function ($data) use ($uidOwner, $sharedBy, $id) { |
|
| 949 | + $this->assertSame($uidOwner, $data['uid_owner']); |
|
| 950 | + $this->assertSame($sharedBy, $data['uid_initiator']); |
|
| 951 | + $this->assertSame($id, (int)$data['id']); |
|
| 952 | + return $this->share; |
|
| 953 | + } |
|
| 954 | + ); |
|
| 955 | + |
|
| 956 | + $result = $instance->getSharesByPath($node); |
|
| 957 | + |
|
| 958 | + $this->assertTrue(is_array($result)); |
|
| 959 | + $this->assertSame(1, count($result)); |
|
| 960 | + $this->assertInstanceOf('OCP\Share\IShare', $result[0]); |
|
| 961 | + } |
|
| 962 | + |
|
| 963 | + public function testGetShareByToken(): void { |
|
| 964 | + $itemSource = 11; |
|
| 965 | + $itemType = 'file'; |
|
| 966 | + $shareWith = '[email protected]'; |
|
| 967 | + $sharedBy = 'user1'; |
|
| 968 | + $uidOwner = 'user2'; |
|
| 969 | + $permissions = 1; |
|
| 970 | + $token = 'token'; |
|
| 971 | 971 | |
| 972 | - $instance = $this->getInstance(['createShareObject']); |
|
| 972 | + $instance = $this->getInstance(['createShareObject']); |
|
| 973 | 973 | |
| 974 | - $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 975 | - $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, '', IShare::TYPE_LINK); |
|
| 974 | + $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 975 | + $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, '', IShare::TYPE_LINK); |
|
| 976 | 976 | |
| 977 | - $this->assertTrue($idMail !== $idPublic); |
|
| 977 | + $this->assertTrue($idMail !== $idPublic); |
|
| 978 | 978 | |
| 979 | - $instance->expects($this->once())->method('createShareObject') |
|
| 980 | - ->willReturnCallback( |
|
| 981 | - function ($data) use ($idMail) { |
|
| 982 | - $this->assertSame($idMail, (int)$data['id']); |
|
| 983 | - return $this->share; |
|
| 984 | - } |
|
| 985 | - ); |
|
| 979 | + $instance->expects($this->once())->method('createShareObject') |
|
| 980 | + ->willReturnCallback( |
|
| 981 | + function ($data) use ($idMail) { |
|
| 982 | + $this->assertSame($idMail, (int)$data['id']); |
|
| 983 | + return $this->share; |
|
| 984 | + } |
|
| 985 | + ); |
|
| 986 | 986 | |
| 987 | - $result = $instance->getShareByToken('token'); |
|
| 987 | + $result = $instance->getShareByToken('token'); |
|
| 988 | 988 | |
| 989 | - $this->assertInstanceOf('OCP\Share\IShare', $result); |
|
| 990 | - } |
|
| 989 | + $this->assertInstanceOf('OCP\Share\IShare', $result); |
|
| 990 | + } |
|
| 991 | 991 | |
| 992 | 992 | |
| 993 | - public function testGetShareByTokenFailed(): void { |
|
| 994 | - $this->expectException(ShareNotFound::class); |
|
| 993 | + public function testGetShareByTokenFailed(): void { |
|
| 994 | + $this->expectException(ShareNotFound::class); |
|
| 995 | 995 | |
| 996 | 996 | |
| 997 | - $itemSource = 11; |
|
| 998 | - $itemType = 'file'; |
|
| 999 | - $shareWith = '[email protected]'; |
|
| 1000 | - $sharedBy = 'user1'; |
|
| 1001 | - $uidOwner = 'user2'; |
|
| 1002 | - $permissions = 1; |
|
| 1003 | - $token = 'token'; |
|
| 997 | + $itemSource = 11; |
|
| 998 | + $itemType = 'file'; |
|
| 999 | + $shareWith = '[email protected]'; |
|
| 1000 | + $sharedBy = 'user1'; |
|
| 1001 | + $uidOwner = 'user2'; |
|
| 1002 | + $permissions = 1; |
|
| 1003 | + $token = 'token'; |
|
| 1004 | 1004 | |
| 1005 | - $instance = $this->getInstance(['createShareObject']); |
|
| 1005 | + $instance = $this->getInstance(['createShareObject']); |
|
| 1006 | 1006 | |
| 1007 | - $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1008 | - $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, 'token2', '', IShare::TYPE_LINK); |
|
| 1007 | + $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1008 | + $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, 'token2', '', IShare::TYPE_LINK); |
|
| 1009 | 1009 | |
| 1010 | - $this->assertTrue($idMail !== $idPublic); |
|
| 1010 | + $this->assertTrue($idMail !== $idPublic); |
|
| 1011 | 1011 | |
| 1012 | - $this->assertInstanceOf('OCP\Share\IShare', |
|
| 1013 | - $instance->getShareByToken('token2') |
|
| 1014 | - ); |
|
| 1015 | - } |
|
| 1012 | + $this->assertInstanceOf('OCP\Share\IShare', |
|
| 1013 | + $instance->getShareByToken('token2') |
|
| 1014 | + ); |
|
| 1015 | + } |
|
| 1016 | 1016 | |
| 1017 | - public function testRemoveShareFromTable(): void { |
|
| 1018 | - $itemSource = 11; |
|
| 1019 | - $itemType = 'file'; |
|
| 1020 | - $shareWith = '[email protected]'; |
|
| 1021 | - $sharedBy = 'user1'; |
|
| 1022 | - $uidOwner = 'user2'; |
|
| 1023 | - $permissions = 1; |
|
| 1024 | - $token = 'token'; |
|
| 1017 | + public function testRemoveShareFromTable(): void { |
|
| 1018 | + $itemSource = 11; |
|
| 1019 | + $itemType = 'file'; |
|
| 1020 | + $shareWith = '[email protected]'; |
|
| 1021 | + $sharedBy = 'user1'; |
|
| 1022 | + $uidOwner = 'user2'; |
|
| 1023 | + $permissions = 1; |
|
| 1024 | + $token = 'token'; |
|
| 1025 | 1025 | |
| 1026 | - $instance = $this->getInstance(); |
|
| 1026 | + $instance = $this->getInstance(); |
|
| 1027 | 1027 | |
| 1028 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1028 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1029 | 1029 | |
| 1030 | - $query = $this->connection->getQueryBuilder(); |
|
| 1031 | - $query->select('*')->from('share') |
|
| 1032 | - ->where($query->expr()->eq('id', $query->createNamedParameter($id))); |
|
| 1030 | + $query = $this->connection->getQueryBuilder(); |
|
| 1031 | + $query->select('*')->from('share') |
|
| 1032 | + ->where($query->expr()->eq('id', $query->createNamedParameter($id))); |
|
| 1033 | 1033 | |
| 1034 | - $result = $query->executeQuery(); |
|
| 1035 | - $before = $result->fetchAllAssociative(); |
|
| 1036 | - $result->closeCursor(); |
|
| 1034 | + $result = $query->executeQuery(); |
|
| 1035 | + $before = $result->fetchAllAssociative(); |
|
| 1036 | + $result->closeCursor(); |
|
| 1037 | 1037 | |
| 1038 | - $this->assertTrue(is_array($before)); |
|
| 1039 | - $this->assertSame(1, count($before)); |
|
| 1038 | + $this->assertTrue(is_array($before)); |
|
| 1039 | + $this->assertSame(1, count($before)); |
|
| 1040 | 1040 | |
| 1041 | - $this->invokePrivate($instance, 'removeShareFromTable', [$id]); |
|
| 1041 | + $this->invokePrivate($instance, 'removeShareFromTable', [$id]); |
|
| 1042 | 1042 | |
| 1043 | - $query = $this->connection->getQueryBuilder(); |
|
| 1044 | - $query->select('*')->from('share') |
|
| 1045 | - ->where($query->expr()->eq('id', $query->createNamedParameter($id))); |
|
| 1043 | + $query = $this->connection->getQueryBuilder(); |
|
| 1044 | + $query->select('*')->from('share') |
|
| 1045 | + ->where($query->expr()->eq('id', $query->createNamedParameter($id))); |
|
| 1046 | 1046 | |
| 1047 | - $result = $query->executeQuery(); |
|
| 1048 | - $after = $result->fetchAllAssociative(); |
|
| 1049 | - $result->closeCursor(); |
|
| 1047 | + $result = $query->executeQuery(); |
|
| 1048 | + $after = $result->fetchAllAssociative(); |
|
| 1049 | + $result->closeCursor(); |
|
| 1050 | 1050 | |
| 1051 | - $this->assertTrue(is_array($after)); |
|
| 1052 | - $this->assertEmpty($after); |
|
| 1053 | - } |
|
| 1051 | + $this->assertTrue(is_array($after)); |
|
| 1052 | + $this->assertEmpty($after); |
|
| 1053 | + } |
|
| 1054 | 1054 | |
| 1055 | - public function testUserDeleted(): void { |
|
| 1056 | - $itemSource = 11; |
|
| 1057 | - $itemType = 'file'; |
|
| 1058 | - $shareWith = '[email protected]'; |
|
| 1059 | - $sharedBy = 'user1'; |
|
| 1060 | - $uidOwner = 'user2'; |
|
| 1061 | - $permissions = 1; |
|
| 1062 | - $token = 'token'; |
|
| 1055 | + public function testUserDeleted(): void { |
|
| 1056 | + $itemSource = 11; |
|
| 1057 | + $itemType = 'file'; |
|
| 1058 | + $shareWith = '[email protected]'; |
|
| 1059 | + $sharedBy = 'user1'; |
|
| 1060 | + $uidOwner = 'user2'; |
|
| 1061 | + $permissions = 1; |
|
| 1062 | + $token = 'token'; |
|
| 1063 | 1063 | |
| 1064 | - $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1065 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, 'user2Wrong', $permissions, $token); |
|
| 1064 | + $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1065 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, 'user2Wrong', $permissions, $token); |
|
| 1066 | 1066 | |
| 1067 | - $query = $this->connection->getQueryBuilder(); |
|
| 1068 | - $query->select('*')->from('share'); |
|
| 1067 | + $query = $this->connection->getQueryBuilder(); |
|
| 1068 | + $query->select('*')->from('share'); |
|
| 1069 | 1069 | |
| 1070 | - $result = $query->executeQuery(); |
|
| 1071 | - $before = $result->fetchAllAssociative(); |
|
| 1072 | - $result->closeCursor(); |
|
| 1070 | + $result = $query->executeQuery(); |
|
| 1071 | + $before = $result->fetchAllAssociative(); |
|
| 1072 | + $result->closeCursor(); |
|
| 1073 | 1073 | |
| 1074 | - $this->assertTrue(is_array($before)); |
|
| 1075 | - $this->assertSame(2, count($before)); |
|
| 1074 | + $this->assertTrue(is_array($before)); |
|
| 1075 | + $this->assertSame(2, count($before)); |
|
| 1076 | 1076 | |
| 1077 | 1077 | |
| 1078 | - $instance = $this->getInstance(); |
|
| 1078 | + $instance = $this->getInstance(); |
|
| 1079 | 1079 | |
| 1080 | - $instance->userDeleted($uidOwner, IShare::TYPE_EMAIL); |
|
| 1080 | + $instance->userDeleted($uidOwner, IShare::TYPE_EMAIL); |
|
| 1081 | 1081 | |
| 1082 | - $query = $this->connection->getQueryBuilder(); |
|
| 1083 | - $query->select('*')->from('share'); |
|
| 1082 | + $query = $this->connection->getQueryBuilder(); |
|
| 1083 | + $query->select('*')->from('share'); |
|
| 1084 | 1084 | |
| 1085 | - $result = $query->executeQuery(); |
|
| 1086 | - $after = $result->fetchAllAssociative(); |
|
| 1087 | - $result->closeCursor(); |
|
| 1085 | + $result = $query->executeQuery(); |
|
| 1086 | + $after = $result->fetchAllAssociative(); |
|
| 1087 | + $result->closeCursor(); |
|
| 1088 | 1088 | |
| 1089 | - $this->assertTrue(is_array($after)); |
|
| 1090 | - $this->assertSame(1, count($after)); |
|
| 1091 | - $this->assertSame($id, (int)$after[0]['id']); |
|
| 1092 | - } |
|
| 1089 | + $this->assertTrue(is_array($after)); |
|
| 1090 | + $this->assertSame(1, count($after)); |
|
| 1091 | + $this->assertSame($id, (int)$after[0]['id']); |
|
| 1092 | + } |
|
| 1093 | 1093 | |
| 1094 | - public function testGetRawShare(): void { |
|
| 1095 | - $itemSource = 11; |
|
| 1096 | - $itemType = 'file'; |
|
| 1097 | - $shareWith = '[email protected]'; |
|
| 1098 | - $sharedBy = 'user1'; |
|
| 1099 | - $uidOwner = 'user2'; |
|
| 1100 | - $permissions = 1; |
|
| 1101 | - $token = 'token'; |
|
| 1094 | + public function testGetRawShare(): void { |
|
| 1095 | + $itemSource = 11; |
|
| 1096 | + $itemType = 'file'; |
|
| 1097 | + $shareWith = '[email protected]'; |
|
| 1098 | + $sharedBy = 'user1'; |
|
| 1099 | + $uidOwner = 'user2'; |
|
| 1100 | + $permissions = 1; |
|
| 1101 | + $token = 'token'; |
|
| 1102 | 1102 | |
| 1103 | - $instance = $this->getInstance(); |
|
| 1103 | + $instance = $this->getInstance(); |
|
| 1104 | 1104 | |
| 1105 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1105 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1106 | 1106 | |
| 1107 | - $result = $this->invokePrivate($instance, 'getRawShare', [$id]); |
|
| 1108 | - |
|
| 1109 | - $this->assertTrue(is_array($result)); |
|
| 1110 | - $this->assertSame($itemSource, (int)$result['item_source']); |
|
| 1111 | - $this->assertSame($itemType, $result['item_type']); |
|
| 1112 | - $this->assertSame($shareWith, $result['share_with']); |
|
| 1113 | - $this->assertSame($sharedBy, $result['uid_initiator']); |
|
| 1114 | - $this->assertSame($uidOwner, $result['uid_owner']); |
|
| 1115 | - $this->assertSame($permissions, (int)$result['permissions']); |
|
| 1116 | - $this->assertSame($token, $result['token']); |
|
| 1117 | - } |
|
| 1107 | + $result = $this->invokePrivate($instance, 'getRawShare', [$id]); |
|
| 1108 | + |
|
| 1109 | + $this->assertTrue(is_array($result)); |
|
| 1110 | + $this->assertSame($itemSource, (int)$result['item_source']); |
|
| 1111 | + $this->assertSame($itemType, $result['item_type']); |
|
| 1112 | + $this->assertSame($shareWith, $result['share_with']); |
|
| 1113 | + $this->assertSame($sharedBy, $result['uid_initiator']); |
|
| 1114 | + $this->assertSame($uidOwner, $result['uid_owner']); |
|
| 1115 | + $this->assertSame($permissions, (int)$result['permissions']); |
|
| 1116 | + $this->assertSame($token, $result['token']); |
|
| 1117 | + } |
|
| 1118 | 1118 | |
| 1119 | 1119 | |
| 1120 | - public function testGetRawShareFailed(): void { |
|
| 1121 | - $this->expectException(ShareNotFound::class); |
|
| 1120 | + public function testGetRawShareFailed(): void { |
|
| 1121 | + $this->expectException(ShareNotFound::class); |
|
| 1122 | 1122 | |
| 1123 | - $itemSource = 11; |
|
| 1124 | - $itemType = 'file'; |
|
| 1125 | - $shareWith = '[email protected]'; |
|
| 1126 | - $sharedBy = 'user1'; |
|
| 1127 | - $uidOwner = 'user2'; |
|
| 1128 | - $permissions = 1; |
|
| 1129 | - $token = 'token'; |
|
| 1130 | - |
|
| 1131 | - $instance = $this->getInstance(); |
|
| 1123 | + $itemSource = 11; |
|
| 1124 | + $itemType = 'file'; |
|
| 1125 | + $shareWith = '[email protected]'; |
|
| 1126 | + $sharedBy = 'user1'; |
|
| 1127 | + $uidOwner = 'user2'; |
|
| 1128 | + $permissions = 1; |
|
| 1129 | + $token = 'token'; |
|
| 1130 | + |
|
| 1131 | + $instance = $this->getInstance(); |
|
| 1132 | 1132 | |
| 1133 | - $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1134 | - |
|
| 1135 | - $this->invokePrivate($instance, 'getRawShare', [$id + 1]); |
|
| 1136 | - } |
|
| 1133 | + $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token); |
|
| 1134 | + |
|
| 1135 | + $this->invokePrivate($instance, 'getRawShare', [$id + 1]); |
|
| 1136 | + } |
|
| 1137 | 1137 | |
| 1138 | - private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note = '', $shareType = IShare::TYPE_EMAIL) { |
|
| 1139 | - $qb = $this->connection->getQueryBuilder(); |
|
| 1140 | - $qb->insert('share') |
|
| 1141 | - ->setValue('share_type', $qb->createNamedParameter($shareType)) |
|
| 1142 | - ->setValue('item_type', $qb->createNamedParameter($itemType)) |
|
| 1143 | - ->setValue('item_source', $qb->createNamedParameter($itemSource)) |
|
| 1144 | - ->setValue('file_source', $qb->createNamedParameter($itemSource)) |
|
| 1145 | - ->setValue('share_with', $qb->createNamedParameter($shareWith)) |
|
| 1146 | - ->setValue('uid_owner', $qb->createNamedParameter($uidOwner)) |
|
| 1147 | - ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy)) |
|
| 1148 | - ->setValue('permissions', $qb->createNamedParameter($permissions)) |
|
| 1149 | - ->setValue('token', $qb->createNamedParameter($token)) |
|
| 1150 | - ->setValue('note', $qb->createNamedParameter($note)) |
|
| 1151 | - ->setValue('stime', $qb->createNamedParameter(time())); |
|
| 1152 | - |
|
| 1153 | - /* |
|
| 1138 | + private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note = '', $shareType = IShare::TYPE_EMAIL) { |
|
| 1139 | + $qb = $this->connection->getQueryBuilder(); |
|
| 1140 | + $qb->insert('share') |
|
| 1141 | + ->setValue('share_type', $qb->createNamedParameter($shareType)) |
|
| 1142 | + ->setValue('item_type', $qb->createNamedParameter($itemType)) |
|
| 1143 | + ->setValue('item_source', $qb->createNamedParameter($itemSource)) |
|
| 1144 | + ->setValue('file_source', $qb->createNamedParameter($itemSource)) |
|
| 1145 | + ->setValue('share_with', $qb->createNamedParameter($shareWith)) |
|
| 1146 | + ->setValue('uid_owner', $qb->createNamedParameter($uidOwner)) |
|
| 1147 | + ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy)) |
|
| 1148 | + ->setValue('permissions', $qb->createNamedParameter($permissions)) |
|
| 1149 | + ->setValue('token', $qb->createNamedParameter($token)) |
|
| 1150 | + ->setValue('note', $qb->createNamedParameter($note)) |
|
| 1151 | + ->setValue('stime', $qb->createNamedParameter(time())); |
|
| 1152 | + |
|
| 1153 | + /* |
|
| 1154 | 1154 | * Added to fix https://github.com/owncloud/core/issues/22215 |
| 1155 | 1155 | * Can be removed once we get rid of ajax/share.php |
| 1156 | 1156 | */ |
| 1157 | - $qb->setValue('file_target', $qb->createNamedParameter('')); |
|
| 1158 | - |
|
| 1159 | - $qb->executeStatement(); |
|
| 1160 | - $id = $qb->getLastInsertId(); |
|
| 1161 | - |
|
| 1162 | - return (int)$id; |
|
| 1163 | - } |
|
| 1164 | - |
|
| 1165 | - public function testGetSharesInFolder(): void { |
|
| 1166 | - $userManager = Server::get(IUserManager::class); |
|
| 1167 | - $rootFolder = Server::get(IRootFolder::class); |
|
| 1168 | - |
|
| 1169 | - $this->shareManager->expects($this->any()) |
|
| 1170 | - ->method('newShare') |
|
| 1171 | - ->willReturn(new Share($rootFolder, $userManager)); |
|
| 1172 | - |
|
| 1173 | - $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']); |
|
| 1174 | - |
|
| 1175 | - $u1 = $userManager->createUser('testFed', md5((string)time())); |
|
| 1176 | - $u2 = $userManager->createUser('testFed2', md5((string)time())); |
|
| 1177 | - |
|
| 1178 | - $folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); |
|
| 1179 | - $file1 = $folder1->newFile('bar1'); |
|
| 1180 | - $file2 = $folder1->newFile('bar2'); |
|
| 1181 | - |
|
| 1182 | - $share1 = $this->shareManager->newShare(); |
|
| 1183 | - $share1->setSharedWith('[email protected]') |
|
| 1184 | - ->setSharedBy($u1->getUID()) |
|
| 1185 | - ->setShareOwner($u1->getUID()) |
|
| 1186 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 1187 | - ->setNode($file1); |
|
| 1188 | - $provider->create($share1); |
|
| 1189 | - |
|
| 1190 | - $share2 = $this->shareManager->newShare(); |
|
| 1191 | - $share2->setSharedWith('[email protected]') |
|
| 1192 | - ->setSharedBy($u2->getUID()) |
|
| 1193 | - ->setShareOwner($u1->getUID()) |
|
| 1194 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 1195 | - ->setNode($file2); |
|
| 1196 | - $provider->create($share2); |
|
| 1197 | - |
|
| 1198 | - $result = $provider->getSharesInFolder($u1->getUID(), $folder1, false); |
|
| 1199 | - $this->assertCount(1, $result); |
|
| 1200 | - $this->assertCount(1, $result[$file1->getId()]); |
|
| 1201 | - |
|
| 1202 | - $result = $provider->getSharesInFolder($u1->getUID(), $folder1, true); |
|
| 1203 | - $this->assertCount(2, $result); |
|
| 1204 | - $this->assertCount(1, $result[$file1->getId()]); |
|
| 1205 | - $this->assertCount(1, $result[$file2->getId()]); |
|
| 1206 | - |
|
| 1207 | - $u1->delete(); |
|
| 1208 | - $u2->delete(); |
|
| 1209 | - } |
|
| 1210 | - |
|
| 1211 | - public function testGetAccessList(): void { |
|
| 1212 | - $userManager = Server::get(IUserManager::class); |
|
| 1213 | - $rootFolder = Server::get(IRootFolder::class); |
|
| 1214 | - |
|
| 1215 | - $this->shareManager->expects($this->any()) |
|
| 1216 | - ->method('newShare') |
|
| 1217 | - ->willReturn(new Share($rootFolder, $userManager)); |
|
| 1218 | - |
|
| 1219 | - $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']); |
|
| 1220 | - |
|
| 1221 | - $u1 = $userManager->createUser('testFed', md5((string)time())); |
|
| 1222 | - $u2 = $userManager->createUser('testFed2', md5((string)time())); |
|
| 1223 | - |
|
| 1224 | - $folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); |
|
| 1225 | - |
|
| 1226 | - $accessList = $provider->getAccessList([$folder], true); |
|
| 1227 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1228 | - $this->assertFalse($accessList['public']); |
|
| 1229 | - $accessList = $provider->getAccessList([$folder], false); |
|
| 1230 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1231 | - $this->assertFalse($accessList['public']); |
|
| 1232 | - |
|
| 1233 | - $share1 = $this->shareManager->newShare(); |
|
| 1234 | - $share1->setSharedWith('[email protected]') |
|
| 1235 | - ->setSharedBy($u1->getUID()) |
|
| 1236 | - ->setShareOwner($u1->getUID()) |
|
| 1237 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 1238 | - ->setNode($folder); |
|
| 1239 | - $share1 = $provider->create($share1); |
|
| 1240 | - |
|
| 1241 | - $share2 = $this->shareManager->newShare(); |
|
| 1242 | - $share2->setSharedWith('[email protected]') |
|
| 1243 | - ->setSharedBy($u2->getUID()) |
|
| 1244 | - ->setShareOwner($u1->getUID()) |
|
| 1245 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 1246 | - ->setNode($folder); |
|
| 1247 | - $share2 = $provider->create($share2); |
|
| 1248 | - |
|
| 1249 | - $accessList = $provider->getAccessList([$folder], true); |
|
| 1250 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1251 | - $this->assertTrue($accessList['public']); |
|
| 1252 | - $accessList = $provider->getAccessList([$folder], false); |
|
| 1253 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1254 | - $this->assertTrue($accessList['public']); |
|
| 1255 | - |
|
| 1256 | - $provider->delete($share2); |
|
| 1257 | - |
|
| 1258 | - $accessList = $provider->getAccessList([$folder], true); |
|
| 1259 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1260 | - $this->assertTrue($accessList['public']); |
|
| 1261 | - $accessList = $provider->getAccessList([$folder], false); |
|
| 1262 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1263 | - $this->assertTrue($accessList['public']); |
|
| 1264 | - |
|
| 1265 | - $provider->delete($share1); |
|
| 1266 | - |
|
| 1267 | - $accessList = $provider->getAccessList([$folder], true); |
|
| 1268 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1269 | - $this->assertFalse($accessList['public']); |
|
| 1270 | - $accessList = $provider->getAccessList([$folder], false); |
|
| 1271 | - $this->assertArrayHasKey('public', $accessList); |
|
| 1272 | - $this->assertFalse($accessList['public']); |
|
| 1273 | - |
|
| 1274 | - $u1->delete(); |
|
| 1275 | - $u2->delete(); |
|
| 1276 | - } |
|
| 1277 | - |
|
| 1278 | - public function testSendMailNotificationWithSameUserAndUserEmail(): void { |
|
| 1279 | - $provider = $this->getInstance(); |
|
| 1280 | - $user = $this->createMock(IUser::class); |
|
| 1281 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1282 | - $this->userManager |
|
| 1283 | - ->expects($this->once()) |
|
| 1284 | - ->method('get') |
|
| 1285 | - ->with('OwnerUser') |
|
| 1286 | - ->willReturn($user); |
|
| 1287 | - $user |
|
| 1288 | - ->expects($this->once()) |
|
| 1289 | - ->method('getDisplayName') |
|
| 1290 | - ->willReturn('Mrs. Owner User'); |
|
| 1291 | - $message = $this->createMock(Message::class); |
|
| 1292 | - $this->mailer |
|
| 1293 | - ->expects($this->once()) |
|
| 1294 | - ->method('createMessage') |
|
| 1295 | - ->willReturn($message); |
|
| 1296 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1297 | - $this->mailer |
|
| 1298 | - ->expects($this->once()) |
|
| 1299 | - ->method('createEMailTemplate') |
|
| 1300 | - ->willReturn($template); |
|
| 1301 | - $template |
|
| 1302 | - ->expects($this->once()) |
|
| 1303 | - ->method('addHeader'); |
|
| 1304 | - $template |
|
| 1305 | - ->expects($this->once()) |
|
| 1306 | - ->method('addHeading') |
|
| 1307 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1308 | - $template |
|
| 1309 | - ->expects($this->once()) |
|
| 1310 | - ->method('addBodyButton') |
|
| 1311 | - ->with( |
|
| 1312 | - 'Open file.txt', |
|
| 1313 | - 'https://example.com/file.txt' |
|
| 1314 | - ); |
|
| 1315 | - $message |
|
| 1316 | - ->expects($this->once()) |
|
| 1317 | - ->method('setTo') |
|
| 1318 | - ->with(['[email protected]']); |
|
| 1319 | - $this->defaults |
|
| 1320 | - ->expects($this->once()) |
|
| 1321 | - ->method('getName') |
|
| 1322 | - ->willReturn('UnitTestCloud'); |
|
| 1323 | - $message |
|
| 1324 | - ->expects($this->once()) |
|
| 1325 | - ->method('setFrom') |
|
| 1326 | - ->with([ |
|
| 1327 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1328 | - ]); |
|
| 1329 | - $user |
|
| 1330 | - ->expects($this->once()) |
|
| 1331 | - ->method('getEMailAddress') |
|
| 1332 | - ->willReturn('[email protected]'); |
|
| 1333 | - $message |
|
| 1334 | - ->expects($this->once()) |
|
| 1335 | - ->method('setReplyTo') |
|
| 1336 | - ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1337 | - $this->defaults |
|
| 1338 | - ->expects($this->exactly(2)) |
|
| 1339 | - ->method('getSlogan') |
|
| 1340 | - ->willReturn('Testing like 1990'); |
|
| 1341 | - $template |
|
| 1342 | - ->expects($this->once()) |
|
| 1343 | - ->method('addFooter') |
|
| 1344 | - ->with('UnitTestCloud - Testing like 1990'); |
|
| 1345 | - $template |
|
| 1346 | - ->expects($this->once()) |
|
| 1347 | - ->method('setSubject') |
|
| 1348 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1349 | - $message |
|
| 1350 | - ->expects($this->once()) |
|
| 1351 | - ->method('useTemplate') |
|
| 1352 | - ->with($template); |
|
| 1353 | - |
|
| 1354 | - $this->mailer |
|
| 1355 | - ->expects($this->once()) |
|
| 1356 | - ->method('send') |
|
| 1357 | - ->with($message); |
|
| 1358 | - |
|
| 1359 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1360 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1361 | - ->willReturn('https://example.com/file.txt'); |
|
| 1362 | - |
|
| 1363 | - $node = $this->createMock(File::class); |
|
| 1364 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1365 | - |
|
| 1366 | - $share = $this->createMock(IShare::class); |
|
| 1367 | - $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1368 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1369 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1370 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1371 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1372 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1373 | - |
|
| 1374 | - self::invokePrivate( |
|
| 1375 | - $provider, |
|
| 1376 | - 'sendMailNotification', |
|
| 1377 | - [$share] |
|
| 1378 | - ); |
|
| 1379 | - } |
|
| 1380 | - |
|
| 1381 | - public function testSendMailNotificationWithSameUserAndUserEmailAndNote(): void { |
|
| 1382 | - $provider = $this->getInstance(); |
|
| 1383 | - $user = $this->createMock(IUser::class); |
|
| 1384 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1385 | - $this->userManager |
|
| 1386 | - ->expects($this->once()) |
|
| 1387 | - ->method('get') |
|
| 1388 | - ->with('OwnerUser') |
|
| 1389 | - ->willReturn($user); |
|
| 1390 | - $user |
|
| 1391 | - ->expects($this->once()) |
|
| 1392 | - ->method('getDisplayName') |
|
| 1393 | - ->willReturn('Mrs. Owner User'); |
|
| 1394 | - $message = $this->createMock(Message::class); |
|
| 1395 | - $this->mailer |
|
| 1396 | - ->expects($this->once()) |
|
| 1397 | - ->method('createMessage') |
|
| 1398 | - ->willReturn($message); |
|
| 1399 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1400 | - $this->mailer |
|
| 1401 | - ->expects($this->once()) |
|
| 1402 | - ->method('createEMailTemplate') |
|
| 1403 | - ->willReturn($template); |
|
| 1404 | - $template |
|
| 1405 | - ->expects($this->once()) |
|
| 1406 | - ->method('addHeader'); |
|
| 1407 | - $template |
|
| 1408 | - ->expects($this->once()) |
|
| 1409 | - ->method('addHeading') |
|
| 1410 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1411 | - |
|
| 1412 | - $this->urlGenerator->expects($this->once())->method('imagePath') |
|
| 1413 | - ->with('core', 'caldav/description.png') |
|
| 1414 | - ->willReturn('core/img/caldav/description.png'); |
|
| 1415 | - $this->urlGenerator->expects($this->once())->method('getAbsoluteURL') |
|
| 1416 | - ->with('core/img/caldav/description.png') |
|
| 1417 | - ->willReturn('https://example.com/core/img/caldav/description.png'); |
|
| 1418 | - $template |
|
| 1419 | - ->expects($this->once()) |
|
| 1420 | - ->method('addBodyListItem') |
|
| 1421 | - ->with( |
|
| 1422 | - 'This is a note to the recipient', |
|
| 1423 | - 'Note:', |
|
| 1424 | - 'https://example.com/core/img/caldav/description.png', |
|
| 1425 | - 'This is a note to the recipient' |
|
| 1426 | - ); |
|
| 1427 | - $template |
|
| 1428 | - ->expects($this->once()) |
|
| 1429 | - ->method('addBodyButton') |
|
| 1430 | - ->with( |
|
| 1431 | - 'Open file.txt', |
|
| 1432 | - 'https://example.com/file.txt' |
|
| 1433 | - ); |
|
| 1434 | - $message |
|
| 1435 | - ->expects($this->once()) |
|
| 1436 | - ->method('setTo') |
|
| 1437 | - ->with(['[email protected]']); |
|
| 1438 | - $this->defaults |
|
| 1439 | - ->expects($this->once()) |
|
| 1440 | - ->method('getName') |
|
| 1441 | - ->willReturn('UnitTestCloud'); |
|
| 1442 | - $message |
|
| 1443 | - ->expects($this->once()) |
|
| 1444 | - ->method('setFrom') |
|
| 1445 | - ->with([ |
|
| 1446 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1447 | - ]); |
|
| 1448 | - $user |
|
| 1449 | - ->expects($this->once()) |
|
| 1450 | - ->method('getEMailAddress') |
|
| 1451 | - ->willReturn('[email protected]'); |
|
| 1452 | - $message |
|
| 1453 | - ->expects($this->once()) |
|
| 1454 | - ->method('setReplyTo') |
|
| 1455 | - ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1456 | - $this->defaults |
|
| 1457 | - ->expects($this->exactly(2)) |
|
| 1458 | - ->method('getSlogan') |
|
| 1459 | - ->willReturn('Testing like 1990'); |
|
| 1460 | - $template |
|
| 1461 | - ->expects($this->once()) |
|
| 1462 | - ->method('addFooter') |
|
| 1463 | - ->with('UnitTestCloud - Testing like 1990'); |
|
| 1464 | - $template |
|
| 1465 | - ->expects($this->once()) |
|
| 1466 | - ->method('setSubject') |
|
| 1467 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1468 | - $message |
|
| 1469 | - ->expects($this->once()) |
|
| 1470 | - ->method('useTemplate') |
|
| 1471 | - ->with($template); |
|
| 1472 | - |
|
| 1473 | - $this->mailer |
|
| 1474 | - ->expects($this->once()) |
|
| 1475 | - ->method('send') |
|
| 1476 | - ->with($message); |
|
| 1477 | - |
|
| 1478 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1479 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1480 | - ->willReturn('https://example.com/file.txt'); |
|
| 1481 | - |
|
| 1482 | - $node = $this->createMock(File::class); |
|
| 1483 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1484 | - |
|
| 1485 | - $share = $this->createMock(IShare::class); |
|
| 1486 | - $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1487 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1488 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1489 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1490 | - $share->expects($this->any())->method('getNote')->willReturn('This is a note to the recipient'); |
|
| 1491 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1492 | - |
|
| 1493 | - self::invokePrivate( |
|
| 1494 | - $provider, |
|
| 1495 | - 'sendMailNotification', |
|
| 1496 | - [$share] |
|
| 1497 | - ); |
|
| 1498 | - } |
|
| 1499 | - |
|
| 1500 | - public function testSendMailNotificationWithSameUserAndUserEmailAndExpiration(): void { |
|
| 1501 | - $provider = $this->getInstance(); |
|
| 1502 | - $user = $this->createMock(IUser::class); |
|
| 1503 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1504 | - $this->userManager |
|
| 1505 | - ->expects($this->once()) |
|
| 1506 | - ->method('get') |
|
| 1507 | - ->with('OwnerUser') |
|
| 1508 | - ->willReturn($user); |
|
| 1509 | - $user |
|
| 1510 | - ->expects($this->once()) |
|
| 1511 | - ->method('getDisplayName') |
|
| 1512 | - ->willReturn('Mrs. Owner User'); |
|
| 1513 | - $message = $this->createMock(Message::class); |
|
| 1514 | - $this->mailer |
|
| 1515 | - ->expects($this->once()) |
|
| 1516 | - ->method('createMessage') |
|
| 1517 | - ->willReturn($message); |
|
| 1518 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1519 | - $this->mailer |
|
| 1520 | - ->expects($this->once()) |
|
| 1521 | - ->method('createEMailTemplate') |
|
| 1522 | - ->willReturn($template); |
|
| 1523 | - $template |
|
| 1524 | - ->expects($this->once()) |
|
| 1525 | - ->method('addHeader'); |
|
| 1526 | - $template |
|
| 1527 | - ->expects($this->once()) |
|
| 1528 | - ->method('addHeading') |
|
| 1529 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1530 | - |
|
| 1531 | - $expiration = new DateTime('2001-01-01'); |
|
| 1532 | - $this->l->expects($this->once()) |
|
| 1533 | - ->method('l') |
|
| 1534 | - ->with('date', $expiration, ['width' => 'medium']) |
|
| 1535 | - ->willReturn('2001-01-01'); |
|
| 1536 | - $this->urlGenerator->expects($this->once())->method('imagePath') |
|
| 1537 | - ->with('core', 'caldav/time.png') |
|
| 1538 | - ->willReturn('core/img/caldav/time.png'); |
|
| 1539 | - $this->urlGenerator->expects($this->once())->method('getAbsoluteURL') |
|
| 1540 | - ->with('core/img/caldav/time.png') |
|
| 1541 | - ->willReturn('https://example.com/core/img/caldav/time.png'); |
|
| 1542 | - $template |
|
| 1543 | - ->expects($this->once()) |
|
| 1544 | - ->method('addBodyListItem') |
|
| 1545 | - ->with( |
|
| 1546 | - 'This share is valid until 2001-01-01 at midnight', |
|
| 1547 | - 'Expiration:', |
|
| 1548 | - 'https://example.com/core/img/caldav/time.png', |
|
| 1549 | - ); |
|
| 1550 | - |
|
| 1551 | - $template |
|
| 1552 | - ->expects($this->once()) |
|
| 1553 | - ->method('addBodyButton') |
|
| 1554 | - ->with( |
|
| 1555 | - 'Open file.txt', |
|
| 1556 | - 'https://example.com/file.txt' |
|
| 1557 | - ); |
|
| 1558 | - $message |
|
| 1559 | - ->expects($this->once()) |
|
| 1560 | - ->method('setTo') |
|
| 1561 | - ->with(['[email protected]']); |
|
| 1562 | - $this->defaults |
|
| 1563 | - ->expects($this->once()) |
|
| 1564 | - ->method('getName') |
|
| 1565 | - ->willReturn('UnitTestCloud'); |
|
| 1566 | - $message |
|
| 1567 | - ->expects($this->once()) |
|
| 1568 | - ->method('setFrom') |
|
| 1569 | - ->with([ |
|
| 1570 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1571 | - ]); |
|
| 1572 | - $user |
|
| 1573 | - ->expects($this->once()) |
|
| 1574 | - ->method('getEMailAddress') |
|
| 1575 | - ->willReturn('[email protected]'); |
|
| 1576 | - $message |
|
| 1577 | - ->expects($this->once()) |
|
| 1578 | - ->method('setReplyTo') |
|
| 1579 | - ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1580 | - $this->defaults |
|
| 1581 | - ->expects($this->exactly(2)) |
|
| 1582 | - ->method('getSlogan') |
|
| 1583 | - ->willReturn('Testing like 1990'); |
|
| 1584 | - $template |
|
| 1585 | - ->expects($this->once()) |
|
| 1586 | - ->method('addFooter') |
|
| 1587 | - ->with('UnitTestCloud - Testing like 1990'); |
|
| 1588 | - $template |
|
| 1589 | - ->expects($this->once()) |
|
| 1590 | - ->method('setSubject') |
|
| 1591 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1592 | - $message |
|
| 1593 | - ->expects($this->once()) |
|
| 1594 | - ->method('useTemplate') |
|
| 1595 | - ->with($template); |
|
| 1596 | - |
|
| 1597 | - $this->mailer |
|
| 1598 | - ->expects($this->once()) |
|
| 1599 | - ->method('send') |
|
| 1600 | - ->with($message); |
|
| 1601 | - |
|
| 1602 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1603 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1604 | - ->willReturn('https://example.com/file.txt'); |
|
| 1605 | - |
|
| 1606 | - $node = $this->createMock(File::class); |
|
| 1607 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1608 | - |
|
| 1609 | - $share = $this->createMock(IShare::class); |
|
| 1610 | - $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1611 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1612 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1613 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1614 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1615 | - $share->expects($this->any())->method('getExpirationDate')->willReturn($expiration); |
|
| 1616 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1617 | - |
|
| 1618 | - self::invokePrivate( |
|
| 1619 | - $provider, |
|
| 1620 | - 'sendMailNotification', |
|
| 1621 | - [$share] |
|
| 1622 | - ); |
|
| 1623 | - } |
|
| 1624 | - |
|
| 1625 | - public function testSendMailNotificationWithDifferentUserAndNoUserEmail(): void { |
|
| 1626 | - $provider = $this->getInstance(); |
|
| 1627 | - $initiatorUser = $this->createMock(IUser::class); |
|
| 1628 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1629 | - $this->userManager |
|
| 1630 | - ->expects($this->once()) |
|
| 1631 | - ->method('get') |
|
| 1632 | - ->with('InitiatorUser') |
|
| 1633 | - ->willReturn($initiatorUser); |
|
| 1634 | - $initiatorUser |
|
| 1635 | - ->expects($this->once()) |
|
| 1636 | - ->method('getDisplayName') |
|
| 1637 | - ->willReturn('Mr. Initiator User'); |
|
| 1638 | - $message = $this->createMock(Message::class); |
|
| 1639 | - $this->mailer |
|
| 1640 | - ->expects($this->once()) |
|
| 1641 | - ->method('createMessage') |
|
| 1642 | - ->willReturn($message); |
|
| 1643 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1644 | - $this->mailer |
|
| 1645 | - ->expects($this->once()) |
|
| 1646 | - ->method('createEMailTemplate') |
|
| 1647 | - ->willReturn($template); |
|
| 1648 | - $template |
|
| 1649 | - ->expects($this->once()) |
|
| 1650 | - ->method('addHeader'); |
|
| 1651 | - $template |
|
| 1652 | - ->expects($this->once()) |
|
| 1653 | - ->method('addHeading') |
|
| 1654 | - ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1655 | - $template |
|
| 1656 | - ->expects($this->once()) |
|
| 1657 | - ->method('addBodyButton') |
|
| 1658 | - ->with( |
|
| 1659 | - 'Open file.txt', |
|
| 1660 | - 'https://example.com/file.txt' |
|
| 1661 | - ); |
|
| 1662 | - $message |
|
| 1663 | - ->expects($this->once()) |
|
| 1664 | - ->method('setTo') |
|
| 1665 | - ->with(['[email protected]']); |
|
| 1666 | - $this->defaults |
|
| 1667 | - ->expects($this->once()) |
|
| 1668 | - ->method('getName') |
|
| 1669 | - ->willReturn('UnitTestCloud'); |
|
| 1670 | - $message |
|
| 1671 | - ->expects($this->once()) |
|
| 1672 | - ->method('setFrom') |
|
| 1673 | - ->with([ |
|
| 1674 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'Mr. Initiator User via UnitTestCloud' |
|
| 1675 | - ]); |
|
| 1676 | - $message |
|
| 1677 | - ->expects($this->never()) |
|
| 1678 | - ->method('setReplyTo'); |
|
| 1679 | - $template |
|
| 1680 | - ->expects($this->once()) |
|
| 1681 | - ->method('addFooter') |
|
| 1682 | - ->with(''); |
|
| 1683 | - $template |
|
| 1684 | - ->expects($this->once()) |
|
| 1685 | - ->method('setSubject') |
|
| 1686 | - ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1687 | - $message |
|
| 1688 | - ->expects($this->once()) |
|
| 1689 | - ->method('useTemplate') |
|
| 1690 | - ->with($template); |
|
| 1691 | - |
|
| 1692 | - $this->mailer |
|
| 1693 | - ->expects($this->once()) |
|
| 1694 | - ->method('send') |
|
| 1695 | - ->with($message); |
|
| 1696 | - |
|
| 1697 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1698 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1699 | - ->willReturn('https://example.com/file.txt'); |
|
| 1700 | - |
|
| 1701 | - $node = $this->createMock(File::class); |
|
| 1702 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1703 | - |
|
| 1704 | - $share = $this->createMock(IShare::class); |
|
| 1705 | - $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser'); |
|
| 1706 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1707 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1708 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1709 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1710 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1711 | - |
|
| 1712 | - self::invokePrivate( |
|
| 1713 | - $provider, |
|
| 1714 | - 'sendMailNotification', |
|
| 1715 | - [$share] |
|
| 1716 | - ); |
|
| 1717 | - } |
|
| 1718 | - |
|
| 1719 | - public function testSendMailNotificationWithSameUserAndUserEmailAndReplyToDesactivate(): void { |
|
| 1720 | - $provider = $this->getInstance(); |
|
| 1721 | - $user = $this->createMock(IUser::class); |
|
| 1722 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false); |
|
| 1723 | - $this->userManager |
|
| 1724 | - ->expects($this->once()) |
|
| 1725 | - ->method('get') |
|
| 1726 | - ->with('OwnerUser') |
|
| 1727 | - ->willReturn($user); |
|
| 1728 | - $user |
|
| 1729 | - ->expects($this->once()) |
|
| 1730 | - ->method('getDisplayName') |
|
| 1731 | - ->willReturn('Mrs. Owner User'); |
|
| 1732 | - $message = $this->createMock(Message::class); |
|
| 1733 | - $this->mailer |
|
| 1734 | - ->expects($this->once()) |
|
| 1735 | - ->method('createMessage') |
|
| 1736 | - ->willReturn($message); |
|
| 1737 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1738 | - $this->mailer |
|
| 1739 | - ->expects($this->once()) |
|
| 1740 | - ->method('createEMailTemplate') |
|
| 1741 | - ->willReturn($template); |
|
| 1742 | - $template |
|
| 1743 | - ->expects($this->once()) |
|
| 1744 | - ->method('addHeader'); |
|
| 1745 | - $template |
|
| 1746 | - ->expects($this->once()) |
|
| 1747 | - ->method('addHeading') |
|
| 1748 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1749 | - $template |
|
| 1750 | - ->expects($this->once()) |
|
| 1751 | - ->method('addBodyButton') |
|
| 1752 | - ->with( |
|
| 1753 | - 'Open file.txt', |
|
| 1754 | - 'https://example.com/file.txt' |
|
| 1755 | - ); |
|
| 1756 | - $message |
|
| 1757 | - ->expects($this->once()) |
|
| 1758 | - ->method('setTo') |
|
| 1759 | - ->with(['[email protected]']); |
|
| 1760 | - $this->defaults |
|
| 1761 | - ->expects($this->once()) |
|
| 1762 | - ->method('getName') |
|
| 1763 | - ->willReturn('UnitTestCloud'); |
|
| 1764 | - $message |
|
| 1765 | - ->expects($this->once()) |
|
| 1766 | - ->method('setFrom') |
|
| 1767 | - ->with([ |
|
| 1768 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud' |
|
| 1769 | - ]); |
|
| 1770 | - // Since replyToInitiator is false, we never get the initiator email address |
|
| 1771 | - $user |
|
| 1772 | - ->expects($this->never()) |
|
| 1773 | - ->method('getEMailAddress'); |
|
| 1774 | - $message |
|
| 1775 | - ->expects($this->never()) |
|
| 1776 | - ->method('setReplyTo'); |
|
| 1777 | - $template |
|
| 1778 | - ->expects($this->once()) |
|
| 1779 | - ->method('addFooter') |
|
| 1780 | - ->with(''); |
|
| 1781 | - $template |
|
| 1782 | - ->expects($this->once()) |
|
| 1783 | - ->method('setSubject') |
|
| 1784 | - ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1785 | - $message |
|
| 1786 | - ->expects($this->once()) |
|
| 1787 | - ->method('useTemplate') |
|
| 1788 | - ->with($template); |
|
| 1789 | - |
|
| 1790 | - $this->mailer |
|
| 1791 | - ->expects($this->once()) |
|
| 1792 | - ->method('send') |
|
| 1793 | - ->with($message); |
|
| 1794 | - |
|
| 1795 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1796 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1797 | - ->willReturn('https://example.com/file.txt'); |
|
| 1798 | - |
|
| 1799 | - $node = $this->createMock(File::class); |
|
| 1800 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1801 | - |
|
| 1802 | - $share = $this->createMock(IShare::class); |
|
| 1803 | - $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1804 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1805 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1806 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1807 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1808 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1809 | - |
|
| 1810 | - self::invokePrivate( |
|
| 1811 | - $provider, |
|
| 1812 | - 'sendMailNotification', |
|
| 1813 | - [$share] |
|
| 1814 | - ); |
|
| 1815 | - } |
|
| 1816 | - |
|
| 1817 | - public function testSendMailNotificationWithDifferentUserAndNoUserEmailAndReplyToDesactivate(): void { |
|
| 1818 | - $provider = $this->getInstance(); |
|
| 1819 | - $initiatorUser = $this->createMock(IUser::class); |
|
| 1820 | - $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false); |
|
| 1821 | - $this->userManager |
|
| 1822 | - ->expects($this->once()) |
|
| 1823 | - ->method('get') |
|
| 1824 | - ->with('InitiatorUser') |
|
| 1825 | - ->willReturn($initiatorUser); |
|
| 1826 | - $initiatorUser |
|
| 1827 | - ->expects($this->once()) |
|
| 1828 | - ->method('getDisplayName') |
|
| 1829 | - ->willReturn('Mr. Initiator User'); |
|
| 1830 | - $message = $this->createMock(Message::class); |
|
| 1831 | - $this->mailer |
|
| 1832 | - ->expects($this->once()) |
|
| 1833 | - ->method('createMessage') |
|
| 1834 | - ->willReturn($message); |
|
| 1835 | - $template = $this->createMock(IEMailTemplate::class); |
|
| 1836 | - $this->mailer |
|
| 1837 | - ->expects($this->once()) |
|
| 1838 | - ->method('createEMailTemplate') |
|
| 1839 | - ->willReturn($template); |
|
| 1840 | - $template |
|
| 1841 | - ->expects($this->once()) |
|
| 1842 | - ->method('addHeader'); |
|
| 1843 | - $template |
|
| 1844 | - ->expects($this->once()) |
|
| 1845 | - ->method('addHeading') |
|
| 1846 | - ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1847 | - $template |
|
| 1848 | - ->expects($this->once()) |
|
| 1849 | - ->method('addBodyButton') |
|
| 1850 | - ->with( |
|
| 1851 | - 'Open file.txt', |
|
| 1852 | - 'https://example.com/file.txt' |
|
| 1853 | - ); |
|
| 1854 | - $message |
|
| 1855 | - ->expects($this->once()) |
|
| 1856 | - ->method('setTo') |
|
| 1857 | - ->with(['[email protected]']); |
|
| 1858 | - $this->defaults |
|
| 1859 | - ->expects($this->once()) |
|
| 1860 | - ->method('getName') |
|
| 1861 | - ->willReturn('UnitTestCloud'); |
|
| 1862 | - $message |
|
| 1863 | - ->expects($this->once()) |
|
| 1864 | - ->method('setFrom') |
|
| 1865 | - ->with([ |
|
| 1866 | - Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud' |
|
| 1867 | - ]); |
|
| 1868 | - $message |
|
| 1869 | - ->expects($this->never()) |
|
| 1870 | - ->method('setReplyTo'); |
|
| 1871 | - $template |
|
| 1872 | - ->expects($this->once()) |
|
| 1873 | - ->method('addFooter') |
|
| 1874 | - ->with(''); |
|
| 1875 | - $template |
|
| 1876 | - ->expects($this->once()) |
|
| 1877 | - ->method('setSubject') |
|
| 1878 | - ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1879 | - $message |
|
| 1880 | - ->expects($this->once()) |
|
| 1881 | - ->method('useTemplate') |
|
| 1882 | - ->with($template); |
|
| 1883 | - |
|
| 1884 | - $this->mailer |
|
| 1885 | - ->expects($this->once()) |
|
| 1886 | - ->method('send') |
|
| 1887 | - ->with($message); |
|
| 1888 | - |
|
| 1889 | - $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1890 | - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1891 | - ->willReturn('https://example.com/file.txt'); |
|
| 1892 | - |
|
| 1893 | - $node = $this->createMock(File::class); |
|
| 1894 | - $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1895 | - |
|
| 1896 | - $share = $this->createMock(IShare::class); |
|
| 1897 | - $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser'); |
|
| 1898 | - $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1899 | - $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1900 | - $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1901 | - $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1902 | - $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1903 | - |
|
| 1904 | - self::invokePrivate( |
|
| 1905 | - $provider, |
|
| 1906 | - 'sendMailNotification', |
|
| 1907 | - [$share] |
|
| 1908 | - ); |
|
| 1909 | - } |
|
| 1157 | + $qb->setValue('file_target', $qb->createNamedParameter('')); |
|
| 1158 | + |
|
| 1159 | + $qb->executeStatement(); |
|
| 1160 | + $id = $qb->getLastInsertId(); |
|
| 1161 | + |
|
| 1162 | + return (int)$id; |
|
| 1163 | + } |
|
| 1164 | + |
|
| 1165 | + public function testGetSharesInFolder(): void { |
|
| 1166 | + $userManager = Server::get(IUserManager::class); |
|
| 1167 | + $rootFolder = Server::get(IRootFolder::class); |
|
| 1168 | + |
|
| 1169 | + $this->shareManager->expects($this->any()) |
|
| 1170 | + ->method('newShare') |
|
| 1171 | + ->willReturn(new Share($rootFolder, $userManager)); |
|
| 1172 | + |
|
| 1173 | + $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']); |
|
| 1174 | + |
|
| 1175 | + $u1 = $userManager->createUser('testFed', md5((string)time())); |
|
| 1176 | + $u2 = $userManager->createUser('testFed2', md5((string)time())); |
|
| 1177 | + |
|
| 1178 | + $folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); |
|
| 1179 | + $file1 = $folder1->newFile('bar1'); |
|
| 1180 | + $file2 = $folder1->newFile('bar2'); |
|
| 1181 | + |
|
| 1182 | + $share1 = $this->shareManager->newShare(); |
|
| 1183 | + $share1->setSharedWith('[email protected]') |
|
| 1184 | + ->setSharedBy($u1->getUID()) |
|
| 1185 | + ->setShareOwner($u1->getUID()) |
|
| 1186 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 1187 | + ->setNode($file1); |
|
| 1188 | + $provider->create($share1); |
|
| 1189 | + |
|
| 1190 | + $share2 = $this->shareManager->newShare(); |
|
| 1191 | + $share2->setSharedWith('[email protected]') |
|
| 1192 | + ->setSharedBy($u2->getUID()) |
|
| 1193 | + ->setShareOwner($u1->getUID()) |
|
| 1194 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 1195 | + ->setNode($file2); |
|
| 1196 | + $provider->create($share2); |
|
| 1197 | + |
|
| 1198 | + $result = $provider->getSharesInFolder($u1->getUID(), $folder1, false); |
|
| 1199 | + $this->assertCount(1, $result); |
|
| 1200 | + $this->assertCount(1, $result[$file1->getId()]); |
|
| 1201 | + |
|
| 1202 | + $result = $provider->getSharesInFolder($u1->getUID(), $folder1, true); |
|
| 1203 | + $this->assertCount(2, $result); |
|
| 1204 | + $this->assertCount(1, $result[$file1->getId()]); |
|
| 1205 | + $this->assertCount(1, $result[$file2->getId()]); |
|
| 1206 | + |
|
| 1207 | + $u1->delete(); |
|
| 1208 | + $u2->delete(); |
|
| 1209 | + } |
|
| 1210 | + |
|
| 1211 | + public function testGetAccessList(): void { |
|
| 1212 | + $userManager = Server::get(IUserManager::class); |
|
| 1213 | + $rootFolder = Server::get(IRootFolder::class); |
|
| 1214 | + |
|
| 1215 | + $this->shareManager->expects($this->any()) |
|
| 1216 | + ->method('newShare') |
|
| 1217 | + ->willReturn(new Share($rootFolder, $userManager)); |
|
| 1218 | + |
|
| 1219 | + $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']); |
|
| 1220 | + |
|
| 1221 | + $u1 = $userManager->createUser('testFed', md5((string)time())); |
|
| 1222 | + $u2 = $userManager->createUser('testFed2', md5((string)time())); |
|
| 1223 | + |
|
| 1224 | + $folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); |
|
| 1225 | + |
|
| 1226 | + $accessList = $provider->getAccessList([$folder], true); |
|
| 1227 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1228 | + $this->assertFalse($accessList['public']); |
|
| 1229 | + $accessList = $provider->getAccessList([$folder], false); |
|
| 1230 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1231 | + $this->assertFalse($accessList['public']); |
|
| 1232 | + |
|
| 1233 | + $share1 = $this->shareManager->newShare(); |
|
| 1234 | + $share1->setSharedWith('[email protected]') |
|
| 1235 | + ->setSharedBy($u1->getUID()) |
|
| 1236 | + ->setShareOwner($u1->getUID()) |
|
| 1237 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 1238 | + ->setNode($folder); |
|
| 1239 | + $share1 = $provider->create($share1); |
|
| 1240 | + |
|
| 1241 | + $share2 = $this->shareManager->newShare(); |
|
| 1242 | + $share2->setSharedWith('[email protected]') |
|
| 1243 | + ->setSharedBy($u2->getUID()) |
|
| 1244 | + ->setShareOwner($u1->getUID()) |
|
| 1245 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 1246 | + ->setNode($folder); |
|
| 1247 | + $share2 = $provider->create($share2); |
|
| 1248 | + |
|
| 1249 | + $accessList = $provider->getAccessList([$folder], true); |
|
| 1250 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1251 | + $this->assertTrue($accessList['public']); |
|
| 1252 | + $accessList = $provider->getAccessList([$folder], false); |
|
| 1253 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1254 | + $this->assertTrue($accessList['public']); |
|
| 1255 | + |
|
| 1256 | + $provider->delete($share2); |
|
| 1257 | + |
|
| 1258 | + $accessList = $provider->getAccessList([$folder], true); |
|
| 1259 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1260 | + $this->assertTrue($accessList['public']); |
|
| 1261 | + $accessList = $provider->getAccessList([$folder], false); |
|
| 1262 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1263 | + $this->assertTrue($accessList['public']); |
|
| 1264 | + |
|
| 1265 | + $provider->delete($share1); |
|
| 1266 | + |
|
| 1267 | + $accessList = $provider->getAccessList([$folder], true); |
|
| 1268 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1269 | + $this->assertFalse($accessList['public']); |
|
| 1270 | + $accessList = $provider->getAccessList([$folder], false); |
|
| 1271 | + $this->assertArrayHasKey('public', $accessList); |
|
| 1272 | + $this->assertFalse($accessList['public']); |
|
| 1273 | + |
|
| 1274 | + $u1->delete(); |
|
| 1275 | + $u2->delete(); |
|
| 1276 | + } |
|
| 1277 | + |
|
| 1278 | + public function testSendMailNotificationWithSameUserAndUserEmail(): void { |
|
| 1279 | + $provider = $this->getInstance(); |
|
| 1280 | + $user = $this->createMock(IUser::class); |
|
| 1281 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1282 | + $this->userManager |
|
| 1283 | + ->expects($this->once()) |
|
| 1284 | + ->method('get') |
|
| 1285 | + ->with('OwnerUser') |
|
| 1286 | + ->willReturn($user); |
|
| 1287 | + $user |
|
| 1288 | + ->expects($this->once()) |
|
| 1289 | + ->method('getDisplayName') |
|
| 1290 | + ->willReturn('Mrs. Owner User'); |
|
| 1291 | + $message = $this->createMock(Message::class); |
|
| 1292 | + $this->mailer |
|
| 1293 | + ->expects($this->once()) |
|
| 1294 | + ->method('createMessage') |
|
| 1295 | + ->willReturn($message); |
|
| 1296 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1297 | + $this->mailer |
|
| 1298 | + ->expects($this->once()) |
|
| 1299 | + ->method('createEMailTemplate') |
|
| 1300 | + ->willReturn($template); |
|
| 1301 | + $template |
|
| 1302 | + ->expects($this->once()) |
|
| 1303 | + ->method('addHeader'); |
|
| 1304 | + $template |
|
| 1305 | + ->expects($this->once()) |
|
| 1306 | + ->method('addHeading') |
|
| 1307 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1308 | + $template |
|
| 1309 | + ->expects($this->once()) |
|
| 1310 | + ->method('addBodyButton') |
|
| 1311 | + ->with( |
|
| 1312 | + 'Open file.txt', |
|
| 1313 | + 'https://example.com/file.txt' |
|
| 1314 | + ); |
|
| 1315 | + $message |
|
| 1316 | + ->expects($this->once()) |
|
| 1317 | + ->method('setTo') |
|
| 1318 | + ->with(['[email protected]']); |
|
| 1319 | + $this->defaults |
|
| 1320 | + ->expects($this->once()) |
|
| 1321 | + ->method('getName') |
|
| 1322 | + ->willReturn('UnitTestCloud'); |
|
| 1323 | + $message |
|
| 1324 | + ->expects($this->once()) |
|
| 1325 | + ->method('setFrom') |
|
| 1326 | + ->with([ |
|
| 1327 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1328 | + ]); |
|
| 1329 | + $user |
|
| 1330 | + ->expects($this->once()) |
|
| 1331 | + ->method('getEMailAddress') |
|
| 1332 | + ->willReturn('[email protected]'); |
|
| 1333 | + $message |
|
| 1334 | + ->expects($this->once()) |
|
| 1335 | + ->method('setReplyTo') |
|
| 1336 | + ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1337 | + $this->defaults |
|
| 1338 | + ->expects($this->exactly(2)) |
|
| 1339 | + ->method('getSlogan') |
|
| 1340 | + ->willReturn('Testing like 1990'); |
|
| 1341 | + $template |
|
| 1342 | + ->expects($this->once()) |
|
| 1343 | + ->method('addFooter') |
|
| 1344 | + ->with('UnitTestCloud - Testing like 1990'); |
|
| 1345 | + $template |
|
| 1346 | + ->expects($this->once()) |
|
| 1347 | + ->method('setSubject') |
|
| 1348 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1349 | + $message |
|
| 1350 | + ->expects($this->once()) |
|
| 1351 | + ->method('useTemplate') |
|
| 1352 | + ->with($template); |
|
| 1353 | + |
|
| 1354 | + $this->mailer |
|
| 1355 | + ->expects($this->once()) |
|
| 1356 | + ->method('send') |
|
| 1357 | + ->with($message); |
|
| 1358 | + |
|
| 1359 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1360 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1361 | + ->willReturn('https://example.com/file.txt'); |
|
| 1362 | + |
|
| 1363 | + $node = $this->createMock(File::class); |
|
| 1364 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1365 | + |
|
| 1366 | + $share = $this->createMock(IShare::class); |
|
| 1367 | + $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1368 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1369 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1370 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1371 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1372 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1373 | + |
|
| 1374 | + self::invokePrivate( |
|
| 1375 | + $provider, |
|
| 1376 | + 'sendMailNotification', |
|
| 1377 | + [$share] |
|
| 1378 | + ); |
|
| 1379 | + } |
|
| 1380 | + |
|
| 1381 | + public function testSendMailNotificationWithSameUserAndUserEmailAndNote(): void { |
|
| 1382 | + $provider = $this->getInstance(); |
|
| 1383 | + $user = $this->createMock(IUser::class); |
|
| 1384 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1385 | + $this->userManager |
|
| 1386 | + ->expects($this->once()) |
|
| 1387 | + ->method('get') |
|
| 1388 | + ->with('OwnerUser') |
|
| 1389 | + ->willReturn($user); |
|
| 1390 | + $user |
|
| 1391 | + ->expects($this->once()) |
|
| 1392 | + ->method('getDisplayName') |
|
| 1393 | + ->willReturn('Mrs. Owner User'); |
|
| 1394 | + $message = $this->createMock(Message::class); |
|
| 1395 | + $this->mailer |
|
| 1396 | + ->expects($this->once()) |
|
| 1397 | + ->method('createMessage') |
|
| 1398 | + ->willReturn($message); |
|
| 1399 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1400 | + $this->mailer |
|
| 1401 | + ->expects($this->once()) |
|
| 1402 | + ->method('createEMailTemplate') |
|
| 1403 | + ->willReturn($template); |
|
| 1404 | + $template |
|
| 1405 | + ->expects($this->once()) |
|
| 1406 | + ->method('addHeader'); |
|
| 1407 | + $template |
|
| 1408 | + ->expects($this->once()) |
|
| 1409 | + ->method('addHeading') |
|
| 1410 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1411 | + |
|
| 1412 | + $this->urlGenerator->expects($this->once())->method('imagePath') |
|
| 1413 | + ->with('core', 'caldav/description.png') |
|
| 1414 | + ->willReturn('core/img/caldav/description.png'); |
|
| 1415 | + $this->urlGenerator->expects($this->once())->method('getAbsoluteURL') |
|
| 1416 | + ->with('core/img/caldav/description.png') |
|
| 1417 | + ->willReturn('https://example.com/core/img/caldav/description.png'); |
|
| 1418 | + $template |
|
| 1419 | + ->expects($this->once()) |
|
| 1420 | + ->method('addBodyListItem') |
|
| 1421 | + ->with( |
|
| 1422 | + 'This is a note to the recipient', |
|
| 1423 | + 'Note:', |
|
| 1424 | + 'https://example.com/core/img/caldav/description.png', |
|
| 1425 | + 'This is a note to the recipient' |
|
| 1426 | + ); |
|
| 1427 | + $template |
|
| 1428 | + ->expects($this->once()) |
|
| 1429 | + ->method('addBodyButton') |
|
| 1430 | + ->with( |
|
| 1431 | + 'Open file.txt', |
|
| 1432 | + 'https://example.com/file.txt' |
|
| 1433 | + ); |
|
| 1434 | + $message |
|
| 1435 | + ->expects($this->once()) |
|
| 1436 | + ->method('setTo') |
|
| 1437 | + ->with(['[email protected]']); |
|
| 1438 | + $this->defaults |
|
| 1439 | + ->expects($this->once()) |
|
| 1440 | + ->method('getName') |
|
| 1441 | + ->willReturn('UnitTestCloud'); |
|
| 1442 | + $message |
|
| 1443 | + ->expects($this->once()) |
|
| 1444 | + ->method('setFrom') |
|
| 1445 | + ->with([ |
|
| 1446 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1447 | + ]); |
|
| 1448 | + $user |
|
| 1449 | + ->expects($this->once()) |
|
| 1450 | + ->method('getEMailAddress') |
|
| 1451 | + ->willReturn('[email protected]'); |
|
| 1452 | + $message |
|
| 1453 | + ->expects($this->once()) |
|
| 1454 | + ->method('setReplyTo') |
|
| 1455 | + ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1456 | + $this->defaults |
|
| 1457 | + ->expects($this->exactly(2)) |
|
| 1458 | + ->method('getSlogan') |
|
| 1459 | + ->willReturn('Testing like 1990'); |
|
| 1460 | + $template |
|
| 1461 | + ->expects($this->once()) |
|
| 1462 | + ->method('addFooter') |
|
| 1463 | + ->with('UnitTestCloud - Testing like 1990'); |
|
| 1464 | + $template |
|
| 1465 | + ->expects($this->once()) |
|
| 1466 | + ->method('setSubject') |
|
| 1467 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1468 | + $message |
|
| 1469 | + ->expects($this->once()) |
|
| 1470 | + ->method('useTemplate') |
|
| 1471 | + ->with($template); |
|
| 1472 | + |
|
| 1473 | + $this->mailer |
|
| 1474 | + ->expects($this->once()) |
|
| 1475 | + ->method('send') |
|
| 1476 | + ->with($message); |
|
| 1477 | + |
|
| 1478 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1479 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1480 | + ->willReturn('https://example.com/file.txt'); |
|
| 1481 | + |
|
| 1482 | + $node = $this->createMock(File::class); |
|
| 1483 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1484 | + |
|
| 1485 | + $share = $this->createMock(IShare::class); |
|
| 1486 | + $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1487 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1488 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1489 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1490 | + $share->expects($this->any())->method('getNote')->willReturn('This is a note to the recipient'); |
|
| 1491 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1492 | + |
|
| 1493 | + self::invokePrivate( |
|
| 1494 | + $provider, |
|
| 1495 | + 'sendMailNotification', |
|
| 1496 | + [$share] |
|
| 1497 | + ); |
|
| 1498 | + } |
|
| 1499 | + |
|
| 1500 | + public function testSendMailNotificationWithSameUserAndUserEmailAndExpiration(): void { |
|
| 1501 | + $provider = $this->getInstance(); |
|
| 1502 | + $user = $this->createMock(IUser::class); |
|
| 1503 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1504 | + $this->userManager |
|
| 1505 | + ->expects($this->once()) |
|
| 1506 | + ->method('get') |
|
| 1507 | + ->with('OwnerUser') |
|
| 1508 | + ->willReturn($user); |
|
| 1509 | + $user |
|
| 1510 | + ->expects($this->once()) |
|
| 1511 | + ->method('getDisplayName') |
|
| 1512 | + ->willReturn('Mrs. Owner User'); |
|
| 1513 | + $message = $this->createMock(Message::class); |
|
| 1514 | + $this->mailer |
|
| 1515 | + ->expects($this->once()) |
|
| 1516 | + ->method('createMessage') |
|
| 1517 | + ->willReturn($message); |
|
| 1518 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1519 | + $this->mailer |
|
| 1520 | + ->expects($this->once()) |
|
| 1521 | + ->method('createEMailTemplate') |
|
| 1522 | + ->willReturn($template); |
|
| 1523 | + $template |
|
| 1524 | + ->expects($this->once()) |
|
| 1525 | + ->method('addHeader'); |
|
| 1526 | + $template |
|
| 1527 | + ->expects($this->once()) |
|
| 1528 | + ->method('addHeading') |
|
| 1529 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1530 | + |
|
| 1531 | + $expiration = new DateTime('2001-01-01'); |
|
| 1532 | + $this->l->expects($this->once()) |
|
| 1533 | + ->method('l') |
|
| 1534 | + ->with('date', $expiration, ['width' => 'medium']) |
|
| 1535 | + ->willReturn('2001-01-01'); |
|
| 1536 | + $this->urlGenerator->expects($this->once())->method('imagePath') |
|
| 1537 | + ->with('core', 'caldav/time.png') |
|
| 1538 | + ->willReturn('core/img/caldav/time.png'); |
|
| 1539 | + $this->urlGenerator->expects($this->once())->method('getAbsoluteURL') |
|
| 1540 | + ->with('core/img/caldav/time.png') |
|
| 1541 | + ->willReturn('https://example.com/core/img/caldav/time.png'); |
|
| 1542 | + $template |
|
| 1543 | + ->expects($this->once()) |
|
| 1544 | + ->method('addBodyListItem') |
|
| 1545 | + ->with( |
|
| 1546 | + 'This share is valid until 2001-01-01 at midnight', |
|
| 1547 | + 'Expiration:', |
|
| 1548 | + 'https://example.com/core/img/caldav/time.png', |
|
| 1549 | + ); |
|
| 1550 | + |
|
| 1551 | + $template |
|
| 1552 | + ->expects($this->once()) |
|
| 1553 | + ->method('addBodyButton') |
|
| 1554 | + ->with( |
|
| 1555 | + 'Open file.txt', |
|
| 1556 | + 'https://example.com/file.txt' |
|
| 1557 | + ); |
|
| 1558 | + $message |
|
| 1559 | + ->expects($this->once()) |
|
| 1560 | + ->method('setTo') |
|
| 1561 | + ->with(['[email protected]']); |
|
| 1562 | + $this->defaults |
|
| 1563 | + ->expects($this->once()) |
|
| 1564 | + ->method('getName') |
|
| 1565 | + ->willReturn('UnitTestCloud'); |
|
| 1566 | + $message |
|
| 1567 | + ->expects($this->once()) |
|
| 1568 | + ->method('setFrom') |
|
| 1569 | + ->with([ |
|
| 1570 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud' |
|
| 1571 | + ]); |
|
| 1572 | + $user |
|
| 1573 | + ->expects($this->once()) |
|
| 1574 | + ->method('getEMailAddress') |
|
| 1575 | + ->willReturn('[email protected]'); |
|
| 1576 | + $message |
|
| 1577 | + ->expects($this->once()) |
|
| 1578 | + ->method('setReplyTo') |
|
| 1579 | + ->with(['[email protected]' => 'Mrs. Owner User']); |
|
| 1580 | + $this->defaults |
|
| 1581 | + ->expects($this->exactly(2)) |
|
| 1582 | + ->method('getSlogan') |
|
| 1583 | + ->willReturn('Testing like 1990'); |
|
| 1584 | + $template |
|
| 1585 | + ->expects($this->once()) |
|
| 1586 | + ->method('addFooter') |
|
| 1587 | + ->with('UnitTestCloud - Testing like 1990'); |
|
| 1588 | + $template |
|
| 1589 | + ->expects($this->once()) |
|
| 1590 | + ->method('setSubject') |
|
| 1591 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1592 | + $message |
|
| 1593 | + ->expects($this->once()) |
|
| 1594 | + ->method('useTemplate') |
|
| 1595 | + ->with($template); |
|
| 1596 | + |
|
| 1597 | + $this->mailer |
|
| 1598 | + ->expects($this->once()) |
|
| 1599 | + ->method('send') |
|
| 1600 | + ->with($message); |
|
| 1601 | + |
|
| 1602 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1603 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1604 | + ->willReturn('https://example.com/file.txt'); |
|
| 1605 | + |
|
| 1606 | + $node = $this->createMock(File::class); |
|
| 1607 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1608 | + |
|
| 1609 | + $share = $this->createMock(IShare::class); |
|
| 1610 | + $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1611 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1612 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1613 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1614 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1615 | + $share->expects($this->any())->method('getExpirationDate')->willReturn($expiration); |
|
| 1616 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1617 | + |
|
| 1618 | + self::invokePrivate( |
|
| 1619 | + $provider, |
|
| 1620 | + 'sendMailNotification', |
|
| 1621 | + [$share] |
|
| 1622 | + ); |
|
| 1623 | + } |
|
| 1624 | + |
|
| 1625 | + public function testSendMailNotificationWithDifferentUserAndNoUserEmail(): void { |
|
| 1626 | + $provider = $this->getInstance(); |
|
| 1627 | + $initiatorUser = $this->createMock(IUser::class); |
|
| 1628 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true); |
|
| 1629 | + $this->userManager |
|
| 1630 | + ->expects($this->once()) |
|
| 1631 | + ->method('get') |
|
| 1632 | + ->with('InitiatorUser') |
|
| 1633 | + ->willReturn($initiatorUser); |
|
| 1634 | + $initiatorUser |
|
| 1635 | + ->expects($this->once()) |
|
| 1636 | + ->method('getDisplayName') |
|
| 1637 | + ->willReturn('Mr. Initiator User'); |
|
| 1638 | + $message = $this->createMock(Message::class); |
|
| 1639 | + $this->mailer |
|
| 1640 | + ->expects($this->once()) |
|
| 1641 | + ->method('createMessage') |
|
| 1642 | + ->willReturn($message); |
|
| 1643 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1644 | + $this->mailer |
|
| 1645 | + ->expects($this->once()) |
|
| 1646 | + ->method('createEMailTemplate') |
|
| 1647 | + ->willReturn($template); |
|
| 1648 | + $template |
|
| 1649 | + ->expects($this->once()) |
|
| 1650 | + ->method('addHeader'); |
|
| 1651 | + $template |
|
| 1652 | + ->expects($this->once()) |
|
| 1653 | + ->method('addHeading') |
|
| 1654 | + ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1655 | + $template |
|
| 1656 | + ->expects($this->once()) |
|
| 1657 | + ->method('addBodyButton') |
|
| 1658 | + ->with( |
|
| 1659 | + 'Open file.txt', |
|
| 1660 | + 'https://example.com/file.txt' |
|
| 1661 | + ); |
|
| 1662 | + $message |
|
| 1663 | + ->expects($this->once()) |
|
| 1664 | + ->method('setTo') |
|
| 1665 | + ->with(['[email protected]']); |
|
| 1666 | + $this->defaults |
|
| 1667 | + ->expects($this->once()) |
|
| 1668 | + ->method('getName') |
|
| 1669 | + ->willReturn('UnitTestCloud'); |
|
| 1670 | + $message |
|
| 1671 | + ->expects($this->once()) |
|
| 1672 | + ->method('setFrom') |
|
| 1673 | + ->with([ |
|
| 1674 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'Mr. Initiator User via UnitTestCloud' |
|
| 1675 | + ]); |
|
| 1676 | + $message |
|
| 1677 | + ->expects($this->never()) |
|
| 1678 | + ->method('setReplyTo'); |
|
| 1679 | + $template |
|
| 1680 | + ->expects($this->once()) |
|
| 1681 | + ->method('addFooter') |
|
| 1682 | + ->with(''); |
|
| 1683 | + $template |
|
| 1684 | + ->expects($this->once()) |
|
| 1685 | + ->method('setSubject') |
|
| 1686 | + ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1687 | + $message |
|
| 1688 | + ->expects($this->once()) |
|
| 1689 | + ->method('useTemplate') |
|
| 1690 | + ->with($template); |
|
| 1691 | + |
|
| 1692 | + $this->mailer |
|
| 1693 | + ->expects($this->once()) |
|
| 1694 | + ->method('send') |
|
| 1695 | + ->with($message); |
|
| 1696 | + |
|
| 1697 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1698 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1699 | + ->willReturn('https://example.com/file.txt'); |
|
| 1700 | + |
|
| 1701 | + $node = $this->createMock(File::class); |
|
| 1702 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1703 | + |
|
| 1704 | + $share = $this->createMock(IShare::class); |
|
| 1705 | + $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser'); |
|
| 1706 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1707 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1708 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1709 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1710 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1711 | + |
|
| 1712 | + self::invokePrivate( |
|
| 1713 | + $provider, |
|
| 1714 | + 'sendMailNotification', |
|
| 1715 | + [$share] |
|
| 1716 | + ); |
|
| 1717 | + } |
|
| 1718 | + |
|
| 1719 | + public function testSendMailNotificationWithSameUserAndUserEmailAndReplyToDesactivate(): void { |
|
| 1720 | + $provider = $this->getInstance(); |
|
| 1721 | + $user = $this->createMock(IUser::class); |
|
| 1722 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false); |
|
| 1723 | + $this->userManager |
|
| 1724 | + ->expects($this->once()) |
|
| 1725 | + ->method('get') |
|
| 1726 | + ->with('OwnerUser') |
|
| 1727 | + ->willReturn($user); |
|
| 1728 | + $user |
|
| 1729 | + ->expects($this->once()) |
|
| 1730 | + ->method('getDisplayName') |
|
| 1731 | + ->willReturn('Mrs. Owner User'); |
|
| 1732 | + $message = $this->createMock(Message::class); |
|
| 1733 | + $this->mailer |
|
| 1734 | + ->expects($this->once()) |
|
| 1735 | + ->method('createMessage') |
|
| 1736 | + ->willReturn($message); |
|
| 1737 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1738 | + $this->mailer |
|
| 1739 | + ->expects($this->once()) |
|
| 1740 | + ->method('createEMailTemplate') |
|
| 1741 | + ->willReturn($template); |
|
| 1742 | + $template |
|
| 1743 | + ->expects($this->once()) |
|
| 1744 | + ->method('addHeader'); |
|
| 1745 | + $template |
|
| 1746 | + ->expects($this->once()) |
|
| 1747 | + ->method('addHeading') |
|
| 1748 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1749 | + $template |
|
| 1750 | + ->expects($this->once()) |
|
| 1751 | + ->method('addBodyButton') |
|
| 1752 | + ->with( |
|
| 1753 | + 'Open file.txt', |
|
| 1754 | + 'https://example.com/file.txt' |
|
| 1755 | + ); |
|
| 1756 | + $message |
|
| 1757 | + ->expects($this->once()) |
|
| 1758 | + ->method('setTo') |
|
| 1759 | + ->with(['[email protected]']); |
|
| 1760 | + $this->defaults |
|
| 1761 | + ->expects($this->once()) |
|
| 1762 | + ->method('getName') |
|
| 1763 | + ->willReturn('UnitTestCloud'); |
|
| 1764 | + $message |
|
| 1765 | + ->expects($this->once()) |
|
| 1766 | + ->method('setFrom') |
|
| 1767 | + ->with([ |
|
| 1768 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud' |
|
| 1769 | + ]); |
|
| 1770 | + // Since replyToInitiator is false, we never get the initiator email address |
|
| 1771 | + $user |
|
| 1772 | + ->expects($this->never()) |
|
| 1773 | + ->method('getEMailAddress'); |
|
| 1774 | + $message |
|
| 1775 | + ->expects($this->never()) |
|
| 1776 | + ->method('setReplyTo'); |
|
| 1777 | + $template |
|
| 1778 | + ->expects($this->once()) |
|
| 1779 | + ->method('addFooter') |
|
| 1780 | + ->with(''); |
|
| 1781 | + $template |
|
| 1782 | + ->expects($this->once()) |
|
| 1783 | + ->method('setSubject') |
|
| 1784 | + ->with('Mrs. Owner User shared file.txt with you'); |
|
| 1785 | + $message |
|
| 1786 | + ->expects($this->once()) |
|
| 1787 | + ->method('useTemplate') |
|
| 1788 | + ->with($template); |
|
| 1789 | + |
|
| 1790 | + $this->mailer |
|
| 1791 | + ->expects($this->once()) |
|
| 1792 | + ->method('send') |
|
| 1793 | + ->with($message); |
|
| 1794 | + |
|
| 1795 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1796 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1797 | + ->willReturn('https://example.com/file.txt'); |
|
| 1798 | + |
|
| 1799 | + $node = $this->createMock(File::class); |
|
| 1800 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1801 | + |
|
| 1802 | + $share = $this->createMock(IShare::class); |
|
| 1803 | + $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser'); |
|
| 1804 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1805 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1806 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1807 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1808 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1809 | + |
|
| 1810 | + self::invokePrivate( |
|
| 1811 | + $provider, |
|
| 1812 | + 'sendMailNotification', |
|
| 1813 | + [$share] |
|
| 1814 | + ); |
|
| 1815 | + } |
|
| 1816 | + |
|
| 1817 | + public function testSendMailNotificationWithDifferentUserAndNoUserEmailAndReplyToDesactivate(): void { |
|
| 1818 | + $provider = $this->getInstance(); |
|
| 1819 | + $initiatorUser = $this->createMock(IUser::class); |
|
| 1820 | + $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false); |
|
| 1821 | + $this->userManager |
|
| 1822 | + ->expects($this->once()) |
|
| 1823 | + ->method('get') |
|
| 1824 | + ->with('InitiatorUser') |
|
| 1825 | + ->willReturn($initiatorUser); |
|
| 1826 | + $initiatorUser |
|
| 1827 | + ->expects($this->once()) |
|
| 1828 | + ->method('getDisplayName') |
|
| 1829 | + ->willReturn('Mr. Initiator User'); |
|
| 1830 | + $message = $this->createMock(Message::class); |
|
| 1831 | + $this->mailer |
|
| 1832 | + ->expects($this->once()) |
|
| 1833 | + ->method('createMessage') |
|
| 1834 | + ->willReturn($message); |
|
| 1835 | + $template = $this->createMock(IEMailTemplate::class); |
|
| 1836 | + $this->mailer |
|
| 1837 | + ->expects($this->once()) |
|
| 1838 | + ->method('createEMailTemplate') |
|
| 1839 | + ->willReturn($template); |
|
| 1840 | + $template |
|
| 1841 | + ->expects($this->once()) |
|
| 1842 | + ->method('addHeader'); |
|
| 1843 | + $template |
|
| 1844 | + ->expects($this->once()) |
|
| 1845 | + ->method('addHeading') |
|
| 1846 | + ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1847 | + $template |
|
| 1848 | + ->expects($this->once()) |
|
| 1849 | + ->method('addBodyButton') |
|
| 1850 | + ->with( |
|
| 1851 | + 'Open file.txt', |
|
| 1852 | + 'https://example.com/file.txt' |
|
| 1853 | + ); |
|
| 1854 | + $message |
|
| 1855 | + ->expects($this->once()) |
|
| 1856 | + ->method('setTo') |
|
| 1857 | + ->with(['[email protected]']); |
|
| 1858 | + $this->defaults |
|
| 1859 | + ->expects($this->once()) |
|
| 1860 | + ->method('getName') |
|
| 1861 | + ->willReturn('UnitTestCloud'); |
|
| 1862 | + $message |
|
| 1863 | + ->expects($this->once()) |
|
| 1864 | + ->method('setFrom') |
|
| 1865 | + ->with([ |
|
| 1866 | + Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud' |
|
| 1867 | + ]); |
|
| 1868 | + $message |
|
| 1869 | + ->expects($this->never()) |
|
| 1870 | + ->method('setReplyTo'); |
|
| 1871 | + $template |
|
| 1872 | + ->expects($this->once()) |
|
| 1873 | + ->method('addFooter') |
|
| 1874 | + ->with(''); |
|
| 1875 | + $template |
|
| 1876 | + ->expects($this->once()) |
|
| 1877 | + ->method('setSubject') |
|
| 1878 | + ->with('Mr. Initiator User shared file.txt with you'); |
|
| 1879 | + $message |
|
| 1880 | + ->expects($this->once()) |
|
| 1881 | + ->method('useTemplate') |
|
| 1882 | + ->with($template); |
|
| 1883 | + |
|
| 1884 | + $this->mailer |
|
| 1885 | + ->expects($this->once()) |
|
| 1886 | + ->method('send') |
|
| 1887 | + ->with($message); |
|
| 1888 | + |
|
| 1889 | + $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute') |
|
| 1890 | + ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) |
|
| 1891 | + ->willReturn('https://example.com/file.txt'); |
|
| 1892 | + |
|
| 1893 | + $node = $this->createMock(File::class); |
|
| 1894 | + $node->expects($this->any())->method('getName')->willReturn('file.txt'); |
|
| 1895 | + |
|
| 1896 | + $share = $this->createMock(IShare::class); |
|
| 1897 | + $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser'); |
|
| 1898 | + $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]'); |
|
| 1899 | + $share->expects($this->any())->method('getNode')->willReturn($node); |
|
| 1900 | + $share->expects($this->any())->method('getId')->willReturn(42); |
|
| 1901 | + $share->expects($this->any())->method('getNote')->willReturn(''); |
|
| 1902 | + $share->expects($this->any())->method('getToken')->willReturn('token'); |
|
| 1903 | + |
|
| 1904 | + self::invokePrivate( |
|
| 1905 | + $provider, |
|
| 1906 | + 'sendMailNotification', |
|
| 1907 | + [$share] |
|
| 1908 | + ); |
|
| 1909 | + } |
|
| 1910 | 1910 | } |
@@ -46,1200 +46,1200 @@ |
||
| 46 | 46 | * @package OCA\ShareByMail |
| 47 | 47 | */ |
| 48 | 48 | class ShareByMailProvider extends DefaultShareProvider implements IShareProviderWithNotification { |
| 49 | - /** |
|
| 50 | - * Return the identifier of this provider. |
|
| 51 | - * |
|
| 52 | - * @return string Containing only [a-zA-Z0-9] |
|
| 53 | - */ |
|
| 54 | - public function identifier(): string { |
|
| 55 | - return 'ocMailShare'; |
|
| 56 | - } |
|
| 57 | - |
|
| 58 | - public function __construct( |
|
| 59 | - private IConfig $config, |
|
| 60 | - private IDBConnection $dbConnection, |
|
| 61 | - private ISecureRandom $secureRandom, |
|
| 62 | - private IUserManager $userManager, |
|
| 63 | - private IRootFolder $rootFolder, |
|
| 64 | - private IL10N $l, |
|
| 65 | - private LoggerInterface $logger, |
|
| 66 | - private IMailer $mailer, |
|
| 67 | - private IURLGenerator $urlGenerator, |
|
| 68 | - private IManager $activityManager, |
|
| 69 | - private SettingsManager $settingsManager, |
|
| 70 | - private Defaults $defaults, |
|
| 71 | - private IHasher $hasher, |
|
| 72 | - private IEventDispatcher $eventDispatcher, |
|
| 73 | - private IShareManager $shareManager, |
|
| 74 | - private IEmailValidator $emailValidator, |
|
| 75 | - ) { |
|
| 76 | - } |
|
| 77 | - |
|
| 78 | - /** |
|
| 79 | - * Share a path |
|
| 80 | - * |
|
| 81 | - * @throws ShareNotFound |
|
| 82 | - * @throws \Exception |
|
| 83 | - */ |
|
| 84 | - public function create(IShare $share): IShare { |
|
| 85 | - $shareWith = $share->getSharedWith(); |
|
| 86 | - // Check if file is not already shared with the given email, |
|
| 87 | - // if we have an email at all. |
|
| 88 | - $alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_EMAIL, $share->getNode(), 1, 0); |
|
| 89 | - if ($shareWith !== '' && !empty($alreadyShared)) { |
|
| 90 | - $message = 'Sharing %1$s failed, because this item is already shared with the account %2$s'; |
|
| 91 | - $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with the account %2$s', [$share->getNode()->getName(), $shareWith]); |
|
| 92 | - $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']); |
|
| 93 | - throw new \Exception($message_t); |
|
| 94 | - } |
|
| 95 | - |
|
| 96 | - // if the admin enforces a password for all mail shares we create a |
|
| 97 | - // random password and send it to the recipient |
|
| 98 | - $password = $share->getPassword() ?: ''; |
|
| 99 | - $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword(); |
|
| 100 | - if ($passwordEnforced && empty($password)) { |
|
| 101 | - $password = $this->autoGeneratePassword($share); |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - if (!empty($password)) { |
|
| 105 | - $share->setPassword($this->hasher->hash($password)); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - $shareId = $this->createMailShare($share); |
|
| 109 | - |
|
| 110 | - $this->createShareActivity($share); |
|
| 111 | - $data = $this->getRawShare($shareId); |
|
| 112 | - |
|
| 113 | - // Temporary set the clear password again to send it by mail |
|
| 114 | - // This need to be done after the share was created in the database |
|
| 115 | - // as the password is hashed in between. |
|
| 116 | - if (!empty($password)) { |
|
| 117 | - $data['password'] = $password; |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - return $this->createShareObject($data); |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - /** |
|
| 124 | - * auto generate password in case of password enforcement on mail shares |
|
| 125 | - * |
|
| 126 | - * @throws \Exception |
|
| 127 | - */ |
|
| 128 | - protected function autoGeneratePassword(IShare $share): string { |
|
| 129 | - $initiatorUser = $this->userManager->get($share->getSharedBy()); |
|
| 130 | - $initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 131 | - $allowPasswordByMail = $this->settingsManager->sendPasswordByMail(); |
|
| 132 | - |
|
| 133 | - if ($initiatorEMailAddress === null && !$allowPasswordByMail) { |
|
| 134 | - throw new \Exception( |
|
| 135 | - $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.') |
|
| 136 | - ); |
|
| 137 | - } |
|
| 138 | - |
|
| 139 | - $passwordEvent = new GenerateSecurePasswordEvent(PasswordContext::SHARING); |
|
| 140 | - $this->eventDispatcher->dispatchTyped($passwordEvent); |
|
| 141 | - |
|
| 142 | - $password = $passwordEvent->getPassword(); |
|
| 143 | - if ($password === null) { |
|
| 144 | - $password = $this->secureRandom->generate(8, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 145 | - } |
|
| 146 | - |
|
| 147 | - return $password; |
|
| 148 | - } |
|
| 149 | - |
|
| 150 | - /** |
|
| 151 | - * create activity if a file/folder was shared by mail |
|
| 152 | - */ |
|
| 153 | - protected function createShareActivity(IShare $share, string $type = 'share'): void { |
|
| 154 | - $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); |
|
| 155 | - |
|
| 156 | - $this->publishActivity( |
|
| 157 | - $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_SELF : Activity::SUBJECT_UNSHARED_EMAIL_SELF, |
|
| 158 | - [$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()], |
|
| 159 | - $share->getSharedBy(), |
|
| 160 | - $share->getNode()->getId(), |
|
| 161 | - (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 162 | - ); |
|
| 163 | - |
|
| 164 | - if ($share->getShareOwner() !== $share->getSharedBy()) { |
|
| 165 | - $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); |
|
| 166 | - $fileId = $share->getNode()->getId(); |
|
| 167 | - $nodes = $ownerFolder->getById($fileId); |
|
| 168 | - $ownerPath = $nodes[0]->getPath(); |
|
| 169 | - $this->publishActivity( |
|
| 170 | - $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_BY : Activity::SUBJECT_UNSHARED_EMAIL_BY, |
|
| 171 | - [$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()], |
|
| 172 | - $share->getShareOwner(), |
|
| 173 | - $fileId, |
|
| 174 | - (string)$ownerFolder->getRelativePath($ownerPath) |
|
| 175 | - ); |
|
| 176 | - } |
|
| 177 | - } |
|
| 178 | - |
|
| 179 | - /** |
|
| 180 | - * create activity if a file/folder was shared by mail |
|
| 181 | - */ |
|
| 182 | - protected function createPasswordSendActivity(IShare $share, string $sharedWith, bool $sendToSelf): void { |
|
| 183 | - $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); |
|
| 184 | - |
|
| 185 | - if ($sendToSelf) { |
|
| 186 | - $this->publishActivity( |
|
| 187 | - Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF, |
|
| 188 | - [$userFolder->getRelativePath($share->getNode()->getPath())], |
|
| 189 | - $share->getSharedBy(), |
|
| 190 | - $share->getNode()->getId(), |
|
| 191 | - (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 192 | - ); |
|
| 193 | - } else { |
|
| 194 | - $this->publishActivity( |
|
| 195 | - Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND, |
|
| 196 | - [$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith], |
|
| 197 | - $share->getSharedBy(), |
|
| 198 | - $share->getNode()->getId(), |
|
| 199 | - (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 200 | - ); |
|
| 201 | - } |
|
| 202 | - } |
|
| 203 | - |
|
| 204 | - |
|
| 205 | - /** |
|
| 206 | - * publish activity if a file/folder was shared by mail |
|
| 207 | - */ |
|
| 208 | - protected function publishActivity(string $subject, array $parameters, string $affectedUser, int $fileId, string $filePath): void { |
|
| 209 | - $event = $this->activityManager->generateEvent(); |
|
| 210 | - $event->setApp('sharebymail') |
|
| 211 | - ->setType('shared') |
|
| 212 | - ->setSubject($subject, $parameters) |
|
| 213 | - ->setAffectedUser($affectedUser) |
|
| 214 | - ->setObject('files', $fileId, $filePath); |
|
| 215 | - $this->activityManager->publish($event); |
|
| 216 | - } |
|
| 217 | - |
|
| 218 | - /** |
|
| 219 | - * @throws \Exception |
|
| 220 | - */ |
|
| 221 | - protected function createMailShare(IShare $share): int { |
|
| 222 | - $share->setToken($this->generateToken()); |
|
| 223 | - return $this->addShareToDB( |
|
| 224 | - $share->getNodeId(), |
|
| 225 | - $share->getNodeType(), |
|
| 226 | - $share->getSharedWith(), |
|
| 227 | - $share->getSharedBy(), |
|
| 228 | - $share->getShareOwner(), |
|
| 229 | - $share->getPermissions(), |
|
| 230 | - $share->getToken(), |
|
| 231 | - $share->getPassword(), |
|
| 232 | - $share->getPasswordExpirationTime(), |
|
| 233 | - $share->getSendPasswordByTalk(), |
|
| 234 | - $share->getHideDownload(), |
|
| 235 | - $share->getLabel(), |
|
| 236 | - $share->getExpirationDate(), |
|
| 237 | - $share->getNote(), |
|
| 238 | - $share->getAttributes(), |
|
| 239 | - $share->getMailSend(), |
|
| 240 | - ); |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - /** |
|
| 244 | - * @inheritDoc |
|
| 245 | - */ |
|
| 246 | - public function sendMailNotification(IShare $share): bool { |
|
| 247 | - $shareId = $share->getId(); |
|
| 248 | - |
|
| 249 | - $emails = $this->getSharedWithEmails($share); |
|
| 250 | - $validEmails = array_filter($emails, function (string $email) { |
|
| 251 | - return $this->emailValidator->isValid($email); |
|
| 252 | - }); |
|
| 253 | - |
|
| 254 | - if (count($validEmails) === 0) { |
|
| 255 | - $this->removeShareFromTable((int)$shareId); |
|
| 256 | - $e = new HintException('Failed to send share by mail. Could not find a valid email address: ' . join(', ', $emails), |
|
| 257 | - $this->l->t('Failed to send share by email. Got an invalid email address')); |
|
| 258 | - $this->logger->error('Failed to send share by mail. Could not find a valid email address ' . join(', ', $emails), [ |
|
| 259 | - 'app' => 'sharebymail', |
|
| 260 | - 'exception' => $e, |
|
| 261 | - ]); |
|
| 262 | - } |
|
| 263 | - |
|
| 264 | - try { |
|
| 265 | - $this->sendEmail($share, $validEmails); |
|
| 266 | - |
|
| 267 | - // If we have a password set, we send it to the recipient |
|
| 268 | - if ($share->getPassword() !== null) { |
|
| 269 | - // If share-by-talk password is enabled, we do not send the notification |
|
| 270 | - // to the recipient. They will have to request it to the owner after opening the link. |
|
| 271 | - // Secondly, if the password expiration is disabled, we send the notification to the recipient |
|
| 272 | - // Lastly, if the mail to recipient failed, we send the password to the owner as a fallback. |
|
| 273 | - // If a password expires, the recipient will still be able to request a new one via talk. |
|
| 274 | - $passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false); |
|
| 275 | - $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword(); |
|
| 276 | - if ($passwordExpire === false || $share->getSendPasswordByTalk()) { |
|
| 277 | - $send = $this->sendPassword($share, $share->getPassword(), $validEmails); |
|
| 278 | - if ($passwordEnforced && $send === false) { |
|
| 279 | - $this->sendPasswordToOwner($share, $share->getPassword()); |
|
| 280 | - } |
|
| 281 | - } |
|
| 282 | - } |
|
| 283 | - |
|
| 284 | - return true; |
|
| 285 | - } catch (HintException $hintException) { |
|
| 286 | - $this->logger->error('Failed to send share by mail.', [ |
|
| 287 | - 'app' => 'sharebymail', |
|
| 288 | - 'exception' => $hintException, |
|
| 289 | - ]); |
|
| 290 | - $this->removeShareFromTable((int)$shareId); |
|
| 291 | - throw $hintException; |
|
| 292 | - } catch (\Exception $e) { |
|
| 293 | - $this->logger->error('Failed to send share by mail.', [ |
|
| 294 | - 'app' => 'sharebymail', |
|
| 295 | - 'exception' => $e, |
|
| 296 | - ]); |
|
| 297 | - $this->removeShareFromTable((int)$shareId); |
|
| 298 | - throw new HintException( |
|
| 299 | - 'Failed to send share by mail', |
|
| 300 | - $this->l->t('Failed to send share by email'), |
|
| 301 | - 0, |
|
| 302 | - $e, |
|
| 303 | - ); |
|
| 304 | - } |
|
| 305 | - return false; |
|
| 306 | - } |
|
| 307 | - |
|
| 308 | - /** |
|
| 309 | - * @param IShare $share The share to send the email for |
|
| 310 | - * @param array $emails The email addresses to send the email to |
|
| 311 | - */ |
|
| 312 | - protected function sendEmail(IShare $share, array $emails): void { |
|
| 313 | - $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [ |
|
| 314 | - 'token' => $share->getToken() |
|
| 315 | - ]); |
|
| 316 | - |
|
| 317 | - $expiration = $share->getExpirationDate(); |
|
| 318 | - $filename = $share->getNode()->getName(); |
|
| 319 | - $initiator = $share->getSharedBy(); |
|
| 320 | - $note = $share->getNote(); |
|
| 321 | - $shareWith = $share->getSharedWith(); |
|
| 322 | - |
|
| 323 | - $initiatorUser = $this->userManager->get($initiator); |
|
| 324 | - $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 325 | - $message = $this->mailer->createMessage(); |
|
| 326 | - |
|
| 327 | - $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [ |
|
| 328 | - 'filename' => $filename, |
|
| 329 | - 'link' => $link, |
|
| 330 | - 'initiator' => $initiatorDisplayName, |
|
| 331 | - 'expiration' => $expiration, |
|
| 332 | - 'shareWith' => $shareWith, |
|
| 333 | - 'note' => $note |
|
| 334 | - ]); |
|
| 335 | - |
|
| 336 | - $emailTemplate->setSubject($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename])); |
|
| 337 | - $emailTemplate->addHeader(); |
|
| 338 | - $emailTemplate->addHeading($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false); |
|
| 339 | - |
|
| 340 | - if ($note !== '') { |
|
| 341 | - $emailTemplate->addBodyListItem( |
|
| 342 | - htmlspecialchars($note), |
|
| 343 | - $this->l->t('Note:'), |
|
| 344 | - $this->getAbsoluteImagePath('caldav/description.png'), |
|
| 345 | - $note |
|
| 346 | - ); |
|
| 347 | - } |
|
| 348 | - |
|
| 349 | - if ($expiration !== null) { |
|
| 350 | - $dateString = (string)$this->l->l('date', $expiration, ['width' => 'medium']); |
|
| 351 | - $emailTemplate->addBodyListItem( |
|
| 352 | - $this->l->t('This share is valid until %s at midnight', [$dateString]), |
|
| 353 | - $this->l->t('Expiration:'), |
|
| 354 | - $this->getAbsoluteImagePath('caldav/time.png'), |
|
| 355 | - ); |
|
| 356 | - } |
|
| 357 | - |
|
| 358 | - $emailTemplate->addBodyButton( |
|
| 359 | - $this->l->t('Open %s', [$filename]), |
|
| 360 | - $link |
|
| 361 | - ); |
|
| 362 | - |
|
| 363 | - // If multiple recipients are given, we send the mail to all of them |
|
| 364 | - if (count($emails) > 1) { |
|
| 365 | - // We do not want to expose the email addresses of the other recipients |
|
| 366 | - $message->setBcc($emails); |
|
| 367 | - } else { |
|
| 368 | - $message->setTo($emails); |
|
| 369 | - } |
|
| 370 | - |
|
| 371 | - // The "From" contains the sharers name |
|
| 372 | - $instanceName = $this->defaults->getName(); |
|
| 373 | - $senderName = $instanceName; |
|
| 374 | - if ($this->settingsManager->replyToInitiator()) { |
|
| 375 | - $senderName = $this->l->t( |
|
| 376 | - '%1$s via %2$s', |
|
| 377 | - [ |
|
| 378 | - $initiatorDisplayName, |
|
| 379 | - $instanceName |
|
| 380 | - ] |
|
| 381 | - ); |
|
| 382 | - } |
|
| 383 | - $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 384 | - |
|
| 385 | - // The "Reply-To" is set to the sharer if an mail address is configured |
|
| 386 | - // also the default footer contains a "Do not reply" which needs to be adjusted. |
|
| 387 | - if ($initiatorUser && $this->settingsManager->replyToInitiator()) { |
|
| 388 | - $initiatorEmail = $initiatorUser->getEMailAddress(); |
|
| 389 | - if ($initiatorEmail !== null) { |
|
| 390 | - $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]); |
|
| 391 | - $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')); |
|
| 392 | - } else { |
|
| 393 | - $emailTemplate->addFooter(); |
|
| 394 | - } |
|
| 395 | - } else { |
|
| 396 | - $emailTemplate->addFooter(); |
|
| 397 | - } |
|
| 398 | - |
|
| 399 | - $message->useTemplate($emailTemplate); |
|
| 400 | - $failedRecipients = $this->mailer->send($message); |
|
| 401 | - if (!empty($failedRecipients)) { |
|
| 402 | - $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients)); |
|
| 403 | - return; |
|
| 404 | - } |
|
| 405 | - } |
|
| 406 | - |
|
| 407 | - /** |
|
| 408 | - * Send password to recipient of a mail share |
|
| 409 | - * Will return false if |
|
| 410 | - * 1. the password is empty |
|
| 411 | - * 2. the setting to send the password by mail is disabled |
|
| 412 | - * 3. the share is set to send the password by talk |
|
| 413 | - * |
|
| 414 | - * @param IShare $share |
|
| 415 | - * @param string $password |
|
| 416 | - * @param array $emails |
|
| 417 | - * @return bool |
|
| 418 | - */ |
|
| 419 | - protected function sendPassword(IShare $share, string $password, array $emails): bool { |
|
| 420 | - $filename = $share->getNode()->getName(); |
|
| 421 | - $initiator = $share->getSharedBy(); |
|
| 422 | - $shareWith = $share->getSharedWith(); |
|
| 423 | - |
|
| 424 | - if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) { |
|
| 425 | - return false; |
|
| 426 | - } |
|
| 427 | - |
|
| 428 | - $initiatorUser = $this->userManager->get($initiator); |
|
| 429 | - $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 430 | - $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 431 | - |
|
| 432 | - $plainBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]); |
|
| 433 | - $htmlBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]); |
|
| 434 | - |
|
| 435 | - $message = $this->mailer->createMessage(); |
|
| 436 | - |
|
| 437 | - $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [ |
|
| 438 | - 'filename' => $filename, |
|
| 439 | - 'password' => $password, |
|
| 440 | - 'initiator' => $initiatorDisplayName, |
|
| 441 | - 'initiatorEmail' => $initiatorEmailAddress, |
|
| 442 | - 'shareWith' => $shareWith, |
|
| 443 | - ]); |
|
| 444 | - |
|
| 445 | - $emailTemplate->setSubject($this->l->t('Password to access %1$s shared to you by %2$s', [$filename, $initiatorDisplayName])); |
|
| 446 | - $emailTemplate->addHeader(); |
|
| 447 | - $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false); |
|
| 448 | - $emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart); |
|
| 449 | - $emailTemplate->addBodyText($this->l->t('It is protected with the following password:')); |
|
| 450 | - $emailTemplate->addBodyText($password); |
|
| 451 | - |
|
| 452 | - if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) { |
|
| 453 | - $expirationTime = new \DateTime(); |
|
| 454 | - $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600); |
|
| 455 | - $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S')); |
|
| 456 | - $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')])); |
|
| 457 | - } |
|
| 458 | - |
|
| 459 | - // If multiple recipients are given, we send the mail to all of them |
|
| 460 | - if (count($emails) > 1) { |
|
| 461 | - // We do not want to expose the email addresses of the other recipients |
|
| 462 | - $message->setBcc($emails); |
|
| 463 | - } else { |
|
| 464 | - $message->setTo($emails); |
|
| 465 | - } |
|
| 466 | - |
|
| 467 | - // The "From" contains the sharers name |
|
| 468 | - $instanceName = $this->defaults->getName(); |
|
| 469 | - $senderName = $instanceName; |
|
| 470 | - if ($this->settingsManager->replyToInitiator()) { |
|
| 471 | - $senderName = $this->l->t( |
|
| 472 | - '%1$s via %2$s', |
|
| 473 | - [ |
|
| 474 | - $initiatorDisplayName, |
|
| 475 | - $instanceName |
|
| 476 | - ] |
|
| 477 | - ); |
|
| 478 | - } |
|
| 479 | - $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 480 | - |
|
| 481 | - // The "Reply-To" is set to the sharer if an mail address is configured |
|
| 482 | - // also the default footer contains a "Do not reply" which needs to be adjusted. |
|
| 483 | - if ($initiatorUser && $this->settingsManager->replyToInitiator()) { |
|
| 484 | - $initiatorEmail = $initiatorUser->getEMailAddress(); |
|
| 485 | - if ($initiatorEmail !== null) { |
|
| 486 | - $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]); |
|
| 487 | - $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')); |
|
| 488 | - } else { |
|
| 489 | - $emailTemplate->addFooter(); |
|
| 490 | - } |
|
| 491 | - } else { |
|
| 492 | - $emailTemplate->addFooter(); |
|
| 493 | - } |
|
| 494 | - |
|
| 495 | - $message->useTemplate($emailTemplate); |
|
| 496 | - $failedRecipients = $this->mailer->send($message); |
|
| 497 | - if (!empty($failedRecipients)) { |
|
| 498 | - $this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients)); |
|
| 499 | - return false; |
|
| 500 | - } |
|
| 501 | - |
|
| 502 | - $this->createPasswordSendActivity($share, $shareWith, false); |
|
| 503 | - return true; |
|
| 504 | - } |
|
| 505 | - |
|
| 506 | - protected function sendNote(IShare $share): void { |
|
| 507 | - $recipient = $share->getSharedWith(); |
|
| 508 | - |
|
| 509 | - |
|
| 510 | - $filename = $share->getNode()->getName(); |
|
| 511 | - $initiator = $share->getSharedBy(); |
|
| 512 | - $note = $share->getNote(); |
|
| 513 | - |
|
| 514 | - $initiatorUser = $this->userManager->get($initiator); |
|
| 515 | - $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 516 | - $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 517 | - |
|
| 518 | - $plainHeading = $this->l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]); |
|
| 519 | - $htmlHeading = $this->l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]); |
|
| 520 | - |
|
| 521 | - $message = $this->mailer->createMessage(); |
|
| 522 | - |
|
| 523 | - $emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote'); |
|
| 524 | - |
|
| 525 | - $emailTemplate->setSubject($this->l->t('%s added a note to a file shared with you', [$initiatorDisplayName])); |
|
| 526 | - $emailTemplate->addHeader(); |
|
| 527 | - $emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading); |
|
| 528 | - $emailTemplate->addBodyText(htmlspecialchars($note), $note); |
|
| 529 | - |
|
| 530 | - $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', |
|
| 531 | - ['token' => $share->getToken()]); |
|
| 532 | - $emailTemplate->addBodyButton( |
|
| 533 | - $this->l->t('Open %s', [$filename]), |
|
| 534 | - $link |
|
| 535 | - ); |
|
| 536 | - |
|
| 537 | - // The "From" contains the sharers name |
|
| 538 | - $instanceName = $this->defaults->getName(); |
|
| 539 | - $senderName = $instanceName; |
|
| 540 | - if ($this->settingsManager->replyToInitiator()) { |
|
| 541 | - $senderName = $this->l->t( |
|
| 542 | - '%1$s via %2$s', |
|
| 543 | - [ |
|
| 544 | - $initiatorDisplayName, |
|
| 545 | - $instanceName |
|
| 546 | - ] |
|
| 547 | - ); |
|
| 548 | - } |
|
| 549 | - $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 550 | - if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) { |
|
| 551 | - $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]); |
|
| 552 | - $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan()); |
|
| 553 | - } else { |
|
| 554 | - $emailTemplate->addFooter(); |
|
| 555 | - } |
|
| 556 | - |
|
| 557 | - $message->setTo([$recipient]); |
|
| 558 | - $message->useTemplate($emailTemplate); |
|
| 559 | - $this->mailer->send($message); |
|
| 560 | - } |
|
| 561 | - |
|
| 562 | - /** |
|
| 563 | - * send auto generated password to the owner. This happens if the admin enforces |
|
| 564 | - * a password for mail shares and forbid to send the password by mail to the recipient |
|
| 565 | - * |
|
| 566 | - * @throws \Exception |
|
| 567 | - */ |
|
| 568 | - protected function sendPasswordToOwner(IShare $share, string $password): bool { |
|
| 569 | - $filename = $share->getNode()->getName(); |
|
| 570 | - $initiator = $this->userManager->get($share->getSharedBy()); |
|
| 571 | - $initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null; |
|
| 572 | - $initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy(); |
|
| 573 | - $shareWith = implode(', ', $this->getSharedWithEmails($share)); |
|
| 574 | - |
|
| 575 | - if ($initiatorEMailAddress === null) { |
|
| 576 | - throw new \Exception( |
|
| 577 | - $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.') |
|
| 578 | - ); |
|
| 579 | - } |
|
| 580 | - |
|
| 581 | - $bodyPart = $this->l->t('You just shared %1$s with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.', [$filename, $shareWith, $this->defaults->getName()]); |
|
| 582 | - |
|
| 583 | - $message = $this->mailer->createMessage(); |
|
| 584 | - $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [ |
|
| 585 | - 'filename' => $filename, |
|
| 586 | - 'password' => $password, |
|
| 587 | - 'initiator' => $initiatorDisplayName, |
|
| 588 | - 'initiatorEmail' => $initiatorEMailAddress, |
|
| 589 | - 'shareWith' => $shareWith, |
|
| 590 | - ]); |
|
| 591 | - |
|
| 592 | - $emailTemplate->setSubject($this->l->t('Password to access %1$s shared by you with %2$s', [$filename, $shareWith])); |
|
| 593 | - $emailTemplate->addHeader(); |
|
| 594 | - $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false); |
|
| 595 | - $emailTemplate->addBodyText($bodyPart); |
|
| 596 | - $emailTemplate->addBodyText($this->l->t('This is the password:')); |
|
| 597 | - $emailTemplate->addBodyText($password); |
|
| 598 | - |
|
| 599 | - if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) { |
|
| 600 | - $expirationTime = new \DateTime(); |
|
| 601 | - $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600); |
|
| 602 | - $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S')); |
|
| 603 | - $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')])); |
|
| 604 | - } |
|
| 605 | - |
|
| 606 | - $emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.')); |
|
| 607 | - |
|
| 608 | - $emailTemplate->addFooter(); |
|
| 609 | - |
|
| 610 | - $instanceName = $this->defaults->getName(); |
|
| 611 | - $senderName = $this->l->t( |
|
| 612 | - '%1$s via %2$s', |
|
| 613 | - [ |
|
| 614 | - $initiatorDisplayName, |
|
| 615 | - $instanceName |
|
| 616 | - ] |
|
| 617 | - ); |
|
| 618 | - $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 619 | - $message->setTo([$initiatorEMailAddress => $initiatorDisplayName]); |
|
| 620 | - $message->useTemplate($emailTemplate); |
|
| 621 | - $this->mailer->send($message); |
|
| 622 | - |
|
| 623 | - $this->createPasswordSendActivity($share, $shareWith, true); |
|
| 624 | - |
|
| 625 | - return true; |
|
| 626 | - } |
|
| 627 | - |
|
| 628 | - private function getAbsoluteImagePath(string $path):string { |
|
| 629 | - return $this->urlGenerator->getAbsoluteURL( |
|
| 630 | - $this->urlGenerator->imagePath('core', $path) |
|
| 631 | - ); |
|
| 632 | - } |
|
| 633 | - |
|
| 634 | - /** |
|
| 635 | - * generate share token |
|
| 636 | - */ |
|
| 637 | - protected function generateToken(int $size = 15): string { |
|
| 638 | - $token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 639 | - return $token; |
|
| 640 | - } |
|
| 641 | - |
|
| 642 | - public function getChildren(IShare $parent): array { |
|
| 643 | - $children = []; |
|
| 644 | - |
|
| 645 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 646 | - $qb->select('*') |
|
| 647 | - ->from('share') |
|
| 648 | - ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId()))) |
|
| 649 | - ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 650 | - ->orderBy('id'); |
|
| 651 | - |
|
| 652 | - $cursor = $qb->executeQuery(); |
|
| 653 | - while ($data = $cursor->fetchAssociative()) { |
|
| 654 | - $children[] = $this->createShareObject($data); |
|
| 655 | - } |
|
| 656 | - $cursor->closeCursor(); |
|
| 657 | - |
|
| 658 | - return $children; |
|
| 659 | - } |
|
| 660 | - |
|
| 661 | - /** |
|
| 662 | - * Add share to the database and return the ID |
|
| 663 | - */ |
|
| 664 | - protected function addShareToDB( |
|
| 665 | - ?int $itemSource, |
|
| 666 | - ?string $itemType, |
|
| 667 | - ?string $shareWith, |
|
| 668 | - ?string $sharedBy, |
|
| 669 | - ?string $uidOwner, |
|
| 670 | - ?int $permissions, |
|
| 671 | - ?string $token, |
|
| 672 | - ?string $password, |
|
| 673 | - ?\DateTimeInterface $passwordExpirationTime, |
|
| 674 | - ?bool $sendPasswordByTalk, |
|
| 675 | - ?bool $hideDownload, |
|
| 676 | - ?string $label, |
|
| 677 | - ?\DateTimeInterface $expirationTime, |
|
| 678 | - ?string $note = '', |
|
| 679 | - ?IAttributes $attributes = null, |
|
| 680 | - ?bool $mailSend = true, |
|
| 681 | - ): int { |
|
| 682 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 683 | - $qb->insert('share') |
|
| 684 | - ->setValue('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 685 | - ->setValue('item_type', $qb->createNamedParameter($itemType)) |
|
| 686 | - ->setValue('item_source', $qb->createNamedParameter($itemSource)) |
|
| 687 | - ->setValue('file_source', $qb->createNamedParameter($itemSource)) |
|
| 688 | - ->setValue('share_with', $qb->createNamedParameter($shareWith)) |
|
| 689 | - ->setValue('uid_owner', $qb->createNamedParameter($uidOwner)) |
|
| 690 | - ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy)) |
|
| 691 | - ->setValue('permissions', $qb->createNamedParameter($permissions)) |
|
| 692 | - ->setValue('token', $qb->createNamedParameter($token)) |
|
| 693 | - ->setValue('password', $qb->createNamedParameter($password)) |
|
| 694 | - ->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 695 | - ->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL)) |
|
| 696 | - ->setValue('stime', $qb->createNamedParameter(time())) |
|
| 697 | - ->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT)) |
|
| 698 | - ->setValue('label', $qb->createNamedParameter($label)) |
|
| 699 | - ->setValue('note', $qb->createNamedParameter($note)) |
|
| 700 | - ->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT)); |
|
| 701 | - |
|
| 702 | - // set share attributes |
|
| 703 | - $shareAttributes = $this->formatShareAttributes($attributes); |
|
| 704 | - |
|
| 705 | - $qb->setValue('attributes', $qb->createNamedParameter($shareAttributes)); |
|
| 706 | - if ($expirationTime !== null) { |
|
| 707 | - $qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE)); |
|
| 708 | - } |
|
| 709 | - |
|
| 710 | - $qb->executeStatement(); |
|
| 711 | - return $qb->getLastInsertId(); |
|
| 712 | - } |
|
| 713 | - |
|
| 714 | - /** |
|
| 715 | - * Update a share |
|
| 716 | - */ |
|
| 717 | - public function update(IShare $share, ?string $plainTextPassword = null): IShare { |
|
| 718 | - $originalShare = $this->getShareById($share->getId()); |
|
| 719 | - |
|
| 720 | - // a real password was given |
|
| 721 | - $validPassword = $plainTextPassword !== null && $plainTextPassword !== ''; |
|
| 722 | - |
|
| 723 | - if ($validPassword && ($originalShare->getPassword() !== $share->getPassword() |
|
| 724 | - || ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) { |
|
| 725 | - $emails = $this->getSharedWithEmails($share); |
|
| 726 | - $validEmails = array_filter($emails, function ($email) { |
|
| 727 | - return $this->emailValidator->isValid($email); |
|
| 728 | - }); |
|
| 729 | - $this->sendPassword($share, $plainTextPassword, $validEmails); |
|
| 730 | - } |
|
| 731 | - |
|
| 732 | - $shareAttributes = $this->formatShareAttributes($share->getAttributes()); |
|
| 733 | - |
|
| 734 | - /* |
|
| 49 | + /** |
|
| 50 | + * Return the identifier of this provider. |
|
| 51 | + * |
|
| 52 | + * @return string Containing only [a-zA-Z0-9] |
|
| 53 | + */ |
|
| 54 | + public function identifier(): string { |
|
| 55 | + return 'ocMailShare'; |
|
| 56 | + } |
|
| 57 | + |
|
| 58 | + public function __construct( |
|
| 59 | + private IConfig $config, |
|
| 60 | + private IDBConnection $dbConnection, |
|
| 61 | + private ISecureRandom $secureRandom, |
|
| 62 | + private IUserManager $userManager, |
|
| 63 | + private IRootFolder $rootFolder, |
|
| 64 | + private IL10N $l, |
|
| 65 | + private LoggerInterface $logger, |
|
| 66 | + private IMailer $mailer, |
|
| 67 | + private IURLGenerator $urlGenerator, |
|
| 68 | + private IManager $activityManager, |
|
| 69 | + private SettingsManager $settingsManager, |
|
| 70 | + private Defaults $defaults, |
|
| 71 | + private IHasher $hasher, |
|
| 72 | + private IEventDispatcher $eventDispatcher, |
|
| 73 | + private IShareManager $shareManager, |
|
| 74 | + private IEmailValidator $emailValidator, |
|
| 75 | + ) { |
|
| 76 | + } |
|
| 77 | + |
|
| 78 | + /** |
|
| 79 | + * Share a path |
|
| 80 | + * |
|
| 81 | + * @throws ShareNotFound |
|
| 82 | + * @throws \Exception |
|
| 83 | + */ |
|
| 84 | + public function create(IShare $share): IShare { |
|
| 85 | + $shareWith = $share->getSharedWith(); |
|
| 86 | + // Check if file is not already shared with the given email, |
|
| 87 | + // if we have an email at all. |
|
| 88 | + $alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_EMAIL, $share->getNode(), 1, 0); |
|
| 89 | + if ($shareWith !== '' && !empty($alreadyShared)) { |
|
| 90 | + $message = 'Sharing %1$s failed, because this item is already shared with the account %2$s'; |
|
| 91 | + $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with the account %2$s', [$share->getNode()->getName(), $shareWith]); |
|
| 92 | + $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']); |
|
| 93 | + throw new \Exception($message_t); |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + // if the admin enforces a password for all mail shares we create a |
|
| 97 | + // random password and send it to the recipient |
|
| 98 | + $password = $share->getPassword() ?: ''; |
|
| 99 | + $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword(); |
|
| 100 | + if ($passwordEnforced && empty($password)) { |
|
| 101 | + $password = $this->autoGeneratePassword($share); |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + if (!empty($password)) { |
|
| 105 | + $share->setPassword($this->hasher->hash($password)); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + $shareId = $this->createMailShare($share); |
|
| 109 | + |
|
| 110 | + $this->createShareActivity($share); |
|
| 111 | + $data = $this->getRawShare($shareId); |
|
| 112 | + |
|
| 113 | + // Temporary set the clear password again to send it by mail |
|
| 114 | + // This need to be done after the share was created in the database |
|
| 115 | + // as the password is hashed in between. |
|
| 116 | + if (!empty($password)) { |
|
| 117 | + $data['password'] = $password; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + return $this->createShareObject($data); |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + /** |
|
| 124 | + * auto generate password in case of password enforcement on mail shares |
|
| 125 | + * |
|
| 126 | + * @throws \Exception |
|
| 127 | + */ |
|
| 128 | + protected function autoGeneratePassword(IShare $share): string { |
|
| 129 | + $initiatorUser = $this->userManager->get($share->getSharedBy()); |
|
| 130 | + $initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 131 | + $allowPasswordByMail = $this->settingsManager->sendPasswordByMail(); |
|
| 132 | + |
|
| 133 | + if ($initiatorEMailAddress === null && !$allowPasswordByMail) { |
|
| 134 | + throw new \Exception( |
|
| 135 | + $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.') |
|
| 136 | + ); |
|
| 137 | + } |
|
| 138 | + |
|
| 139 | + $passwordEvent = new GenerateSecurePasswordEvent(PasswordContext::SHARING); |
|
| 140 | + $this->eventDispatcher->dispatchTyped($passwordEvent); |
|
| 141 | + |
|
| 142 | + $password = $passwordEvent->getPassword(); |
|
| 143 | + if ($password === null) { |
|
| 144 | + $password = $this->secureRandom->generate(8, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + return $password; |
|
| 148 | + } |
|
| 149 | + |
|
| 150 | + /** |
|
| 151 | + * create activity if a file/folder was shared by mail |
|
| 152 | + */ |
|
| 153 | + protected function createShareActivity(IShare $share, string $type = 'share'): void { |
|
| 154 | + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); |
|
| 155 | + |
|
| 156 | + $this->publishActivity( |
|
| 157 | + $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_SELF : Activity::SUBJECT_UNSHARED_EMAIL_SELF, |
|
| 158 | + [$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()], |
|
| 159 | + $share->getSharedBy(), |
|
| 160 | + $share->getNode()->getId(), |
|
| 161 | + (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 162 | + ); |
|
| 163 | + |
|
| 164 | + if ($share->getShareOwner() !== $share->getSharedBy()) { |
|
| 165 | + $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); |
|
| 166 | + $fileId = $share->getNode()->getId(); |
|
| 167 | + $nodes = $ownerFolder->getById($fileId); |
|
| 168 | + $ownerPath = $nodes[0]->getPath(); |
|
| 169 | + $this->publishActivity( |
|
| 170 | + $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_BY : Activity::SUBJECT_UNSHARED_EMAIL_BY, |
|
| 171 | + [$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()], |
|
| 172 | + $share->getShareOwner(), |
|
| 173 | + $fileId, |
|
| 174 | + (string)$ownerFolder->getRelativePath($ownerPath) |
|
| 175 | + ); |
|
| 176 | + } |
|
| 177 | + } |
|
| 178 | + |
|
| 179 | + /** |
|
| 180 | + * create activity if a file/folder was shared by mail |
|
| 181 | + */ |
|
| 182 | + protected function createPasswordSendActivity(IShare $share, string $sharedWith, bool $sendToSelf): void { |
|
| 183 | + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); |
|
| 184 | + |
|
| 185 | + if ($sendToSelf) { |
|
| 186 | + $this->publishActivity( |
|
| 187 | + Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF, |
|
| 188 | + [$userFolder->getRelativePath($share->getNode()->getPath())], |
|
| 189 | + $share->getSharedBy(), |
|
| 190 | + $share->getNode()->getId(), |
|
| 191 | + (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 192 | + ); |
|
| 193 | + } else { |
|
| 194 | + $this->publishActivity( |
|
| 195 | + Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND, |
|
| 196 | + [$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith], |
|
| 197 | + $share->getSharedBy(), |
|
| 198 | + $share->getNode()->getId(), |
|
| 199 | + (string)$userFolder->getRelativePath($share->getNode()->getPath()) |
|
| 200 | + ); |
|
| 201 | + } |
|
| 202 | + } |
|
| 203 | + |
|
| 204 | + |
|
| 205 | + /** |
|
| 206 | + * publish activity if a file/folder was shared by mail |
|
| 207 | + */ |
|
| 208 | + protected function publishActivity(string $subject, array $parameters, string $affectedUser, int $fileId, string $filePath): void { |
|
| 209 | + $event = $this->activityManager->generateEvent(); |
|
| 210 | + $event->setApp('sharebymail') |
|
| 211 | + ->setType('shared') |
|
| 212 | + ->setSubject($subject, $parameters) |
|
| 213 | + ->setAffectedUser($affectedUser) |
|
| 214 | + ->setObject('files', $fileId, $filePath); |
|
| 215 | + $this->activityManager->publish($event); |
|
| 216 | + } |
|
| 217 | + |
|
| 218 | + /** |
|
| 219 | + * @throws \Exception |
|
| 220 | + */ |
|
| 221 | + protected function createMailShare(IShare $share): int { |
|
| 222 | + $share->setToken($this->generateToken()); |
|
| 223 | + return $this->addShareToDB( |
|
| 224 | + $share->getNodeId(), |
|
| 225 | + $share->getNodeType(), |
|
| 226 | + $share->getSharedWith(), |
|
| 227 | + $share->getSharedBy(), |
|
| 228 | + $share->getShareOwner(), |
|
| 229 | + $share->getPermissions(), |
|
| 230 | + $share->getToken(), |
|
| 231 | + $share->getPassword(), |
|
| 232 | + $share->getPasswordExpirationTime(), |
|
| 233 | + $share->getSendPasswordByTalk(), |
|
| 234 | + $share->getHideDownload(), |
|
| 235 | + $share->getLabel(), |
|
| 236 | + $share->getExpirationDate(), |
|
| 237 | + $share->getNote(), |
|
| 238 | + $share->getAttributes(), |
|
| 239 | + $share->getMailSend(), |
|
| 240 | + ); |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + /** |
|
| 244 | + * @inheritDoc |
|
| 245 | + */ |
|
| 246 | + public function sendMailNotification(IShare $share): bool { |
|
| 247 | + $shareId = $share->getId(); |
|
| 248 | + |
|
| 249 | + $emails = $this->getSharedWithEmails($share); |
|
| 250 | + $validEmails = array_filter($emails, function (string $email) { |
|
| 251 | + return $this->emailValidator->isValid($email); |
|
| 252 | + }); |
|
| 253 | + |
|
| 254 | + if (count($validEmails) === 0) { |
|
| 255 | + $this->removeShareFromTable((int)$shareId); |
|
| 256 | + $e = new HintException('Failed to send share by mail. Could not find a valid email address: ' . join(', ', $emails), |
|
| 257 | + $this->l->t('Failed to send share by email. Got an invalid email address')); |
|
| 258 | + $this->logger->error('Failed to send share by mail. Could not find a valid email address ' . join(', ', $emails), [ |
|
| 259 | + 'app' => 'sharebymail', |
|
| 260 | + 'exception' => $e, |
|
| 261 | + ]); |
|
| 262 | + } |
|
| 263 | + |
|
| 264 | + try { |
|
| 265 | + $this->sendEmail($share, $validEmails); |
|
| 266 | + |
|
| 267 | + // If we have a password set, we send it to the recipient |
|
| 268 | + if ($share->getPassword() !== null) { |
|
| 269 | + // If share-by-talk password is enabled, we do not send the notification |
|
| 270 | + // to the recipient. They will have to request it to the owner after opening the link. |
|
| 271 | + // Secondly, if the password expiration is disabled, we send the notification to the recipient |
|
| 272 | + // Lastly, if the mail to recipient failed, we send the password to the owner as a fallback. |
|
| 273 | + // If a password expires, the recipient will still be able to request a new one via talk. |
|
| 274 | + $passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false); |
|
| 275 | + $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword(); |
|
| 276 | + if ($passwordExpire === false || $share->getSendPasswordByTalk()) { |
|
| 277 | + $send = $this->sendPassword($share, $share->getPassword(), $validEmails); |
|
| 278 | + if ($passwordEnforced && $send === false) { |
|
| 279 | + $this->sendPasswordToOwner($share, $share->getPassword()); |
|
| 280 | + } |
|
| 281 | + } |
|
| 282 | + } |
|
| 283 | + |
|
| 284 | + return true; |
|
| 285 | + } catch (HintException $hintException) { |
|
| 286 | + $this->logger->error('Failed to send share by mail.', [ |
|
| 287 | + 'app' => 'sharebymail', |
|
| 288 | + 'exception' => $hintException, |
|
| 289 | + ]); |
|
| 290 | + $this->removeShareFromTable((int)$shareId); |
|
| 291 | + throw $hintException; |
|
| 292 | + } catch (\Exception $e) { |
|
| 293 | + $this->logger->error('Failed to send share by mail.', [ |
|
| 294 | + 'app' => 'sharebymail', |
|
| 295 | + 'exception' => $e, |
|
| 296 | + ]); |
|
| 297 | + $this->removeShareFromTable((int)$shareId); |
|
| 298 | + throw new HintException( |
|
| 299 | + 'Failed to send share by mail', |
|
| 300 | + $this->l->t('Failed to send share by email'), |
|
| 301 | + 0, |
|
| 302 | + $e, |
|
| 303 | + ); |
|
| 304 | + } |
|
| 305 | + return false; |
|
| 306 | + } |
|
| 307 | + |
|
| 308 | + /** |
|
| 309 | + * @param IShare $share The share to send the email for |
|
| 310 | + * @param array $emails The email addresses to send the email to |
|
| 311 | + */ |
|
| 312 | + protected function sendEmail(IShare $share, array $emails): void { |
|
| 313 | + $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [ |
|
| 314 | + 'token' => $share->getToken() |
|
| 315 | + ]); |
|
| 316 | + |
|
| 317 | + $expiration = $share->getExpirationDate(); |
|
| 318 | + $filename = $share->getNode()->getName(); |
|
| 319 | + $initiator = $share->getSharedBy(); |
|
| 320 | + $note = $share->getNote(); |
|
| 321 | + $shareWith = $share->getSharedWith(); |
|
| 322 | + |
|
| 323 | + $initiatorUser = $this->userManager->get($initiator); |
|
| 324 | + $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 325 | + $message = $this->mailer->createMessage(); |
|
| 326 | + |
|
| 327 | + $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [ |
|
| 328 | + 'filename' => $filename, |
|
| 329 | + 'link' => $link, |
|
| 330 | + 'initiator' => $initiatorDisplayName, |
|
| 331 | + 'expiration' => $expiration, |
|
| 332 | + 'shareWith' => $shareWith, |
|
| 333 | + 'note' => $note |
|
| 334 | + ]); |
|
| 335 | + |
|
| 336 | + $emailTemplate->setSubject($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename])); |
|
| 337 | + $emailTemplate->addHeader(); |
|
| 338 | + $emailTemplate->addHeading($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false); |
|
| 339 | + |
|
| 340 | + if ($note !== '') { |
|
| 341 | + $emailTemplate->addBodyListItem( |
|
| 342 | + htmlspecialchars($note), |
|
| 343 | + $this->l->t('Note:'), |
|
| 344 | + $this->getAbsoluteImagePath('caldav/description.png'), |
|
| 345 | + $note |
|
| 346 | + ); |
|
| 347 | + } |
|
| 348 | + |
|
| 349 | + if ($expiration !== null) { |
|
| 350 | + $dateString = (string)$this->l->l('date', $expiration, ['width' => 'medium']); |
|
| 351 | + $emailTemplate->addBodyListItem( |
|
| 352 | + $this->l->t('This share is valid until %s at midnight', [$dateString]), |
|
| 353 | + $this->l->t('Expiration:'), |
|
| 354 | + $this->getAbsoluteImagePath('caldav/time.png'), |
|
| 355 | + ); |
|
| 356 | + } |
|
| 357 | + |
|
| 358 | + $emailTemplate->addBodyButton( |
|
| 359 | + $this->l->t('Open %s', [$filename]), |
|
| 360 | + $link |
|
| 361 | + ); |
|
| 362 | + |
|
| 363 | + // If multiple recipients are given, we send the mail to all of them |
|
| 364 | + if (count($emails) > 1) { |
|
| 365 | + // We do not want to expose the email addresses of the other recipients |
|
| 366 | + $message->setBcc($emails); |
|
| 367 | + } else { |
|
| 368 | + $message->setTo($emails); |
|
| 369 | + } |
|
| 370 | + |
|
| 371 | + // The "From" contains the sharers name |
|
| 372 | + $instanceName = $this->defaults->getName(); |
|
| 373 | + $senderName = $instanceName; |
|
| 374 | + if ($this->settingsManager->replyToInitiator()) { |
|
| 375 | + $senderName = $this->l->t( |
|
| 376 | + '%1$s via %2$s', |
|
| 377 | + [ |
|
| 378 | + $initiatorDisplayName, |
|
| 379 | + $instanceName |
|
| 380 | + ] |
|
| 381 | + ); |
|
| 382 | + } |
|
| 383 | + $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 384 | + |
|
| 385 | + // The "Reply-To" is set to the sharer if an mail address is configured |
|
| 386 | + // also the default footer contains a "Do not reply" which needs to be adjusted. |
|
| 387 | + if ($initiatorUser && $this->settingsManager->replyToInitiator()) { |
|
| 388 | + $initiatorEmail = $initiatorUser->getEMailAddress(); |
|
| 389 | + if ($initiatorEmail !== null) { |
|
| 390 | + $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]); |
|
| 391 | + $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')); |
|
| 392 | + } else { |
|
| 393 | + $emailTemplate->addFooter(); |
|
| 394 | + } |
|
| 395 | + } else { |
|
| 396 | + $emailTemplate->addFooter(); |
|
| 397 | + } |
|
| 398 | + |
|
| 399 | + $message->useTemplate($emailTemplate); |
|
| 400 | + $failedRecipients = $this->mailer->send($message); |
|
| 401 | + if (!empty($failedRecipients)) { |
|
| 402 | + $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients)); |
|
| 403 | + return; |
|
| 404 | + } |
|
| 405 | + } |
|
| 406 | + |
|
| 407 | + /** |
|
| 408 | + * Send password to recipient of a mail share |
|
| 409 | + * Will return false if |
|
| 410 | + * 1. the password is empty |
|
| 411 | + * 2. the setting to send the password by mail is disabled |
|
| 412 | + * 3. the share is set to send the password by talk |
|
| 413 | + * |
|
| 414 | + * @param IShare $share |
|
| 415 | + * @param string $password |
|
| 416 | + * @param array $emails |
|
| 417 | + * @return bool |
|
| 418 | + */ |
|
| 419 | + protected function sendPassword(IShare $share, string $password, array $emails): bool { |
|
| 420 | + $filename = $share->getNode()->getName(); |
|
| 421 | + $initiator = $share->getSharedBy(); |
|
| 422 | + $shareWith = $share->getSharedWith(); |
|
| 423 | + |
|
| 424 | + if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) { |
|
| 425 | + return false; |
|
| 426 | + } |
|
| 427 | + |
|
| 428 | + $initiatorUser = $this->userManager->get($initiator); |
|
| 429 | + $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 430 | + $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 431 | + |
|
| 432 | + $plainBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]); |
|
| 433 | + $htmlBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]); |
|
| 434 | + |
|
| 435 | + $message = $this->mailer->createMessage(); |
|
| 436 | + |
|
| 437 | + $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [ |
|
| 438 | + 'filename' => $filename, |
|
| 439 | + 'password' => $password, |
|
| 440 | + 'initiator' => $initiatorDisplayName, |
|
| 441 | + 'initiatorEmail' => $initiatorEmailAddress, |
|
| 442 | + 'shareWith' => $shareWith, |
|
| 443 | + ]); |
|
| 444 | + |
|
| 445 | + $emailTemplate->setSubject($this->l->t('Password to access %1$s shared to you by %2$s', [$filename, $initiatorDisplayName])); |
|
| 446 | + $emailTemplate->addHeader(); |
|
| 447 | + $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false); |
|
| 448 | + $emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart); |
|
| 449 | + $emailTemplate->addBodyText($this->l->t('It is protected with the following password:')); |
|
| 450 | + $emailTemplate->addBodyText($password); |
|
| 451 | + |
|
| 452 | + if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) { |
|
| 453 | + $expirationTime = new \DateTime(); |
|
| 454 | + $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600); |
|
| 455 | + $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S')); |
|
| 456 | + $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')])); |
|
| 457 | + } |
|
| 458 | + |
|
| 459 | + // If multiple recipients are given, we send the mail to all of them |
|
| 460 | + if (count($emails) > 1) { |
|
| 461 | + // We do not want to expose the email addresses of the other recipients |
|
| 462 | + $message->setBcc($emails); |
|
| 463 | + } else { |
|
| 464 | + $message->setTo($emails); |
|
| 465 | + } |
|
| 466 | + |
|
| 467 | + // The "From" contains the sharers name |
|
| 468 | + $instanceName = $this->defaults->getName(); |
|
| 469 | + $senderName = $instanceName; |
|
| 470 | + if ($this->settingsManager->replyToInitiator()) { |
|
| 471 | + $senderName = $this->l->t( |
|
| 472 | + '%1$s via %2$s', |
|
| 473 | + [ |
|
| 474 | + $initiatorDisplayName, |
|
| 475 | + $instanceName |
|
| 476 | + ] |
|
| 477 | + ); |
|
| 478 | + } |
|
| 479 | + $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 480 | + |
|
| 481 | + // The "Reply-To" is set to the sharer if an mail address is configured |
|
| 482 | + // also the default footer contains a "Do not reply" which needs to be adjusted. |
|
| 483 | + if ($initiatorUser && $this->settingsManager->replyToInitiator()) { |
|
| 484 | + $initiatorEmail = $initiatorUser->getEMailAddress(); |
|
| 485 | + if ($initiatorEmail !== null) { |
|
| 486 | + $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]); |
|
| 487 | + $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')); |
|
| 488 | + } else { |
|
| 489 | + $emailTemplate->addFooter(); |
|
| 490 | + } |
|
| 491 | + } else { |
|
| 492 | + $emailTemplate->addFooter(); |
|
| 493 | + } |
|
| 494 | + |
|
| 495 | + $message->useTemplate($emailTemplate); |
|
| 496 | + $failedRecipients = $this->mailer->send($message); |
|
| 497 | + if (!empty($failedRecipients)) { |
|
| 498 | + $this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients)); |
|
| 499 | + return false; |
|
| 500 | + } |
|
| 501 | + |
|
| 502 | + $this->createPasswordSendActivity($share, $shareWith, false); |
|
| 503 | + return true; |
|
| 504 | + } |
|
| 505 | + |
|
| 506 | + protected function sendNote(IShare $share): void { |
|
| 507 | + $recipient = $share->getSharedWith(); |
|
| 508 | + |
|
| 509 | + |
|
| 510 | + $filename = $share->getNode()->getName(); |
|
| 511 | + $initiator = $share->getSharedBy(); |
|
| 512 | + $note = $share->getNote(); |
|
| 513 | + |
|
| 514 | + $initiatorUser = $this->userManager->get($initiator); |
|
| 515 | + $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; |
|
| 516 | + $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; |
|
| 517 | + |
|
| 518 | + $plainHeading = $this->l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]); |
|
| 519 | + $htmlHeading = $this->l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]); |
|
| 520 | + |
|
| 521 | + $message = $this->mailer->createMessage(); |
|
| 522 | + |
|
| 523 | + $emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote'); |
|
| 524 | + |
|
| 525 | + $emailTemplate->setSubject($this->l->t('%s added a note to a file shared with you', [$initiatorDisplayName])); |
|
| 526 | + $emailTemplate->addHeader(); |
|
| 527 | + $emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading); |
|
| 528 | + $emailTemplate->addBodyText(htmlspecialchars($note), $note); |
|
| 529 | + |
|
| 530 | + $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', |
|
| 531 | + ['token' => $share->getToken()]); |
|
| 532 | + $emailTemplate->addBodyButton( |
|
| 533 | + $this->l->t('Open %s', [$filename]), |
|
| 534 | + $link |
|
| 535 | + ); |
|
| 536 | + |
|
| 537 | + // The "From" contains the sharers name |
|
| 538 | + $instanceName = $this->defaults->getName(); |
|
| 539 | + $senderName = $instanceName; |
|
| 540 | + if ($this->settingsManager->replyToInitiator()) { |
|
| 541 | + $senderName = $this->l->t( |
|
| 542 | + '%1$s via %2$s', |
|
| 543 | + [ |
|
| 544 | + $initiatorDisplayName, |
|
| 545 | + $instanceName |
|
| 546 | + ] |
|
| 547 | + ); |
|
| 548 | + } |
|
| 549 | + $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 550 | + if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) { |
|
| 551 | + $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]); |
|
| 552 | + $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan()); |
|
| 553 | + } else { |
|
| 554 | + $emailTemplate->addFooter(); |
|
| 555 | + } |
|
| 556 | + |
|
| 557 | + $message->setTo([$recipient]); |
|
| 558 | + $message->useTemplate($emailTemplate); |
|
| 559 | + $this->mailer->send($message); |
|
| 560 | + } |
|
| 561 | + |
|
| 562 | + /** |
|
| 563 | + * send auto generated password to the owner. This happens if the admin enforces |
|
| 564 | + * a password for mail shares and forbid to send the password by mail to the recipient |
|
| 565 | + * |
|
| 566 | + * @throws \Exception |
|
| 567 | + */ |
|
| 568 | + protected function sendPasswordToOwner(IShare $share, string $password): bool { |
|
| 569 | + $filename = $share->getNode()->getName(); |
|
| 570 | + $initiator = $this->userManager->get($share->getSharedBy()); |
|
| 571 | + $initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null; |
|
| 572 | + $initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy(); |
|
| 573 | + $shareWith = implode(', ', $this->getSharedWithEmails($share)); |
|
| 574 | + |
|
| 575 | + if ($initiatorEMailAddress === null) { |
|
| 576 | + throw new \Exception( |
|
| 577 | + $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.') |
|
| 578 | + ); |
|
| 579 | + } |
|
| 580 | + |
|
| 581 | + $bodyPart = $this->l->t('You just shared %1$s with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.', [$filename, $shareWith, $this->defaults->getName()]); |
|
| 582 | + |
|
| 583 | + $message = $this->mailer->createMessage(); |
|
| 584 | + $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [ |
|
| 585 | + 'filename' => $filename, |
|
| 586 | + 'password' => $password, |
|
| 587 | + 'initiator' => $initiatorDisplayName, |
|
| 588 | + 'initiatorEmail' => $initiatorEMailAddress, |
|
| 589 | + 'shareWith' => $shareWith, |
|
| 590 | + ]); |
|
| 591 | + |
|
| 592 | + $emailTemplate->setSubject($this->l->t('Password to access %1$s shared by you with %2$s', [$filename, $shareWith])); |
|
| 593 | + $emailTemplate->addHeader(); |
|
| 594 | + $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false); |
|
| 595 | + $emailTemplate->addBodyText($bodyPart); |
|
| 596 | + $emailTemplate->addBodyText($this->l->t('This is the password:')); |
|
| 597 | + $emailTemplate->addBodyText($password); |
|
| 598 | + |
|
| 599 | + if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) { |
|
| 600 | + $expirationTime = new \DateTime(); |
|
| 601 | + $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600); |
|
| 602 | + $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S')); |
|
| 603 | + $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')])); |
|
| 604 | + } |
|
| 605 | + |
|
| 606 | + $emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.')); |
|
| 607 | + |
|
| 608 | + $emailTemplate->addFooter(); |
|
| 609 | + |
|
| 610 | + $instanceName = $this->defaults->getName(); |
|
| 611 | + $senderName = $this->l->t( |
|
| 612 | + '%1$s via %2$s', |
|
| 613 | + [ |
|
| 614 | + $initiatorDisplayName, |
|
| 615 | + $instanceName |
|
| 616 | + ] |
|
| 617 | + ); |
|
| 618 | + $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); |
|
| 619 | + $message->setTo([$initiatorEMailAddress => $initiatorDisplayName]); |
|
| 620 | + $message->useTemplate($emailTemplate); |
|
| 621 | + $this->mailer->send($message); |
|
| 622 | + |
|
| 623 | + $this->createPasswordSendActivity($share, $shareWith, true); |
|
| 624 | + |
|
| 625 | + return true; |
|
| 626 | + } |
|
| 627 | + |
|
| 628 | + private function getAbsoluteImagePath(string $path):string { |
|
| 629 | + return $this->urlGenerator->getAbsoluteURL( |
|
| 630 | + $this->urlGenerator->imagePath('core', $path) |
|
| 631 | + ); |
|
| 632 | + } |
|
| 633 | + |
|
| 634 | + /** |
|
| 635 | + * generate share token |
|
| 636 | + */ |
|
| 637 | + protected function generateToken(int $size = 15): string { |
|
| 638 | + $token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 639 | + return $token; |
|
| 640 | + } |
|
| 641 | + |
|
| 642 | + public function getChildren(IShare $parent): array { |
|
| 643 | + $children = []; |
|
| 644 | + |
|
| 645 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 646 | + $qb->select('*') |
|
| 647 | + ->from('share') |
|
| 648 | + ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId()))) |
|
| 649 | + ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 650 | + ->orderBy('id'); |
|
| 651 | + |
|
| 652 | + $cursor = $qb->executeQuery(); |
|
| 653 | + while ($data = $cursor->fetchAssociative()) { |
|
| 654 | + $children[] = $this->createShareObject($data); |
|
| 655 | + } |
|
| 656 | + $cursor->closeCursor(); |
|
| 657 | + |
|
| 658 | + return $children; |
|
| 659 | + } |
|
| 660 | + |
|
| 661 | + /** |
|
| 662 | + * Add share to the database and return the ID |
|
| 663 | + */ |
|
| 664 | + protected function addShareToDB( |
|
| 665 | + ?int $itemSource, |
|
| 666 | + ?string $itemType, |
|
| 667 | + ?string $shareWith, |
|
| 668 | + ?string $sharedBy, |
|
| 669 | + ?string $uidOwner, |
|
| 670 | + ?int $permissions, |
|
| 671 | + ?string $token, |
|
| 672 | + ?string $password, |
|
| 673 | + ?\DateTimeInterface $passwordExpirationTime, |
|
| 674 | + ?bool $sendPasswordByTalk, |
|
| 675 | + ?bool $hideDownload, |
|
| 676 | + ?string $label, |
|
| 677 | + ?\DateTimeInterface $expirationTime, |
|
| 678 | + ?string $note = '', |
|
| 679 | + ?IAttributes $attributes = null, |
|
| 680 | + ?bool $mailSend = true, |
|
| 681 | + ): int { |
|
| 682 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 683 | + $qb->insert('share') |
|
| 684 | + ->setValue('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 685 | + ->setValue('item_type', $qb->createNamedParameter($itemType)) |
|
| 686 | + ->setValue('item_source', $qb->createNamedParameter($itemSource)) |
|
| 687 | + ->setValue('file_source', $qb->createNamedParameter($itemSource)) |
|
| 688 | + ->setValue('share_with', $qb->createNamedParameter($shareWith)) |
|
| 689 | + ->setValue('uid_owner', $qb->createNamedParameter($uidOwner)) |
|
| 690 | + ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy)) |
|
| 691 | + ->setValue('permissions', $qb->createNamedParameter($permissions)) |
|
| 692 | + ->setValue('token', $qb->createNamedParameter($token)) |
|
| 693 | + ->setValue('password', $qb->createNamedParameter($password)) |
|
| 694 | + ->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 695 | + ->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL)) |
|
| 696 | + ->setValue('stime', $qb->createNamedParameter(time())) |
|
| 697 | + ->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT)) |
|
| 698 | + ->setValue('label', $qb->createNamedParameter($label)) |
|
| 699 | + ->setValue('note', $qb->createNamedParameter($note)) |
|
| 700 | + ->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT)); |
|
| 701 | + |
|
| 702 | + // set share attributes |
|
| 703 | + $shareAttributes = $this->formatShareAttributes($attributes); |
|
| 704 | + |
|
| 705 | + $qb->setValue('attributes', $qb->createNamedParameter($shareAttributes)); |
|
| 706 | + if ($expirationTime !== null) { |
|
| 707 | + $qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE)); |
|
| 708 | + } |
|
| 709 | + |
|
| 710 | + $qb->executeStatement(); |
|
| 711 | + return $qb->getLastInsertId(); |
|
| 712 | + } |
|
| 713 | + |
|
| 714 | + /** |
|
| 715 | + * Update a share |
|
| 716 | + */ |
|
| 717 | + public function update(IShare $share, ?string $plainTextPassword = null): IShare { |
|
| 718 | + $originalShare = $this->getShareById($share->getId()); |
|
| 719 | + |
|
| 720 | + // a real password was given |
|
| 721 | + $validPassword = $plainTextPassword !== null && $plainTextPassword !== ''; |
|
| 722 | + |
|
| 723 | + if ($validPassword && ($originalShare->getPassword() !== $share->getPassword() |
|
| 724 | + || ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) { |
|
| 725 | + $emails = $this->getSharedWithEmails($share); |
|
| 726 | + $validEmails = array_filter($emails, function ($email) { |
|
| 727 | + return $this->emailValidator->isValid($email); |
|
| 728 | + }); |
|
| 729 | + $this->sendPassword($share, $plainTextPassword, $validEmails); |
|
| 730 | + } |
|
| 731 | + |
|
| 732 | + $shareAttributes = $this->formatShareAttributes($share->getAttributes()); |
|
| 733 | + |
|
| 734 | + /* |
|
| 735 | 735 | * We allow updating mail shares |
| 736 | 736 | */ |
| 737 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 738 | - $qb->update('share') |
|
| 739 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) |
|
| 740 | - ->set('item_source', $qb->createNamedParameter($share->getNodeId())) |
|
| 741 | - ->set('file_source', $qb->createNamedParameter($share->getNodeId())) |
|
| 742 | - ->set('share_with', $qb->createNamedParameter($share->getSharedWith())) |
|
| 743 | - ->set('permissions', $qb->createNamedParameter($share->getPermissions())) |
|
| 744 | - ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) |
|
| 745 | - ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) |
|
| 746 | - ->set('password', $qb->createNamedParameter($share->getPassword())) |
|
| 747 | - ->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 748 | - ->set('label', $qb->createNamedParameter($share->getLabel())) |
|
| 749 | - ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL)) |
|
| 750 | - ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 751 | - ->set('note', $qb->createNamedParameter($share->getNote())) |
|
| 752 | - ->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT)) |
|
| 753 | - ->set('attributes', $qb->createNamedParameter($shareAttributes)) |
|
| 754 | - ->set('mail_send', $qb->createNamedParameter((int)$share->getMailSend(), IQueryBuilder::PARAM_INT)) |
|
| 755 | - ->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL)) |
|
| 756 | - ->executeStatement(); |
|
| 757 | - |
|
| 758 | - if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') { |
|
| 759 | - $this->sendNote($share); |
|
| 760 | - } |
|
| 761 | - |
|
| 762 | - return $share; |
|
| 763 | - } |
|
| 764 | - |
|
| 765 | - /** |
|
| 766 | - * @inheritdoc |
|
| 767 | - */ |
|
| 768 | - public function move(IShare $share, $recipient): IShare { |
|
| 769 | - /** |
|
| 770 | - * nothing to do here, mail shares are only outgoing shares |
|
| 771 | - */ |
|
| 772 | - return $share; |
|
| 773 | - } |
|
| 774 | - |
|
| 775 | - /** |
|
| 776 | - * Delete a share (owner unShares the file) |
|
| 777 | - * |
|
| 778 | - * @param IShare $share |
|
| 779 | - */ |
|
| 780 | - public function delete(IShare $share): void { |
|
| 781 | - try { |
|
| 782 | - $this->createShareActivity($share, 'unshare'); |
|
| 783 | - } catch (\Exception $e) { |
|
| 784 | - } |
|
| 785 | - |
|
| 786 | - $this->removeShareFromTable((int)$share->getId()); |
|
| 787 | - } |
|
| 788 | - |
|
| 789 | - /** |
|
| 790 | - * @inheritdoc |
|
| 791 | - */ |
|
| 792 | - public function deleteFromSelf(IShare $share, $recipient): void { |
|
| 793 | - // nothing to do here, mail shares are only outgoing shares |
|
| 794 | - } |
|
| 795 | - |
|
| 796 | - public function restore(IShare $share, string $recipient): IShare { |
|
| 797 | - throw new GenericShareException('not implemented'); |
|
| 798 | - } |
|
| 799 | - |
|
| 800 | - /** |
|
| 801 | - * @inheritdoc |
|
| 802 | - */ |
|
| 803 | - public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset): array { |
|
| 804 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 805 | - $qb->select('*') |
|
| 806 | - ->from('share'); |
|
| 807 | - |
|
| 808 | - $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 809 | - |
|
| 810 | - /** |
|
| 811 | - * Reshares for this user are shares where they are the owner. |
|
| 812 | - */ |
|
| 813 | - if ($reshares === false) { |
|
| 814 | - //Special case for old shares created via the web UI |
|
| 815 | - $or1 = $qb->expr()->andX( |
|
| 816 | - $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 817 | - $qb->expr()->isNull('uid_initiator') |
|
| 818 | - ); |
|
| 819 | - |
|
| 820 | - $qb->andWhere( |
|
| 821 | - $qb->expr()->orX( |
|
| 822 | - $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)), |
|
| 823 | - $or1 |
|
| 824 | - ) |
|
| 825 | - ); |
|
| 826 | - } elseif ($node === null) { |
|
| 827 | - $qb->andWhere( |
|
| 828 | - $qb->expr()->orX( |
|
| 829 | - $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 830 | - $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) |
|
| 831 | - ) |
|
| 832 | - ); |
|
| 833 | - } |
|
| 834 | - |
|
| 835 | - if ($node !== null) { |
|
| 836 | - $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); |
|
| 837 | - } |
|
| 838 | - |
|
| 839 | - if ($limit !== -1) { |
|
| 840 | - $qb->setMaxResults($limit); |
|
| 841 | - } |
|
| 842 | - |
|
| 843 | - $qb->setFirstResult($offset); |
|
| 844 | - $qb->orderBy('id'); |
|
| 845 | - |
|
| 846 | - $cursor = $qb->executeQuery(); |
|
| 847 | - $shares = []; |
|
| 848 | - while ($data = $cursor->fetchAssociative()) { |
|
| 849 | - $shares[] = $this->createShareObject($data); |
|
| 850 | - } |
|
| 851 | - $cursor->closeCursor(); |
|
| 852 | - |
|
| 853 | - return $shares; |
|
| 854 | - } |
|
| 855 | - |
|
| 856 | - /** |
|
| 857 | - * @inheritdoc |
|
| 858 | - */ |
|
| 859 | - public function getShareById($id, $recipientId = null): IShare { |
|
| 860 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 861 | - |
|
| 862 | - $qb->select('*') |
|
| 863 | - ->from('share') |
|
| 864 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) |
|
| 865 | - ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 866 | - |
|
| 867 | - $cursor = $qb->executeQuery(); |
|
| 868 | - $data = $cursor->fetchAssociative(); |
|
| 869 | - $cursor->closeCursor(); |
|
| 870 | - |
|
| 871 | - if ($data === false) { |
|
| 872 | - throw new ShareNotFound(); |
|
| 873 | - } |
|
| 874 | - |
|
| 875 | - try { |
|
| 876 | - $share = $this->createShareObject($data); |
|
| 877 | - } catch (InvalidShare $e) { |
|
| 878 | - throw new ShareNotFound(); |
|
| 879 | - } |
|
| 880 | - |
|
| 881 | - return $share; |
|
| 882 | - } |
|
| 883 | - |
|
| 884 | - /** |
|
| 885 | - * Get shares for a given path |
|
| 886 | - * |
|
| 887 | - * @return IShare[] |
|
| 888 | - */ |
|
| 889 | - public function getSharesByPath(Node $path): array { |
|
| 890 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 891 | - |
|
| 892 | - $cursor = $qb->select('*') |
|
| 893 | - ->from('share') |
|
| 894 | - ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId()))) |
|
| 895 | - ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 896 | - ->executeQuery(); |
|
| 897 | - |
|
| 898 | - $shares = []; |
|
| 899 | - while ($data = $cursor->fetchAssociative()) { |
|
| 900 | - $shares[] = $this->createShareObject($data); |
|
| 901 | - } |
|
| 902 | - $cursor->closeCursor(); |
|
| 903 | - |
|
| 904 | - return $shares; |
|
| 905 | - } |
|
| 906 | - |
|
| 907 | - /** |
|
| 908 | - * @inheritdoc |
|
| 909 | - */ |
|
| 910 | - public function getSharedWith($userId, $shareType, $node, $limit, $offset): array { |
|
| 911 | - /** @var IShare[] $shares */ |
|
| 912 | - $shares = []; |
|
| 913 | - |
|
| 914 | - //Get shares directly with this user |
|
| 915 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 916 | - $qb->select('*') |
|
| 917 | - ->from('share'); |
|
| 918 | - |
|
| 919 | - // Order by id |
|
| 920 | - $qb->orderBy('id'); |
|
| 921 | - |
|
| 922 | - // Set limit and offset |
|
| 923 | - if ($limit !== -1) { |
|
| 924 | - $qb->setMaxResults($limit); |
|
| 925 | - } |
|
| 926 | - $qb->setFirstResult($offset); |
|
| 927 | - |
|
| 928 | - $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 929 | - $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))); |
|
| 930 | - |
|
| 931 | - // Filter by node if provided |
|
| 932 | - if ($node !== null) { |
|
| 933 | - $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); |
|
| 934 | - } |
|
| 935 | - |
|
| 936 | - $cursor = $qb->executeQuery(); |
|
| 937 | - |
|
| 938 | - while ($data = $cursor->fetchAssociative()) { |
|
| 939 | - $shares[] = $this->createShareObject($data); |
|
| 940 | - } |
|
| 941 | - $cursor->closeCursor(); |
|
| 942 | - |
|
| 943 | - |
|
| 944 | - return $shares; |
|
| 945 | - } |
|
| 946 | - |
|
| 947 | - /** |
|
| 948 | - * Get a share by token |
|
| 949 | - * |
|
| 950 | - * @throws ShareNotFound |
|
| 951 | - */ |
|
| 952 | - public function getShareByToken($token): IShare { |
|
| 953 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 954 | - |
|
| 955 | - $cursor = $qb->select('*') |
|
| 956 | - ->from('share') |
|
| 957 | - ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 958 | - ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token))) |
|
| 959 | - ->executeQuery(); |
|
| 960 | - |
|
| 961 | - $data = $cursor->fetchAssociative(); |
|
| 962 | - |
|
| 963 | - if ($data === false) { |
|
| 964 | - throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); |
|
| 965 | - } |
|
| 966 | - |
|
| 967 | - try { |
|
| 968 | - $share = $this->createShareObject($data); |
|
| 969 | - } catch (InvalidShare $e) { |
|
| 970 | - throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); |
|
| 971 | - } |
|
| 972 | - |
|
| 973 | - return $share; |
|
| 974 | - } |
|
| 975 | - |
|
| 976 | - /** |
|
| 977 | - * remove share from table |
|
| 978 | - */ |
|
| 979 | - protected function removeShareFromTable(int $shareId): void { |
|
| 980 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 981 | - $qb->delete('share') |
|
| 982 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId))); |
|
| 983 | - $qb->executeStatement(); |
|
| 984 | - } |
|
| 985 | - |
|
| 986 | - /** |
|
| 987 | - * Create a share object from a database row |
|
| 988 | - * |
|
| 989 | - * @throws InvalidShare |
|
| 990 | - * @throws ShareNotFound |
|
| 991 | - */ |
|
| 992 | - protected function createShareObject(array $data): IShare { |
|
| 993 | - $share = new Share($this->rootFolder, $this->userManager); |
|
| 994 | - $share->setId((int)$data['id']) |
|
| 995 | - ->setShareType((int)$data['share_type']) |
|
| 996 | - ->setPermissions((int)$data['permissions']) |
|
| 997 | - ->setTarget($data['file_target']) |
|
| 998 | - ->setMailSend((bool)$data['mail_send']) |
|
| 999 | - ->setNote($data['note']) |
|
| 1000 | - ->setToken($data['token']); |
|
| 1001 | - |
|
| 1002 | - $shareTime = new \DateTime(); |
|
| 1003 | - $shareTime->setTimestamp((int)$data['stime']); |
|
| 1004 | - $share->setShareTime($shareTime); |
|
| 1005 | - $share->setSharedWith($data['share_with'] ?? ''); |
|
| 1006 | - $share->setPassword($data['password']); |
|
| 1007 | - $passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? ''); |
|
| 1008 | - $share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null); |
|
| 1009 | - $share->setLabel($data['label'] ?? ''); |
|
| 1010 | - $share->setSendPasswordByTalk((bool)$data['password_by_talk']); |
|
| 1011 | - $share->setHideDownload((bool)$data['hide_download']); |
|
| 1012 | - $share->setReminderSent((bool)$data['reminder_sent']); |
|
| 1013 | - |
|
| 1014 | - if ($data['uid_initiator'] !== null) { |
|
| 1015 | - $share->setShareOwner($data['uid_owner']); |
|
| 1016 | - $share->setSharedBy($data['uid_initiator']); |
|
| 1017 | - } else { |
|
| 1018 | - //OLD SHARE |
|
| 1019 | - $share->setSharedBy($data['uid_owner']); |
|
| 1020 | - $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']); |
|
| 1021 | - |
|
| 1022 | - $owner = $path->getOwner(); |
|
| 1023 | - $share->setShareOwner($owner->getUID()); |
|
| 1024 | - } |
|
| 1025 | - |
|
| 1026 | - if ($data['expiration'] !== null) { |
|
| 1027 | - $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']); |
|
| 1028 | - if ($expiration !== false) { |
|
| 1029 | - $share->setExpirationDate($expiration); |
|
| 1030 | - } |
|
| 1031 | - } |
|
| 1032 | - |
|
| 1033 | - $share = $this->updateShareAttributes($share, $data['attributes']); |
|
| 1034 | - |
|
| 1035 | - $share->setNodeId((int)$data['file_source']); |
|
| 1036 | - $share->setNodeType($data['item_type']); |
|
| 1037 | - |
|
| 1038 | - $share->setProviderId($this->identifier()); |
|
| 1039 | - |
|
| 1040 | - return $share; |
|
| 1041 | - } |
|
| 1042 | - |
|
| 1043 | - /** |
|
| 1044 | - * Get the node with file $id for $user |
|
| 1045 | - * |
|
| 1046 | - * @throws InvalidShare |
|
| 1047 | - */ |
|
| 1048 | - private function getNode(string $userId, int $id): Node { |
|
| 1049 | - try { |
|
| 1050 | - $userFolder = $this->rootFolder->getUserFolder($userId); |
|
| 1051 | - } catch (NoUserException $e) { |
|
| 1052 | - throw new InvalidShare(); |
|
| 1053 | - } |
|
| 1054 | - |
|
| 1055 | - $nodes = $userFolder->getById($id); |
|
| 1056 | - |
|
| 1057 | - if (empty($nodes)) { |
|
| 1058 | - throw new InvalidShare(); |
|
| 1059 | - } |
|
| 1060 | - |
|
| 1061 | - return $nodes[0]; |
|
| 1062 | - } |
|
| 1063 | - |
|
| 1064 | - /** |
|
| 1065 | - * A user is deleted from the system |
|
| 1066 | - * So clean up the relevant shares. |
|
| 1067 | - */ |
|
| 1068 | - public function userDeleted($uid, $shareType): void { |
|
| 1069 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1070 | - |
|
| 1071 | - $qb->delete('share') |
|
| 1072 | - ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 1073 | - ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))) |
|
| 1074 | - ->executeStatement(); |
|
| 1075 | - } |
|
| 1076 | - |
|
| 1077 | - /** |
|
| 1078 | - * This provider does not support group shares |
|
| 1079 | - */ |
|
| 1080 | - public function groupDeleted($gid): void { |
|
| 1081 | - } |
|
| 1082 | - |
|
| 1083 | - /** |
|
| 1084 | - * This provider does not support group shares |
|
| 1085 | - */ |
|
| 1086 | - public function userDeletedFromGroup($uid, $gid): void { |
|
| 1087 | - } |
|
| 1088 | - |
|
| 1089 | - /** |
|
| 1090 | - * get database row of a give share |
|
| 1091 | - * |
|
| 1092 | - * @throws ShareNotFound |
|
| 1093 | - */ |
|
| 1094 | - protected function getRawShare(int $id): array { |
|
| 1095 | - // Now fetch the inserted share and create a complete share object |
|
| 1096 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1097 | - $qb->select('*') |
|
| 1098 | - ->from('share') |
|
| 1099 | - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 1100 | - |
|
| 1101 | - $cursor = $qb->executeQuery(); |
|
| 1102 | - $data = $cursor->fetchAssociative(); |
|
| 1103 | - $cursor->closeCursor(); |
|
| 1104 | - |
|
| 1105 | - if ($data === false) { |
|
| 1106 | - throw new ShareNotFound; |
|
| 1107 | - } |
|
| 1108 | - |
|
| 1109 | - return $data; |
|
| 1110 | - } |
|
| 1111 | - |
|
| 1112 | - public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true): array { |
|
| 1113 | - return $this->getSharesInFolderInternal($userId, $node, $reshares); |
|
| 1114 | - } |
|
| 1115 | - |
|
| 1116 | - public function getAllSharesInFolder(Folder $node): array { |
|
| 1117 | - return $this->getSharesInFolderInternal(null, $node, null); |
|
| 1118 | - } |
|
| 1119 | - |
|
| 1120 | - /** |
|
| 1121 | - * @return array<int, list<IShare>> |
|
| 1122 | - */ |
|
| 1123 | - private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array { |
|
| 1124 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1125 | - $qb->select('*') |
|
| 1126 | - ->from('share', 's') |
|
| 1127 | - ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))) |
|
| 1128 | - ->andWhere( |
|
| 1129 | - $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 1130 | - ); |
|
| 1131 | - |
|
| 1132 | - if ($userId !== null) { |
|
| 1133 | - /** |
|
| 1134 | - * Reshares for this user are shares where they are the owner. |
|
| 1135 | - */ |
|
| 1136 | - if ($reshares !== true) { |
|
| 1137 | - $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); |
|
| 1138 | - } else { |
|
| 1139 | - $qb->andWhere( |
|
| 1140 | - $qb->expr()->orX( |
|
| 1141 | - $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 1142 | - $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) |
|
| 1143 | - ) |
|
| 1144 | - ); |
|
| 1145 | - } |
|
| 1146 | - } |
|
| 1147 | - |
|
| 1148 | - $qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')); |
|
| 1149 | - |
|
| 1150 | - $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId()))); |
|
| 1151 | - |
|
| 1152 | - $qb->orderBy('id'); |
|
| 1153 | - |
|
| 1154 | - $cursor = $qb->executeQuery(); |
|
| 1155 | - $shares = []; |
|
| 1156 | - while ($data = $cursor->fetchAssociative()) { |
|
| 1157 | - $shares[$data['fileid']][] = $this->createShareObject($data); |
|
| 1158 | - } |
|
| 1159 | - $cursor->closeCursor(); |
|
| 1160 | - |
|
| 1161 | - return $shares; |
|
| 1162 | - } |
|
| 1163 | - |
|
| 1164 | - /** |
|
| 1165 | - * @inheritdoc |
|
| 1166 | - */ |
|
| 1167 | - public function getAccessList($nodes, $currentAccess): array { |
|
| 1168 | - $ids = []; |
|
| 1169 | - foreach ($nodes as $node) { |
|
| 1170 | - $ids[] = $node->getId(); |
|
| 1171 | - } |
|
| 1172 | - |
|
| 1173 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1174 | - $qb->select('share_with', 'file_source', 'token') |
|
| 1175 | - ->from('share') |
|
| 1176 | - ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 1177 | - ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) |
|
| 1178 | - ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))); |
|
| 1179 | - $cursor = $qb->executeQuery(); |
|
| 1180 | - |
|
| 1181 | - $public = false; |
|
| 1182 | - $mail = []; |
|
| 1183 | - while ($row = $cursor->fetchAssociative()) { |
|
| 1184 | - $public = true; |
|
| 1185 | - if ($currentAccess === false) { |
|
| 1186 | - $mail[] = $row['share_with']; |
|
| 1187 | - } else { |
|
| 1188 | - $mail[$row['share_with']] = [ |
|
| 1189 | - 'node_id' => $row['file_source'], |
|
| 1190 | - 'token' => $row['token'] |
|
| 1191 | - ]; |
|
| 1192 | - } |
|
| 1193 | - } |
|
| 1194 | - $cursor->closeCursor(); |
|
| 1195 | - |
|
| 1196 | - return ['public' => $public, 'mail' => $mail]; |
|
| 1197 | - } |
|
| 1198 | - |
|
| 1199 | - public function getAllShares(): iterable { |
|
| 1200 | - $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1201 | - |
|
| 1202 | - $qb->select('*') |
|
| 1203 | - ->from('share') |
|
| 1204 | - ->where( |
|
| 1205 | - $qb->expr()->orX( |
|
| 1206 | - $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 1207 | - ) |
|
| 1208 | - ); |
|
| 1209 | - |
|
| 1210 | - $cursor = $qb->executeQuery(); |
|
| 1211 | - while ($data = $cursor->fetchAssociative()) { |
|
| 1212 | - try { |
|
| 1213 | - $share = $this->createShareObject($data); |
|
| 1214 | - } catch (InvalidShare $e) { |
|
| 1215 | - continue; |
|
| 1216 | - } catch (ShareNotFound $e) { |
|
| 1217 | - continue; |
|
| 1218 | - } |
|
| 1219 | - |
|
| 1220 | - yield $share; |
|
| 1221 | - } |
|
| 1222 | - $cursor->closeCursor(); |
|
| 1223 | - } |
|
| 1224 | - |
|
| 1225 | - /** |
|
| 1226 | - * Extract the emails from the share |
|
| 1227 | - * It can be a single email, from the share_with field |
|
| 1228 | - * or a list of emails from the emails attributes field. |
|
| 1229 | - * @param IShare $share |
|
| 1230 | - * @return string[] |
|
| 1231 | - */ |
|
| 1232 | - protected function getSharedWithEmails(IShare $share): array { |
|
| 1233 | - $attributes = $share->getAttributes(); |
|
| 1234 | - |
|
| 1235 | - if ($attributes === null) { |
|
| 1236 | - return [$share->getSharedWith()]; |
|
| 1237 | - } |
|
| 1238 | - |
|
| 1239 | - $emails = $attributes->getAttribute('shareWith', 'emails'); |
|
| 1240 | - if (isset($emails) && is_array($emails) && !empty($emails)) { |
|
| 1241 | - return $emails; |
|
| 1242 | - } |
|
| 1243 | - return [$share->getSharedWith()]; |
|
| 1244 | - } |
|
| 737 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 738 | + $qb->update('share') |
|
| 739 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) |
|
| 740 | + ->set('item_source', $qb->createNamedParameter($share->getNodeId())) |
|
| 741 | + ->set('file_source', $qb->createNamedParameter($share->getNodeId())) |
|
| 742 | + ->set('share_with', $qb->createNamedParameter($share->getSharedWith())) |
|
| 743 | + ->set('permissions', $qb->createNamedParameter($share->getPermissions())) |
|
| 744 | + ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) |
|
| 745 | + ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) |
|
| 746 | + ->set('password', $qb->createNamedParameter($share->getPassword())) |
|
| 747 | + ->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 748 | + ->set('label', $qb->createNamedParameter($share->getLabel())) |
|
| 749 | + ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL)) |
|
| 750 | + ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME_MUTABLE)) |
|
| 751 | + ->set('note', $qb->createNamedParameter($share->getNote())) |
|
| 752 | + ->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT)) |
|
| 753 | + ->set('attributes', $qb->createNamedParameter($shareAttributes)) |
|
| 754 | + ->set('mail_send', $qb->createNamedParameter((int)$share->getMailSend(), IQueryBuilder::PARAM_INT)) |
|
| 755 | + ->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL)) |
|
| 756 | + ->executeStatement(); |
|
| 757 | + |
|
| 758 | + if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') { |
|
| 759 | + $this->sendNote($share); |
|
| 760 | + } |
|
| 761 | + |
|
| 762 | + return $share; |
|
| 763 | + } |
|
| 764 | + |
|
| 765 | + /** |
|
| 766 | + * @inheritdoc |
|
| 767 | + */ |
|
| 768 | + public function move(IShare $share, $recipient): IShare { |
|
| 769 | + /** |
|
| 770 | + * nothing to do here, mail shares are only outgoing shares |
|
| 771 | + */ |
|
| 772 | + return $share; |
|
| 773 | + } |
|
| 774 | + |
|
| 775 | + /** |
|
| 776 | + * Delete a share (owner unShares the file) |
|
| 777 | + * |
|
| 778 | + * @param IShare $share |
|
| 779 | + */ |
|
| 780 | + public function delete(IShare $share): void { |
|
| 781 | + try { |
|
| 782 | + $this->createShareActivity($share, 'unshare'); |
|
| 783 | + } catch (\Exception $e) { |
|
| 784 | + } |
|
| 785 | + |
|
| 786 | + $this->removeShareFromTable((int)$share->getId()); |
|
| 787 | + } |
|
| 788 | + |
|
| 789 | + /** |
|
| 790 | + * @inheritdoc |
|
| 791 | + */ |
|
| 792 | + public function deleteFromSelf(IShare $share, $recipient): void { |
|
| 793 | + // nothing to do here, mail shares are only outgoing shares |
|
| 794 | + } |
|
| 795 | + |
|
| 796 | + public function restore(IShare $share, string $recipient): IShare { |
|
| 797 | + throw new GenericShareException('not implemented'); |
|
| 798 | + } |
|
| 799 | + |
|
| 800 | + /** |
|
| 801 | + * @inheritdoc |
|
| 802 | + */ |
|
| 803 | + public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset): array { |
|
| 804 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 805 | + $qb->select('*') |
|
| 806 | + ->from('share'); |
|
| 807 | + |
|
| 808 | + $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 809 | + |
|
| 810 | + /** |
|
| 811 | + * Reshares for this user are shares where they are the owner. |
|
| 812 | + */ |
|
| 813 | + if ($reshares === false) { |
|
| 814 | + //Special case for old shares created via the web UI |
|
| 815 | + $or1 = $qb->expr()->andX( |
|
| 816 | + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 817 | + $qb->expr()->isNull('uid_initiator') |
|
| 818 | + ); |
|
| 819 | + |
|
| 820 | + $qb->andWhere( |
|
| 821 | + $qb->expr()->orX( |
|
| 822 | + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)), |
|
| 823 | + $or1 |
|
| 824 | + ) |
|
| 825 | + ); |
|
| 826 | + } elseif ($node === null) { |
|
| 827 | + $qb->andWhere( |
|
| 828 | + $qb->expr()->orX( |
|
| 829 | + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 830 | + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) |
|
| 831 | + ) |
|
| 832 | + ); |
|
| 833 | + } |
|
| 834 | + |
|
| 835 | + if ($node !== null) { |
|
| 836 | + $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); |
|
| 837 | + } |
|
| 838 | + |
|
| 839 | + if ($limit !== -1) { |
|
| 840 | + $qb->setMaxResults($limit); |
|
| 841 | + } |
|
| 842 | + |
|
| 843 | + $qb->setFirstResult($offset); |
|
| 844 | + $qb->orderBy('id'); |
|
| 845 | + |
|
| 846 | + $cursor = $qb->executeQuery(); |
|
| 847 | + $shares = []; |
|
| 848 | + while ($data = $cursor->fetchAssociative()) { |
|
| 849 | + $shares[] = $this->createShareObject($data); |
|
| 850 | + } |
|
| 851 | + $cursor->closeCursor(); |
|
| 852 | + |
|
| 853 | + return $shares; |
|
| 854 | + } |
|
| 855 | + |
|
| 856 | + /** |
|
| 857 | + * @inheritdoc |
|
| 858 | + */ |
|
| 859 | + public function getShareById($id, $recipientId = null): IShare { |
|
| 860 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 861 | + |
|
| 862 | + $qb->select('*') |
|
| 863 | + ->from('share') |
|
| 864 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) |
|
| 865 | + ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 866 | + |
|
| 867 | + $cursor = $qb->executeQuery(); |
|
| 868 | + $data = $cursor->fetchAssociative(); |
|
| 869 | + $cursor->closeCursor(); |
|
| 870 | + |
|
| 871 | + if ($data === false) { |
|
| 872 | + throw new ShareNotFound(); |
|
| 873 | + } |
|
| 874 | + |
|
| 875 | + try { |
|
| 876 | + $share = $this->createShareObject($data); |
|
| 877 | + } catch (InvalidShare $e) { |
|
| 878 | + throw new ShareNotFound(); |
|
| 879 | + } |
|
| 880 | + |
|
| 881 | + return $share; |
|
| 882 | + } |
|
| 883 | + |
|
| 884 | + /** |
|
| 885 | + * Get shares for a given path |
|
| 886 | + * |
|
| 887 | + * @return IShare[] |
|
| 888 | + */ |
|
| 889 | + public function getSharesByPath(Node $path): array { |
|
| 890 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 891 | + |
|
| 892 | + $cursor = $qb->select('*') |
|
| 893 | + ->from('share') |
|
| 894 | + ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId()))) |
|
| 895 | + ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 896 | + ->executeQuery(); |
|
| 897 | + |
|
| 898 | + $shares = []; |
|
| 899 | + while ($data = $cursor->fetchAssociative()) { |
|
| 900 | + $shares[] = $this->createShareObject($data); |
|
| 901 | + } |
|
| 902 | + $cursor->closeCursor(); |
|
| 903 | + |
|
| 904 | + return $shares; |
|
| 905 | + } |
|
| 906 | + |
|
| 907 | + /** |
|
| 908 | + * @inheritdoc |
|
| 909 | + */ |
|
| 910 | + public function getSharedWith($userId, $shareType, $node, $limit, $offset): array { |
|
| 911 | + /** @var IShare[] $shares */ |
|
| 912 | + $shares = []; |
|
| 913 | + |
|
| 914 | + //Get shares directly with this user |
|
| 915 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 916 | + $qb->select('*') |
|
| 917 | + ->from('share'); |
|
| 918 | + |
|
| 919 | + // Order by id |
|
| 920 | + $qb->orderBy('id'); |
|
| 921 | + |
|
| 922 | + // Set limit and offset |
|
| 923 | + if ($limit !== -1) { |
|
| 924 | + $qb->setMaxResults($limit); |
|
| 925 | + } |
|
| 926 | + $qb->setFirstResult($offset); |
|
| 927 | + |
|
| 928 | + $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); |
|
| 929 | + $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))); |
|
| 930 | + |
|
| 931 | + // Filter by node if provided |
|
| 932 | + if ($node !== null) { |
|
| 933 | + $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); |
|
| 934 | + } |
|
| 935 | + |
|
| 936 | + $cursor = $qb->executeQuery(); |
|
| 937 | + |
|
| 938 | + while ($data = $cursor->fetchAssociative()) { |
|
| 939 | + $shares[] = $this->createShareObject($data); |
|
| 940 | + } |
|
| 941 | + $cursor->closeCursor(); |
|
| 942 | + |
|
| 943 | + |
|
| 944 | + return $shares; |
|
| 945 | + } |
|
| 946 | + |
|
| 947 | + /** |
|
| 948 | + * Get a share by token |
|
| 949 | + * |
|
| 950 | + * @throws ShareNotFound |
|
| 951 | + */ |
|
| 952 | + public function getShareByToken($token): IShare { |
|
| 953 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 954 | + |
|
| 955 | + $cursor = $qb->select('*') |
|
| 956 | + ->from('share') |
|
| 957 | + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 958 | + ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token))) |
|
| 959 | + ->executeQuery(); |
|
| 960 | + |
|
| 961 | + $data = $cursor->fetchAssociative(); |
|
| 962 | + |
|
| 963 | + if ($data === false) { |
|
| 964 | + throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); |
|
| 965 | + } |
|
| 966 | + |
|
| 967 | + try { |
|
| 968 | + $share = $this->createShareObject($data); |
|
| 969 | + } catch (InvalidShare $e) { |
|
| 970 | + throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); |
|
| 971 | + } |
|
| 972 | + |
|
| 973 | + return $share; |
|
| 974 | + } |
|
| 975 | + |
|
| 976 | + /** |
|
| 977 | + * remove share from table |
|
| 978 | + */ |
|
| 979 | + protected function removeShareFromTable(int $shareId): void { |
|
| 980 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 981 | + $qb->delete('share') |
|
| 982 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId))); |
|
| 983 | + $qb->executeStatement(); |
|
| 984 | + } |
|
| 985 | + |
|
| 986 | + /** |
|
| 987 | + * Create a share object from a database row |
|
| 988 | + * |
|
| 989 | + * @throws InvalidShare |
|
| 990 | + * @throws ShareNotFound |
|
| 991 | + */ |
|
| 992 | + protected function createShareObject(array $data): IShare { |
|
| 993 | + $share = new Share($this->rootFolder, $this->userManager); |
|
| 994 | + $share->setId((int)$data['id']) |
|
| 995 | + ->setShareType((int)$data['share_type']) |
|
| 996 | + ->setPermissions((int)$data['permissions']) |
|
| 997 | + ->setTarget($data['file_target']) |
|
| 998 | + ->setMailSend((bool)$data['mail_send']) |
|
| 999 | + ->setNote($data['note']) |
|
| 1000 | + ->setToken($data['token']); |
|
| 1001 | + |
|
| 1002 | + $shareTime = new \DateTime(); |
|
| 1003 | + $shareTime->setTimestamp((int)$data['stime']); |
|
| 1004 | + $share->setShareTime($shareTime); |
|
| 1005 | + $share->setSharedWith($data['share_with'] ?? ''); |
|
| 1006 | + $share->setPassword($data['password']); |
|
| 1007 | + $passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? ''); |
|
| 1008 | + $share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null); |
|
| 1009 | + $share->setLabel($data['label'] ?? ''); |
|
| 1010 | + $share->setSendPasswordByTalk((bool)$data['password_by_talk']); |
|
| 1011 | + $share->setHideDownload((bool)$data['hide_download']); |
|
| 1012 | + $share->setReminderSent((bool)$data['reminder_sent']); |
|
| 1013 | + |
|
| 1014 | + if ($data['uid_initiator'] !== null) { |
|
| 1015 | + $share->setShareOwner($data['uid_owner']); |
|
| 1016 | + $share->setSharedBy($data['uid_initiator']); |
|
| 1017 | + } else { |
|
| 1018 | + //OLD SHARE |
|
| 1019 | + $share->setSharedBy($data['uid_owner']); |
|
| 1020 | + $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']); |
|
| 1021 | + |
|
| 1022 | + $owner = $path->getOwner(); |
|
| 1023 | + $share->setShareOwner($owner->getUID()); |
|
| 1024 | + } |
|
| 1025 | + |
|
| 1026 | + if ($data['expiration'] !== null) { |
|
| 1027 | + $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']); |
|
| 1028 | + if ($expiration !== false) { |
|
| 1029 | + $share->setExpirationDate($expiration); |
|
| 1030 | + } |
|
| 1031 | + } |
|
| 1032 | + |
|
| 1033 | + $share = $this->updateShareAttributes($share, $data['attributes']); |
|
| 1034 | + |
|
| 1035 | + $share->setNodeId((int)$data['file_source']); |
|
| 1036 | + $share->setNodeType($data['item_type']); |
|
| 1037 | + |
|
| 1038 | + $share->setProviderId($this->identifier()); |
|
| 1039 | + |
|
| 1040 | + return $share; |
|
| 1041 | + } |
|
| 1042 | + |
|
| 1043 | + /** |
|
| 1044 | + * Get the node with file $id for $user |
|
| 1045 | + * |
|
| 1046 | + * @throws InvalidShare |
|
| 1047 | + */ |
|
| 1048 | + private function getNode(string $userId, int $id): Node { |
|
| 1049 | + try { |
|
| 1050 | + $userFolder = $this->rootFolder->getUserFolder($userId); |
|
| 1051 | + } catch (NoUserException $e) { |
|
| 1052 | + throw new InvalidShare(); |
|
| 1053 | + } |
|
| 1054 | + |
|
| 1055 | + $nodes = $userFolder->getById($id); |
|
| 1056 | + |
|
| 1057 | + if (empty($nodes)) { |
|
| 1058 | + throw new InvalidShare(); |
|
| 1059 | + } |
|
| 1060 | + |
|
| 1061 | + return $nodes[0]; |
|
| 1062 | + } |
|
| 1063 | + |
|
| 1064 | + /** |
|
| 1065 | + * A user is deleted from the system |
|
| 1066 | + * So clean up the relevant shares. |
|
| 1067 | + */ |
|
| 1068 | + public function userDeleted($uid, $shareType): void { |
|
| 1069 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1070 | + |
|
| 1071 | + $qb->delete('share') |
|
| 1072 | + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 1073 | + ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))) |
|
| 1074 | + ->executeStatement(); |
|
| 1075 | + } |
|
| 1076 | + |
|
| 1077 | + /** |
|
| 1078 | + * This provider does not support group shares |
|
| 1079 | + */ |
|
| 1080 | + public function groupDeleted($gid): void { |
|
| 1081 | + } |
|
| 1082 | + |
|
| 1083 | + /** |
|
| 1084 | + * This provider does not support group shares |
|
| 1085 | + */ |
|
| 1086 | + public function userDeletedFromGroup($uid, $gid): void { |
|
| 1087 | + } |
|
| 1088 | + |
|
| 1089 | + /** |
|
| 1090 | + * get database row of a give share |
|
| 1091 | + * |
|
| 1092 | + * @throws ShareNotFound |
|
| 1093 | + */ |
|
| 1094 | + protected function getRawShare(int $id): array { |
|
| 1095 | + // Now fetch the inserted share and create a complete share object |
|
| 1096 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1097 | + $qb->select('*') |
|
| 1098 | + ->from('share') |
|
| 1099 | + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); |
|
| 1100 | + |
|
| 1101 | + $cursor = $qb->executeQuery(); |
|
| 1102 | + $data = $cursor->fetchAssociative(); |
|
| 1103 | + $cursor->closeCursor(); |
|
| 1104 | + |
|
| 1105 | + if ($data === false) { |
|
| 1106 | + throw new ShareNotFound; |
|
| 1107 | + } |
|
| 1108 | + |
|
| 1109 | + return $data; |
|
| 1110 | + } |
|
| 1111 | + |
|
| 1112 | + public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true): array { |
|
| 1113 | + return $this->getSharesInFolderInternal($userId, $node, $reshares); |
|
| 1114 | + } |
|
| 1115 | + |
|
| 1116 | + public function getAllSharesInFolder(Folder $node): array { |
|
| 1117 | + return $this->getSharesInFolderInternal(null, $node, null); |
|
| 1118 | + } |
|
| 1119 | + |
|
| 1120 | + /** |
|
| 1121 | + * @return array<int, list<IShare>> |
|
| 1122 | + */ |
|
| 1123 | + private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array { |
|
| 1124 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1125 | + $qb->select('*') |
|
| 1126 | + ->from('share', 's') |
|
| 1127 | + ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))) |
|
| 1128 | + ->andWhere( |
|
| 1129 | + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 1130 | + ); |
|
| 1131 | + |
|
| 1132 | + if ($userId !== null) { |
|
| 1133 | + /** |
|
| 1134 | + * Reshares for this user are shares where they are the owner. |
|
| 1135 | + */ |
|
| 1136 | + if ($reshares !== true) { |
|
| 1137 | + $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); |
|
| 1138 | + } else { |
|
| 1139 | + $qb->andWhere( |
|
| 1140 | + $qb->expr()->orX( |
|
| 1141 | + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), |
|
| 1142 | + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) |
|
| 1143 | + ) |
|
| 1144 | + ); |
|
| 1145 | + } |
|
| 1146 | + } |
|
| 1147 | + |
|
| 1148 | + $qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')); |
|
| 1149 | + |
|
| 1150 | + $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId()))); |
|
| 1151 | + |
|
| 1152 | + $qb->orderBy('id'); |
|
| 1153 | + |
|
| 1154 | + $cursor = $qb->executeQuery(); |
|
| 1155 | + $shares = []; |
|
| 1156 | + while ($data = $cursor->fetchAssociative()) { |
|
| 1157 | + $shares[$data['fileid']][] = $this->createShareObject($data); |
|
| 1158 | + } |
|
| 1159 | + $cursor->closeCursor(); |
|
| 1160 | + |
|
| 1161 | + return $shares; |
|
| 1162 | + } |
|
| 1163 | + |
|
| 1164 | + /** |
|
| 1165 | + * @inheritdoc |
|
| 1166 | + */ |
|
| 1167 | + public function getAccessList($nodes, $currentAccess): array { |
|
| 1168 | + $ids = []; |
|
| 1169 | + foreach ($nodes as $node) { |
|
| 1170 | + $ids[] = $node->getId(); |
|
| 1171 | + } |
|
| 1172 | + |
|
| 1173 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1174 | + $qb->select('share_with', 'file_source', 'token') |
|
| 1175 | + ->from('share') |
|
| 1176 | + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))) |
|
| 1177 | + ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) |
|
| 1178 | + ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))); |
|
| 1179 | + $cursor = $qb->executeQuery(); |
|
| 1180 | + |
|
| 1181 | + $public = false; |
|
| 1182 | + $mail = []; |
|
| 1183 | + while ($row = $cursor->fetchAssociative()) { |
|
| 1184 | + $public = true; |
|
| 1185 | + if ($currentAccess === false) { |
|
| 1186 | + $mail[] = $row['share_with']; |
|
| 1187 | + } else { |
|
| 1188 | + $mail[$row['share_with']] = [ |
|
| 1189 | + 'node_id' => $row['file_source'], |
|
| 1190 | + 'token' => $row['token'] |
|
| 1191 | + ]; |
|
| 1192 | + } |
|
| 1193 | + } |
|
| 1194 | + $cursor->closeCursor(); |
|
| 1195 | + |
|
| 1196 | + return ['public' => $public, 'mail' => $mail]; |
|
| 1197 | + } |
|
| 1198 | + |
|
| 1199 | + public function getAllShares(): iterable { |
|
| 1200 | + $qb = $this->dbConnection->getQueryBuilder(); |
|
| 1201 | + |
|
| 1202 | + $qb->select('*') |
|
| 1203 | + ->from('share') |
|
| 1204 | + ->where( |
|
| 1205 | + $qb->expr()->orX( |
|
| 1206 | + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)) |
|
| 1207 | + ) |
|
| 1208 | + ); |
|
| 1209 | + |
|
| 1210 | + $cursor = $qb->executeQuery(); |
|
| 1211 | + while ($data = $cursor->fetchAssociative()) { |
|
| 1212 | + try { |
|
| 1213 | + $share = $this->createShareObject($data); |
|
| 1214 | + } catch (InvalidShare $e) { |
|
| 1215 | + continue; |
|
| 1216 | + } catch (ShareNotFound $e) { |
|
| 1217 | + continue; |
|
| 1218 | + } |
|
| 1219 | + |
|
| 1220 | + yield $share; |
|
| 1221 | + } |
|
| 1222 | + $cursor->closeCursor(); |
|
| 1223 | + } |
|
| 1224 | + |
|
| 1225 | + /** |
|
| 1226 | + * Extract the emails from the share |
|
| 1227 | + * It can be a single email, from the share_with field |
|
| 1228 | + * or a list of emails from the emails attributes field. |
|
| 1229 | + * @param IShare $share |
|
| 1230 | + * @return string[] |
|
| 1231 | + */ |
|
| 1232 | + protected function getSharedWithEmails(IShare $share): array { |
|
| 1233 | + $attributes = $share->getAttributes(); |
|
| 1234 | + |
|
| 1235 | + if ($attributes === null) { |
|
| 1236 | + return [$share->getSharedWith()]; |
|
| 1237 | + } |
|
| 1238 | + |
|
| 1239 | + $emails = $attributes->getAttribute('shareWith', 'emails'); |
|
| 1240 | + if (isset($emails) && is_array($emails) && !empty($emails)) { |
|
| 1241 | + return $emails; |
|
| 1242 | + } |
|
| 1243 | + return [$share->getSharedWith()]; |
|
| 1244 | + } |
|
| 1245 | 1245 | } |
@@ -13,50 +13,50 @@ |
||
| 13 | 13 | use OCP\Util; |
| 14 | 14 | |
| 15 | 15 | class Admin implements IDelegatedSettings { |
| 16 | - public function __construct( |
|
| 17 | - private SettingsManager $settingsManager, |
|
| 18 | - private IL10N $l, |
|
| 19 | - private IInitialState $initialState, |
|
| 20 | - ) { |
|
| 21 | - } |
|
| 22 | - |
|
| 23 | - /** |
|
| 24 | - * @return TemplateResponse |
|
| 25 | - */ |
|
| 26 | - public function getForm() { |
|
| 27 | - $this->initialState->provideInitialState('sendPasswordMail', $this->settingsManager->sendPasswordByMail()); |
|
| 28 | - $this->initialState->provideInitialState('replyToInitiator', $this->settingsManager->replyToInitiator()); |
|
| 29 | - |
|
| 30 | - Util::addStyle('sharebymail', 'admin-settings'); |
|
| 31 | - Util::addScript('sharebymail', 'admin-settings'); |
|
| 32 | - return new TemplateResponse('sharebymail', 'settings-admin', [], ''); |
|
| 33 | - } |
|
| 34 | - |
|
| 35 | - /** |
|
| 36 | - * @return string the section ID, e.g. 'sharing' |
|
| 37 | - */ |
|
| 38 | - public function getSection() { |
|
| 39 | - return 'sharing'; |
|
| 40 | - } |
|
| 41 | - |
|
| 42 | - /** |
|
| 43 | - * @return int whether the form should be rather on the top or bottom of |
|
| 44 | - * the admin section. The forms are arranged in ascending order of the |
|
| 45 | - * priority values. It is required to return a value between 0 and 100. |
|
| 46 | - * |
|
| 47 | - * E.g.: 70 |
|
| 48 | - */ |
|
| 49 | - public function getPriority() { |
|
| 50 | - return 40; |
|
| 51 | - } |
|
| 52 | - |
|
| 53 | - public function getName(): ?string { |
|
| 54 | - return $this->l->t('Share by mail'); |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - public function getAuthorizedAppConfig(): array { |
|
| 58 | - return [ |
|
| 59 | - 'sharebymail' => ['s/(sendpasswordmail|replyToInitiator)/'], |
|
| 60 | - ]; |
|
| 61 | - } |
|
| 16 | + public function __construct( |
|
| 17 | + private SettingsManager $settingsManager, |
|
| 18 | + private IL10N $l, |
|
| 19 | + private IInitialState $initialState, |
|
| 20 | + ) { |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | + /** |
|
| 24 | + * @return TemplateResponse |
|
| 25 | + */ |
|
| 26 | + public function getForm() { |
|
| 27 | + $this->initialState->provideInitialState('sendPasswordMail', $this->settingsManager->sendPasswordByMail()); |
|
| 28 | + $this->initialState->provideInitialState('replyToInitiator', $this->settingsManager->replyToInitiator()); |
|
| 29 | + |
|
| 30 | + Util::addStyle('sharebymail', 'admin-settings'); |
|
| 31 | + Util::addScript('sharebymail', 'admin-settings'); |
|
| 32 | + return new TemplateResponse('sharebymail', 'settings-admin', [], ''); |
|
| 33 | + } |
|
| 34 | + |
|
| 35 | + /** |
|
| 36 | + * @return string the section ID, e.g. 'sharing' |
|
| 37 | + */ |
|
| 38 | + public function getSection() { |
|
| 39 | + return 'sharing'; |
|
| 40 | + } |
|
| 41 | + |
|
| 42 | + /** |
|
| 43 | + * @return int whether the form should be rather on the top or bottom of |
|
| 44 | + * the admin section. The forms are arranged in ascending order of the |
|
| 45 | + * priority values. It is required to return a value between 0 and 100. |
|
| 46 | + * |
|
| 47 | + * E.g.: 70 |
|
| 48 | + */ |
|
| 49 | + public function getPriority() { |
|
| 50 | + return 40; |
|
| 51 | + } |
|
| 52 | + |
|
| 53 | + public function getName(): ?string { |
|
| 54 | + return $this->l->t('Share by mail'); |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + public function getAuthorizedAppConfig(): array { |
|
| 58 | + return [ |
|
| 59 | + 'sharebymail' => ['s/(sendpasswordmail|replyToInitiator)/'], |
|
| 60 | + ]; |
|
| 61 | + } |
|
| 62 | 62 | } |