Completed
Push — master ( 453450...6f0537 )
by
unknown
37:16
created
apps/settings/lib/SetupChecks/SupportedDatabase.php 1 patch
Indentation   +98 added lines, -98 removed lines patch added patch discarded remove patch
@@ -16,106 +16,106 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/settings/lib/Settings/Admin/Server.php 1 patch
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -19,94 +19,94 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/files_trashbin/tests/Command/CleanUpTest.php 1 patch
Indentation   +192 added lines, -192 removed lines patch added patch discarded remove patch
@@ -29,196 +29,196 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 1 patch
Indentation   +1137 added lines, -1137 removed lines patch added patch discarded remove patch
@@ -50,1141 +50,1141 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/theming/lib/Jobs/RestoreBackgroundImageColor.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -23,183 +23,183 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/theming/lib/Jobs/MigrateBackgroundImages.php 1 patch
Indentation   +173 added lines, -173 removed lines patch added patch discarded remove patch
@@ -23,177 +23,177 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/sharebymail/tests/ShareByMailProviderTest.php 1 patch
Indentation   +1810 added lines, -1810 removed lines patch added patch discarded remove patch
@@ -50,1861 +50,1861 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/sharebymail/lib/ShareByMailProvider.php 1 patch
Indentation   +1194 added lines, -1194 removed lines patch added patch discarded remove patch
@@ -46,1200 +46,1200 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
apps/sharebymail/lib/Settings/Admin.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -13,50 +13,50 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.