Passed
Push — master ( aeb32e...81302f )
by Christoph
15:20 queued 10s
created
apps/settings/lib/Controller/CheckSetupController.php 1 patch
Indentation   +671 added lines, -671 removed lines patch added patch discarded remove patch
@@ -81,295 +81,295 @@  discard block
 block discarded – undo
81 81
 use Symfony\Component\EventDispatcher\GenericEvent;
82 82
 
83 83
 class CheckSetupController extends Controller {
84
-	/** @var IConfig */
85
-	private $config;
86
-	/** @var IClientService */
87
-	private $clientService;
88
-	/** @var IURLGenerator */
89
-	private $urlGenerator;
90
-	/** @var IL10N */
91
-	private $l10n;
92
-	/** @var Checker */
93
-	private $checker;
94
-	/** @var ILogger */
95
-	private $logger;
96
-	/** @var EventDispatcherInterface */
97
-	private $dispatcher;
98
-	/** @var Connection */
99
-	private $db;
100
-	/** @var ILockingProvider */
101
-	private $lockingProvider;
102
-	/** @var IDateTimeFormatter */
103
-	private $dateTimeFormatter;
104
-	/** @var MemoryInfo */
105
-	private $memoryInfo;
106
-	/** @var ISecureRandom */
107
-	private $secureRandom;
108
-	/** @var IniGetWrapper */
109
-	private $iniGetWrapper;
110
-
111
-	public function __construct($AppName,
112
-								IRequest $request,
113
-								IConfig $config,
114
-								IClientService $clientService,
115
-								IURLGenerator $urlGenerator,
116
-								IL10N $l10n,
117
-								Checker $checker,
118
-								ILogger $logger,
119
-								EventDispatcherInterface $dispatcher,
120
-								Connection $db,
121
-								ILockingProvider $lockingProvider,
122
-								IDateTimeFormatter $dateTimeFormatter,
123
-								MemoryInfo $memoryInfo,
124
-								ISecureRandom $secureRandom,
125
-								IniGetWrapper $iniGetWrapper) {
126
-		parent::__construct($AppName, $request);
127
-		$this->config = $config;
128
-		$this->clientService = $clientService;
129
-		$this->urlGenerator = $urlGenerator;
130
-		$this->l10n = $l10n;
131
-		$this->checker = $checker;
132
-		$this->logger = $logger;
133
-		$this->dispatcher = $dispatcher;
134
-		$this->db = $db;
135
-		$this->lockingProvider = $lockingProvider;
136
-		$this->dateTimeFormatter = $dateTimeFormatter;
137
-		$this->memoryInfo = $memoryInfo;
138
-		$this->secureRandom = $secureRandom;
139
-		$this->iniGetWrapper = $iniGetWrapper;
140
-	}
141
-
142
-	/**
143
-	 * Checks if the server can connect to the internet using HTTPS and HTTP
144
-	 * @return bool
145
-	 */
146
-	private function hasInternetConnectivityProblems(): bool {
147
-		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
148
-			return false;
149
-		}
150
-
151
-		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
152
-			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
153
-		]);
154
-
155
-		foreach ($siteArray as $site) {
156
-			if ($this->isSiteReachable($site)) {
157
-				return false;
158
-			}
159
-		}
160
-		return true;
161
-	}
162
-
163
-	/**
164
-	 * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
165
-	 * @return bool
166
-	 */
167
-	private function isSiteReachable($sitename) {
168
-		$httpSiteName = 'http://' . $sitename . '/';
169
-		$httpsSiteName = 'https://' . $sitename . '/';
170
-
171
-		try {
172
-			$client = $this->clientService->newClient();
173
-			$client->get($httpSiteName);
174
-			$client->get($httpsSiteName);
175
-		} catch (\Exception $e) {
176
-			$this->logger->logException($e, ['app' => 'internet_connection_check']);
177
-			return false;
178
-		}
179
-		return true;
180
-	}
181
-
182
-	/**
183
-	 * Checks whether a local memcache is installed or not
184
-	 * @return bool
185
-	 */
186
-	private function isMemcacheConfigured() {
187
-		return $this->config->getSystemValue('memcache.local', null) !== null;
188
-	}
189
-
190
-	/**
191
-	 * Whether PHP can generate "secure" pseudorandom integers
192
-	 *
193
-	 * @return bool
194
-	 */
195
-	private function isRandomnessSecure() {
196
-		try {
197
-			$this->secureRandom->generate(1);
198
-		} catch (\Exception $ex) {
199
-			return false;
200
-		}
201
-		return true;
202
-	}
203
-
204
-	/**
205
-	 * Public for the sake of unit-testing
206
-	 *
207
-	 * @return array
208
-	 */
209
-	protected function getCurlVersion() {
210
-		return curl_version();
211
-	}
212
-
213
-	/**
214
-	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
215
-	 * have multiple bugs which likely lead to problems in combination with
216
-	 * functionality required by ownCloud such as SNI.
217
-	 *
218
-	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
219
-	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
220
-	 * @return string
221
-	 */
222
-	private function isUsedTlsLibOutdated() {
223
-		// Don't run check when:
224
-		// 1. Server has `has_internet_connection` set to false
225
-		// 2. AppStore AND S2S is disabled
226
-		if (!$this->config->getSystemValue('has_internet_connection', true)) {
227
-			return '';
228
-		}
229
-		if (!$this->config->getSystemValue('appstoreenabled', true)
230
-			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
231
-			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
232
-			return '';
233
-		}
234
-
235
-		$versionString = $this->getCurlVersion();
236
-		if (isset($versionString['ssl_version'])) {
237
-			$versionString = $versionString['ssl_version'];
238
-		} else {
239
-			return '';
240
-		}
241
-
242
-		$features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
243
-		if (!$this->config->getSystemValue('appstoreenabled', true)) {
244
-			$features = (string)$this->l10n->t('Federated Cloud Sharing');
245
-		}
246
-
247
-		// Check if at least OpenSSL after 1.01d or 1.0.2b
248
-		if (strpos($versionString, 'OpenSSL/') === 0) {
249
-			$majorVersion = substr($versionString, 8, 5);
250
-			$patchRelease = substr($versionString, 13, 6);
251
-
252
-			if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
253
-				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
254
-				return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
255
-			}
256
-		}
257
-
258
-		// Check if NSS and perform heuristic check
259
-		if (strpos($versionString, 'NSS/') === 0) {
260
-			try {
261
-				$firstClient = $this->clientService->newClient();
262
-				$firstClient->get('https://nextcloud.com/');
263
-
264
-				$secondClient = $this->clientService->newClient();
265
-				$secondClient->get('https://nextcloud.com/');
266
-			} catch (ClientException $e) {
267
-				if ($e->getResponse()->getStatusCode() === 400) {
268
-					return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
269
-				}
270
-			} catch (\Exception $e) {
271
-				$this->logger->logException($e, ['app' => 'settings', 'level' => \OCP\ILogger::WARN]);
272
-				return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the nextcloud log file for more details.');
273
-			}
274
-		}
275
-
276
-		return '';
277
-	}
278
-
279
-	/**
280
-	 * Whether the version is outdated
281
-	 *
282
-	 * @return bool
283
-	 */
284
-	protected function isPhpOutdated(): bool {
285
-		return PHP_VERSION_ID < 70300;
286
-	}
287
-
288
-	/**
289
-	 * Whether the php version is still supported (at time of release)
290
-	 * according to: https://www.php.net/supported-versions.php
291
-	 *
292
-	 * @return array
293
-	 */
294
-	private function isPhpSupported(): array {
295
-		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
296
-	}
297
-
298
-	/**
299
-	 * Check if the reverse proxy configuration is working as expected
300
-	 *
301
-	 * @return bool
302
-	 */
303
-	private function forwardedForHeadersWorking() {
304
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
305
-		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
306
-
307
-		if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
308
-			return false;
309
-		}
310
-
311
-		if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
312
-			return $remoteAddress !== $this->request->getRemoteAddress();
313
-		}
314
-
315
-		// either not enabled or working correctly
316
-		return true;
317
-	}
318
-
319
-	/**
320
-	 * Checks if the correct memcache module for PHP is installed. Only
321
-	 * fails if memcached is configured and the working module is not installed.
322
-	 *
323
-	 * @return bool
324
-	 */
325
-	private function isCorrectMemcachedPHPModuleInstalled() {
326
-		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
327
-			return true;
328
-		}
329
-
330
-		// there are two different memcached modules for PHP
331
-		// we only support memcached and not memcache
332
-		// https://code.google.com/p/memcached/wiki/PHPClientComparison
333
-		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
334
-	}
335
-
336
-	/**
337
-	 * Checks if set_time_limit is not disabled.
338
-	 *
339
-	 * @return bool
340
-	 */
341
-	private function isSettimelimitAvailable() {
342
-		if (function_exists('set_time_limit')
343
-			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
344
-			return true;
345
-		}
346
-
347
-		return false;
348
-	}
349
-
350
-	/**
351
-	 * @return RedirectResponse
352
-	 */
353
-	public function rescanFailedIntegrityCheck() {
354
-		$this->checker->runInstanceVerification();
355
-		return new RedirectResponse(
356
-			$this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
357
-		);
358
-	}
359
-
360
-	/**
361
-	 * @NoCSRFRequired
362
-	 * @return DataResponse
363
-	 */
364
-	public function getFailedIntegrityCheckFiles() {
365
-		if (!$this->checker->isCodeCheckEnforced()) {
366
-			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
367
-		}
368
-
369
-		$completeResults = $this->checker->getResults();
370
-
371
-		if (!empty($completeResults)) {
372
-			$formattedTextResponse = 'Technical information
84
+    /** @var IConfig */
85
+    private $config;
86
+    /** @var IClientService */
87
+    private $clientService;
88
+    /** @var IURLGenerator */
89
+    private $urlGenerator;
90
+    /** @var IL10N */
91
+    private $l10n;
92
+    /** @var Checker */
93
+    private $checker;
94
+    /** @var ILogger */
95
+    private $logger;
96
+    /** @var EventDispatcherInterface */
97
+    private $dispatcher;
98
+    /** @var Connection */
99
+    private $db;
100
+    /** @var ILockingProvider */
101
+    private $lockingProvider;
102
+    /** @var IDateTimeFormatter */
103
+    private $dateTimeFormatter;
104
+    /** @var MemoryInfo */
105
+    private $memoryInfo;
106
+    /** @var ISecureRandom */
107
+    private $secureRandom;
108
+    /** @var IniGetWrapper */
109
+    private $iniGetWrapper;
110
+
111
+    public function __construct($AppName,
112
+                                IRequest $request,
113
+                                IConfig $config,
114
+                                IClientService $clientService,
115
+                                IURLGenerator $urlGenerator,
116
+                                IL10N $l10n,
117
+                                Checker $checker,
118
+                                ILogger $logger,
119
+                                EventDispatcherInterface $dispatcher,
120
+                                Connection $db,
121
+                                ILockingProvider $lockingProvider,
122
+                                IDateTimeFormatter $dateTimeFormatter,
123
+                                MemoryInfo $memoryInfo,
124
+                                ISecureRandom $secureRandom,
125
+                                IniGetWrapper $iniGetWrapper) {
126
+        parent::__construct($AppName, $request);
127
+        $this->config = $config;
128
+        $this->clientService = $clientService;
129
+        $this->urlGenerator = $urlGenerator;
130
+        $this->l10n = $l10n;
131
+        $this->checker = $checker;
132
+        $this->logger = $logger;
133
+        $this->dispatcher = $dispatcher;
134
+        $this->db = $db;
135
+        $this->lockingProvider = $lockingProvider;
136
+        $this->dateTimeFormatter = $dateTimeFormatter;
137
+        $this->memoryInfo = $memoryInfo;
138
+        $this->secureRandom = $secureRandom;
139
+        $this->iniGetWrapper = $iniGetWrapper;
140
+    }
141
+
142
+    /**
143
+     * Checks if the server can connect to the internet using HTTPS and HTTP
144
+     * @return bool
145
+     */
146
+    private function hasInternetConnectivityProblems(): bool {
147
+        if ($this->config->getSystemValue('has_internet_connection', true) === false) {
148
+            return false;
149
+        }
150
+
151
+        $siteArray = $this->config->getSystemValue('connectivity_check_domains', [
152
+            'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
153
+        ]);
154
+
155
+        foreach ($siteArray as $site) {
156
+            if ($this->isSiteReachable($site)) {
157
+                return false;
158
+            }
159
+        }
160
+        return true;
161
+    }
162
+
163
+    /**
164
+     * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
165
+     * @return bool
166
+     */
167
+    private function isSiteReachable($sitename) {
168
+        $httpSiteName = 'http://' . $sitename . '/';
169
+        $httpsSiteName = 'https://' . $sitename . '/';
170
+
171
+        try {
172
+            $client = $this->clientService->newClient();
173
+            $client->get($httpSiteName);
174
+            $client->get($httpsSiteName);
175
+        } catch (\Exception $e) {
176
+            $this->logger->logException($e, ['app' => 'internet_connection_check']);
177
+            return false;
178
+        }
179
+        return true;
180
+    }
181
+
182
+    /**
183
+     * Checks whether a local memcache is installed or not
184
+     * @return bool
185
+     */
186
+    private function isMemcacheConfigured() {
187
+        return $this->config->getSystemValue('memcache.local', null) !== null;
188
+    }
189
+
190
+    /**
191
+     * Whether PHP can generate "secure" pseudorandom integers
192
+     *
193
+     * @return bool
194
+     */
195
+    private function isRandomnessSecure() {
196
+        try {
197
+            $this->secureRandom->generate(1);
198
+        } catch (\Exception $ex) {
199
+            return false;
200
+        }
201
+        return true;
202
+    }
203
+
204
+    /**
205
+     * Public for the sake of unit-testing
206
+     *
207
+     * @return array
208
+     */
209
+    protected function getCurlVersion() {
210
+        return curl_version();
211
+    }
212
+
213
+    /**
214
+     * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
215
+     * have multiple bugs which likely lead to problems in combination with
216
+     * functionality required by ownCloud such as SNI.
217
+     *
218
+     * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
219
+     * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
220
+     * @return string
221
+     */
222
+    private function isUsedTlsLibOutdated() {
223
+        // Don't run check when:
224
+        // 1. Server has `has_internet_connection` set to false
225
+        // 2. AppStore AND S2S is disabled
226
+        if (!$this->config->getSystemValue('has_internet_connection', true)) {
227
+            return '';
228
+        }
229
+        if (!$this->config->getSystemValue('appstoreenabled', true)
230
+            && $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
231
+            && $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
232
+            return '';
233
+        }
234
+
235
+        $versionString = $this->getCurlVersion();
236
+        if (isset($versionString['ssl_version'])) {
237
+            $versionString = $versionString['ssl_version'];
238
+        } else {
239
+            return '';
240
+        }
241
+
242
+        $features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
243
+        if (!$this->config->getSystemValue('appstoreenabled', true)) {
244
+            $features = (string)$this->l10n->t('Federated Cloud Sharing');
245
+        }
246
+
247
+        // Check if at least OpenSSL after 1.01d or 1.0.2b
248
+        if (strpos($versionString, 'OpenSSL/') === 0) {
249
+            $majorVersion = substr($versionString, 8, 5);
250
+            $patchRelease = substr($versionString, 13, 6);
251
+
252
+            if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
253
+                ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
254
+                return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
255
+            }
256
+        }
257
+
258
+        // Check if NSS and perform heuristic check
259
+        if (strpos($versionString, 'NSS/') === 0) {
260
+            try {
261
+                $firstClient = $this->clientService->newClient();
262
+                $firstClient->get('https://nextcloud.com/');
263
+
264
+                $secondClient = $this->clientService->newClient();
265
+                $secondClient->get('https://nextcloud.com/');
266
+            } catch (ClientException $e) {
267
+                if ($e->getResponse()->getStatusCode() === 400) {
268
+                    return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
269
+                }
270
+            } catch (\Exception $e) {
271
+                $this->logger->logException($e, ['app' => 'settings', 'level' => \OCP\ILogger::WARN]);
272
+                return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the nextcloud log file for more details.');
273
+            }
274
+        }
275
+
276
+        return '';
277
+    }
278
+
279
+    /**
280
+     * Whether the version is outdated
281
+     *
282
+     * @return bool
283
+     */
284
+    protected function isPhpOutdated(): bool {
285
+        return PHP_VERSION_ID < 70300;
286
+    }
287
+
288
+    /**
289
+     * Whether the php version is still supported (at time of release)
290
+     * according to: https://www.php.net/supported-versions.php
291
+     *
292
+     * @return array
293
+     */
294
+    private function isPhpSupported(): array {
295
+        return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
296
+    }
297
+
298
+    /**
299
+     * Check if the reverse proxy configuration is working as expected
300
+     *
301
+     * @return bool
302
+     */
303
+    private function forwardedForHeadersWorking() {
304
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
305
+        $remoteAddress = $this->request->getHeader('REMOTE_ADDR');
306
+
307
+        if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
308
+            return false;
309
+        }
310
+
311
+        if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
312
+            return $remoteAddress !== $this->request->getRemoteAddress();
313
+        }
314
+
315
+        // either not enabled or working correctly
316
+        return true;
317
+    }
318
+
319
+    /**
320
+     * Checks if the correct memcache module for PHP is installed. Only
321
+     * fails if memcached is configured and the working module is not installed.
322
+     *
323
+     * @return bool
324
+     */
325
+    private function isCorrectMemcachedPHPModuleInstalled() {
326
+        if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
327
+            return true;
328
+        }
329
+
330
+        // there are two different memcached modules for PHP
331
+        // we only support memcached and not memcache
332
+        // https://code.google.com/p/memcached/wiki/PHPClientComparison
333
+        return !(!extension_loaded('memcached') && extension_loaded('memcache'));
334
+    }
335
+
336
+    /**
337
+     * Checks if set_time_limit is not disabled.
338
+     *
339
+     * @return bool
340
+     */
341
+    private function isSettimelimitAvailable() {
342
+        if (function_exists('set_time_limit')
343
+            && strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
344
+            return true;
345
+        }
346
+
347
+        return false;
348
+    }
349
+
350
+    /**
351
+     * @return RedirectResponse
352
+     */
353
+    public function rescanFailedIntegrityCheck() {
354
+        $this->checker->runInstanceVerification();
355
+        return new RedirectResponse(
356
+            $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
357
+        );
358
+    }
359
+
360
+    /**
361
+     * @NoCSRFRequired
362
+     * @return DataResponse
363
+     */
364
+    public function getFailedIntegrityCheckFiles() {
365
+        if (!$this->checker->isCodeCheckEnforced()) {
366
+            return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
367
+        }
368
+
369
+        $completeResults = $this->checker->getResults();
370
+
371
+        if (!empty($completeResults)) {
372
+            $formattedTextResponse = 'Technical information
373 373
 =====================
374 374
 The following list covers which files have failed the integrity check. Please read
375 375
 the previous linked documentation to learn more about the errors and how to fix
@@ -378,389 +378,389 @@  discard block
 block discarded – undo
378 378
 Results
379 379
 =======
380 380
 ';
381
-			foreach ($completeResults as $context => $contextResult) {
382
-				$formattedTextResponse .= "- $context\n";
383
-
384
-				foreach ($contextResult as $category => $result) {
385
-					$formattedTextResponse .= "\t- $category\n";
386
-					if ($category !== 'EXCEPTION') {
387
-						foreach ($result as $key => $results) {
388
-							$formattedTextResponse .= "\t\t- $key\n";
389
-						}
390
-					} else {
391
-						foreach ($result as $key => $results) {
392
-							$formattedTextResponse .= "\t\t- $results\n";
393
-						}
394
-					}
395
-				}
396
-			}
397
-
398
-			$formattedTextResponse .= '
381
+            foreach ($completeResults as $context => $contextResult) {
382
+                $formattedTextResponse .= "- $context\n";
383
+
384
+                foreach ($contextResult as $category => $result) {
385
+                    $formattedTextResponse .= "\t- $category\n";
386
+                    if ($category !== 'EXCEPTION') {
387
+                        foreach ($result as $key => $results) {
388
+                            $formattedTextResponse .= "\t\t- $key\n";
389
+                        }
390
+                    } else {
391
+                        foreach ($result as $key => $results) {
392
+                            $formattedTextResponse .= "\t\t- $results\n";
393
+                        }
394
+                    }
395
+                }
396
+            }
397
+
398
+            $formattedTextResponse .= '
399 399
 Raw output
400 400
 ==========
401 401
 ';
402
-			$formattedTextResponse .= print_r($completeResults, true);
403
-		} else {
404
-			$formattedTextResponse = 'No errors have been found.';
405
-		}
406
-
407
-
408
-		$response = new DataDisplayResponse(
409
-			$formattedTextResponse,
410
-			Http::STATUS_OK,
411
-			[
412
-				'Content-Type' => 'text/plain',
413
-			]
414
-		);
415
-
416
-		return $response;
417
-	}
418
-
419
-	/**
420
-	 * Checks whether a PHP opcache is properly set up
421
-	 * @return bool
422
-	 */
423
-	protected function isOpcacheProperlySetup() {
424
-		if (!$this->iniGetWrapper->getBool('opcache.enable')) {
425
-			return false;
426
-		}
427
-
428
-		if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
429
-			return false;
430
-		}
431
-
432
-		if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
433
-			return false;
434
-		}
435
-
436
-		if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
437
-			return false;
438
-		}
439
-
440
-		if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
441
-			return false;
442
-		}
443
-
444
-		return true;
445
-	}
446
-
447
-	/**
448
-	 * Check if the required FreeType functions are present
449
-	 * @return bool
450
-	 */
451
-	protected function hasFreeTypeSupport() {
452
-		return function_exists('imagettfbbox') && function_exists('imagettftext');
453
-	}
454
-
455
-	protected function hasMissingIndexes(): array {
456
-		$indexInfo = new MissingIndexInformation();
457
-		// Dispatch event so apps can also hint for pending index updates if needed
458
-		$event = new GenericEvent($indexInfo);
459
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
460
-
461
-		return $indexInfo->getListOfMissingIndexes();
462
-	}
463
-
464
-	protected function hasMissingPrimaryKeys(): array {
465
-		$info = new MissingPrimaryKeyInformation();
466
-		// Dispatch event so apps can also hint for pending index updates if needed
467
-		$event = new GenericEvent($info);
468
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
469
-
470
-		return $info->getListOfMissingPrimaryKeys();
471
-	}
472
-
473
-	protected function hasMissingColumns(): array {
474
-		$indexInfo = new MissingColumnInformation();
475
-		// Dispatch event so apps can also hint for pending index updates if needed
476
-		$event = new GenericEvent($indexInfo);
477
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
478
-
479
-		return $indexInfo->getListOfMissingColumns();
480
-	}
481
-
482
-	protected function isSqliteUsed() {
483
-		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
484
-	}
485
-
486
-	protected function isReadOnlyConfig(): bool {
487
-		return \OC_Helper::isReadOnlyConfigEnabled();
488
-	}
489
-
490
-	protected function hasValidTransactionIsolationLevel(): bool {
491
-		try {
492
-			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
493
-				return true;
494
-			}
495
-
496
-			return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
497
-		} catch (Exception $e) {
498
-			// ignore
499
-		}
500
-
501
-		return true;
502
-	}
503
-
504
-	protected function hasFileinfoInstalled(): bool {
505
-		return \OC_Util::fileInfoLoaded();
506
-	}
507
-
508
-	protected function hasWorkingFileLocking(): bool {
509
-		return !($this->lockingProvider instanceof NoopLockingProvider);
510
-	}
511
-
512
-	protected function getSuggestedOverwriteCliURL(): string {
513
-		$suggestedOverwriteCliUrl = '';
514
-		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
515
-			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
516
-			if (!$this->config->getSystemValue('config_is_read_only', false)) {
517
-				// Set the overwrite URL when it was not set yet.
518
-				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
519
-				$suggestedOverwriteCliUrl = '';
520
-			}
521
-		}
522
-		return $suggestedOverwriteCliUrl;
523
-	}
524
-
525
-	protected function getLastCronInfo(): array {
526
-		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
527
-		return [
528
-			'diffInSeconds' => time() - $lastCronRun,
529
-			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
530
-			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
531
-		];
532
-	}
533
-
534
-	protected function getCronErrors() {
535
-		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
536
-
537
-		if (is_array($errors)) {
538
-			return $errors;
539
-		}
540
-
541
-		return [];
542
-	}
543
-
544
-	protected function hasOpcacheLoaded(): bool {
545
-		return extension_loaded('Zend OPcache');
546
-	}
547
-
548
-	/**
549
-	 * Iterates through the configured app roots and
550
-	 * tests if the subdirectories are owned by the same user than the current user.
551
-	 *
552
-	 * @return array
553
-	 */
554
-	protected function getAppDirsWithDifferentOwner(): array {
555
-		$currentUser = posix_getuid();
556
-		$appDirsWithDifferentOwner = [[]];
557
-
558
-		foreach (OC::$APPSROOTS as $appRoot) {
559
-			if ($appRoot['writable'] === true) {
560
-				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
561
-			}
562
-		}
563
-
564
-		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
565
-		sort($appDirsWithDifferentOwner);
566
-
567
-		return $appDirsWithDifferentOwner;
568
-	}
569
-
570
-	/**
571
-	 * Tests if the directories for one apps directory are writable by the current user.
572
-	 *
573
-	 * @param int $currentUser The current user
574
-	 * @param array $appRoot The app root config
575
-	 * @return string[] The none writable directory paths inside the app root
576
-	 */
577
-	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
578
-		$appDirsWithDifferentOwner = [];
579
-		$appsPath = $appRoot['path'];
580
-		$appsDir = new DirectoryIterator($appRoot['path']);
581
-
582
-		foreach ($appsDir as $fileInfo) {
583
-			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
584
-				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
585
-				$appDirUser = fileowner($absAppPath);
586
-				if ($appDirUser !== $currentUser) {
587
-					$appDirsWithDifferentOwner[] = $absAppPath;
588
-				}
589
-			}
590
-		}
591
-
592
-		return $appDirsWithDifferentOwner;
593
-	}
594
-
595
-	/**
596
-	 * Checks for potential PHP modules that would improve the instance
597
-	 *
598
-	 * @return string[] A list of PHP modules that is recommended
599
-	 */
600
-	protected function hasRecommendedPHPModules(): array {
601
-		$recommendedPHPModules = [];
602
-
603
-		if (!extension_loaded('intl')) {
604
-			$recommendedPHPModules[] = 'intl';
605
-		}
606
-
607
-		if (!extension_loaded('bcmath')) {
608
-			$recommendedPHPModules[] = 'bcmath';
609
-		}
610
-
611
-		if (!extension_loaded('gmp')) {
612
-			$recommendedPHPModules[] = 'gmp';
613
-		}
614
-
615
-		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
616
-			if (!extension_loaded('imagick')) {
617
-				$recommendedPHPModules[] = 'imagick';
618
-			}
619
-		}
620
-
621
-		return $recommendedPHPModules;
622
-	}
623
-
624
-	protected function isMysqlUsedWithoutUTF8MB4(): bool {
625
-		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
626
-	}
627
-
628
-	protected function hasBigIntConversionPendingColumns(): array {
629
-		// copy of ConvertFilecacheBigInt::getColumnsByTable()
630
-		$tables = [
631
-			'activity' => ['activity_id', 'object_id'],
632
-			'activity_mq' => ['mail_id'],
633
-			'authtoken' => ['id'],
634
-			'bruteforce_attempts' => ['id'],
635
-			'federated_reshares' => ['share_id'],
636
-			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
637
-			'filecache_extended' => ['fileid'],
638
-			'file_locks' => ['id'],
639
-			'jobs' => ['id'],
640
-			'mimetypes' => ['id'],
641
-			'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
642
-			'share_external' => ['id', 'parent'],
643
-			'storages' => ['numeric_id'],
644
-		];
645
-
646
-		$schema = new SchemaWrapper($this->db);
647
-		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
648
-		$pendingColumns = [];
649
-
650
-		foreach ($tables as $tableName => $columns) {
651
-			if (!$schema->hasTable($tableName)) {
652
-				continue;
653
-			}
654
-
655
-			$table = $schema->getTable($tableName);
656
-			foreach ($columns as $columnName) {
657
-				$column = $table->getColumn($columnName);
658
-				$isAutoIncrement = $column->getAutoincrement();
659
-				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
660
-				if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
661
-					$pendingColumns[] = $tableName . '.' . $columnName;
662
-				}
663
-			}
664
-		}
665
-
666
-		return $pendingColumns;
667
-	}
668
-
669
-	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
670
-		$objectStore = $this->config->getSystemValue('objectstore', null);
671
-		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
672
-
673
-		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
674
-			return true;
675
-		}
676
-
677
-		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
678
-			return true;
679
-		}
680
-
681
-		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
682
-			return true;
683
-		}
684
-
685
-		$tempPath = sys_get_temp_dir();
686
-		if (!is_dir($tempPath)) {
687
-			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
688
-			return false;
689
-		}
690
-		$freeSpaceInTemp = disk_free_space($tempPath);
691
-		if ($freeSpaceInTemp === false) {
692
-			$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
693
-			return false;
694
-		}
695
-
696
-		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
697
-		if ($freeSpaceInTempInGB > 50) {
698
-			return true;
699
-		}
700
-
701
-		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
702
-		return false;
703
-	}
704
-
705
-	protected function imageMagickLacksSVGSupport(): bool {
706
-		return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
707
-	}
708
-
709
-	/**
710
-	 * @return DataResponse
711
-	 */
712
-	public function check() {
713
-		$phpDefaultCharset = new PhpDefaultCharset();
714
-		$phpOutputBuffering = new PhpOutputBuffering();
715
-		$legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
716
-		$checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
717
-
718
-		return new DataResponse(
719
-			[
720
-				'isGetenvServerWorking' => !empty(getenv('PATH')),
721
-				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
722
-				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
723
-				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
724
-				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
725
-				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
726
-				'cronInfo' => $this->getLastCronInfo(),
727
-				'cronErrors' => $this->getCronErrors(),
728
-				'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
729
-				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
730
-				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
731
-				'isRandomnessSecure' => $this->isRandomnessSecure(),
732
-				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
733
-				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
734
-				'phpSupported' => $this->isPhpSupported(),
735
-				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
736
-				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
737
-				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
738
-				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
739
-				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
740
-				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
741
-				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
742
-				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
743
-				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
744
-				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
745
-				'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
746
-				'missingIndexes' => $this->hasMissingIndexes(),
747
-				'missingColumns' => $this->hasMissingColumns(),
748
-				'isSqliteUsed' => $this->isSqliteUsed(),
749
-				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
750
-				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
751
-				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
752
-				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
753
-				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
754
-				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
755
-				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
756
-				'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
757
-				'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
758
-				PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
759
-				PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
760
-				LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
761
-				CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
762
-				'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
763
-			]
764
-		);
765
-	}
402
+            $formattedTextResponse .= print_r($completeResults, true);
403
+        } else {
404
+            $formattedTextResponse = 'No errors have been found.';
405
+        }
406
+
407
+
408
+        $response = new DataDisplayResponse(
409
+            $formattedTextResponse,
410
+            Http::STATUS_OK,
411
+            [
412
+                'Content-Type' => 'text/plain',
413
+            ]
414
+        );
415
+
416
+        return $response;
417
+    }
418
+
419
+    /**
420
+     * Checks whether a PHP opcache is properly set up
421
+     * @return bool
422
+     */
423
+    protected function isOpcacheProperlySetup() {
424
+        if (!$this->iniGetWrapper->getBool('opcache.enable')) {
425
+            return false;
426
+        }
427
+
428
+        if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
429
+            return false;
430
+        }
431
+
432
+        if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
433
+            return false;
434
+        }
435
+
436
+        if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
437
+            return false;
438
+        }
439
+
440
+        if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
441
+            return false;
442
+        }
443
+
444
+        return true;
445
+    }
446
+
447
+    /**
448
+     * Check if the required FreeType functions are present
449
+     * @return bool
450
+     */
451
+    protected function hasFreeTypeSupport() {
452
+        return function_exists('imagettfbbox') && function_exists('imagettftext');
453
+    }
454
+
455
+    protected function hasMissingIndexes(): array {
456
+        $indexInfo = new MissingIndexInformation();
457
+        // Dispatch event so apps can also hint for pending index updates if needed
458
+        $event = new GenericEvent($indexInfo);
459
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
460
+
461
+        return $indexInfo->getListOfMissingIndexes();
462
+    }
463
+
464
+    protected function hasMissingPrimaryKeys(): array {
465
+        $info = new MissingPrimaryKeyInformation();
466
+        // Dispatch event so apps can also hint for pending index updates if needed
467
+        $event = new GenericEvent($info);
468
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
469
+
470
+        return $info->getListOfMissingPrimaryKeys();
471
+    }
472
+
473
+    protected function hasMissingColumns(): array {
474
+        $indexInfo = new MissingColumnInformation();
475
+        // Dispatch event so apps can also hint for pending index updates if needed
476
+        $event = new GenericEvent($indexInfo);
477
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
478
+
479
+        return $indexInfo->getListOfMissingColumns();
480
+    }
481
+
482
+    protected function isSqliteUsed() {
483
+        return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
484
+    }
485
+
486
+    protected function isReadOnlyConfig(): bool {
487
+        return \OC_Helper::isReadOnlyConfigEnabled();
488
+    }
489
+
490
+    protected function hasValidTransactionIsolationLevel(): bool {
491
+        try {
492
+            if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
493
+                return true;
494
+            }
495
+
496
+            return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
497
+        } catch (Exception $e) {
498
+            // ignore
499
+        }
500
+
501
+        return true;
502
+    }
503
+
504
+    protected function hasFileinfoInstalled(): bool {
505
+        return \OC_Util::fileInfoLoaded();
506
+    }
507
+
508
+    protected function hasWorkingFileLocking(): bool {
509
+        return !($this->lockingProvider instanceof NoopLockingProvider);
510
+    }
511
+
512
+    protected function getSuggestedOverwriteCliURL(): string {
513
+        $suggestedOverwriteCliUrl = '';
514
+        if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
515
+            $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
516
+            if (!$this->config->getSystemValue('config_is_read_only', false)) {
517
+                // Set the overwrite URL when it was not set yet.
518
+                $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
519
+                $suggestedOverwriteCliUrl = '';
520
+            }
521
+        }
522
+        return $suggestedOverwriteCliUrl;
523
+    }
524
+
525
+    protected function getLastCronInfo(): array {
526
+        $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
527
+        return [
528
+            'diffInSeconds' => time() - $lastCronRun,
529
+            'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
530
+            'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
531
+        ];
532
+    }
533
+
534
+    protected function getCronErrors() {
535
+        $errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
536
+
537
+        if (is_array($errors)) {
538
+            return $errors;
539
+        }
540
+
541
+        return [];
542
+    }
543
+
544
+    protected function hasOpcacheLoaded(): bool {
545
+        return extension_loaded('Zend OPcache');
546
+    }
547
+
548
+    /**
549
+     * Iterates through the configured app roots and
550
+     * tests if the subdirectories are owned by the same user than the current user.
551
+     *
552
+     * @return array
553
+     */
554
+    protected function getAppDirsWithDifferentOwner(): array {
555
+        $currentUser = posix_getuid();
556
+        $appDirsWithDifferentOwner = [[]];
557
+
558
+        foreach (OC::$APPSROOTS as $appRoot) {
559
+            if ($appRoot['writable'] === true) {
560
+                $appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
561
+            }
562
+        }
563
+
564
+        $appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
565
+        sort($appDirsWithDifferentOwner);
566
+
567
+        return $appDirsWithDifferentOwner;
568
+    }
569
+
570
+    /**
571
+     * Tests if the directories for one apps directory are writable by the current user.
572
+     *
573
+     * @param int $currentUser The current user
574
+     * @param array $appRoot The app root config
575
+     * @return string[] The none writable directory paths inside the app root
576
+     */
577
+    private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
578
+        $appDirsWithDifferentOwner = [];
579
+        $appsPath = $appRoot['path'];
580
+        $appsDir = new DirectoryIterator($appRoot['path']);
581
+
582
+        foreach ($appsDir as $fileInfo) {
583
+            if ($fileInfo->isDir() && !$fileInfo->isDot()) {
584
+                $absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
585
+                $appDirUser = fileowner($absAppPath);
586
+                if ($appDirUser !== $currentUser) {
587
+                    $appDirsWithDifferentOwner[] = $absAppPath;
588
+                }
589
+            }
590
+        }
591
+
592
+        return $appDirsWithDifferentOwner;
593
+    }
594
+
595
+    /**
596
+     * Checks for potential PHP modules that would improve the instance
597
+     *
598
+     * @return string[] A list of PHP modules that is recommended
599
+     */
600
+    protected function hasRecommendedPHPModules(): array {
601
+        $recommendedPHPModules = [];
602
+
603
+        if (!extension_loaded('intl')) {
604
+            $recommendedPHPModules[] = 'intl';
605
+        }
606
+
607
+        if (!extension_loaded('bcmath')) {
608
+            $recommendedPHPModules[] = 'bcmath';
609
+        }
610
+
611
+        if (!extension_loaded('gmp')) {
612
+            $recommendedPHPModules[] = 'gmp';
613
+        }
614
+
615
+        if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
616
+            if (!extension_loaded('imagick')) {
617
+                $recommendedPHPModules[] = 'imagick';
618
+            }
619
+        }
620
+
621
+        return $recommendedPHPModules;
622
+    }
623
+
624
+    protected function isMysqlUsedWithoutUTF8MB4(): bool {
625
+        return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
626
+    }
627
+
628
+    protected function hasBigIntConversionPendingColumns(): array {
629
+        // copy of ConvertFilecacheBigInt::getColumnsByTable()
630
+        $tables = [
631
+            'activity' => ['activity_id', 'object_id'],
632
+            'activity_mq' => ['mail_id'],
633
+            'authtoken' => ['id'],
634
+            'bruteforce_attempts' => ['id'],
635
+            'federated_reshares' => ['share_id'],
636
+            'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
637
+            'filecache_extended' => ['fileid'],
638
+            'file_locks' => ['id'],
639
+            'jobs' => ['id'],
640
+            'mimetypes' => ['id'],
641
+            'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
642
+            'share_external' => ['id', 'parent'],
643
+            'storages' => ['numeric_id'],
644
+        ];
645
+
646
+        $schema = new SchemaWrapper($this->db);
647
+        $isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
648
+        $pendingColumns = [];
649
+
650
+        foreach ($tables as $tableName => $columns) {
651
+            if (!$schema->hasTable($tableName)) {
652
+                continue;
653
+            }
654
+
655
+            $table = $schema->getTable($tableName);
656
+            foreach ($columns as $columnName) {
657
+                $column = $table->getColumn($columnName);
658
+                $isAutoIncrement = $column->getAutoincrement();
659
+                $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
660
+                if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
661
+                    $pendingColumns[] = $tableName . '.' . $columnName;
662
+                }
663
+            }
664
+        }
665
+
666
+        return $pendingColumns;
667
+    }
668
+
669
+    protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
670
+        $objectStore = $this->config->getSystemValue('objectstore', null);
671
+        $objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
672
+
673
+        if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
674
+            return true;
675
+        }
676
+
677
+        if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
678
+            return true;
679
+        }
680
+
681
+        if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
682
+            return true;
683
+        }
684
+
685
+        $tempPath = sys_get_temp_dir();
686
+        if (!is_dir($tempPath)) {
687
+            $this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
688
+            return false;
689
+        }
690
+        $freeSpaceInTemp = disk_free_space($tempPath);
691
+        if ($freeSpaceInTemp === false) {
692
+            $this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
693
+            return false;
694
+        }
695
+
696
+        $freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
697
+        if ($freeSpaceInTempInGB > 50) {
698
+            return true;
699
+        }
700
+
701
+        $this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
702
+        return false;
703
+    }
704
+
705
+    protected function imageMagickLacksSVGSupport(): bool {
706
+        return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
707
+    }
708
+
709
+    /**
710
+     * @return DataResponse
711
+     */
712
+    public function check() {
713
+        $phpDefaultCharset = new PhpDefaultCharset();
714
+        $phpOutputBuffering = new PhpOutputBuffering();
715
+        $legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
716
+        $checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
717
+
718
+        return new DataResponse(
719
+            [
720
+                'isGetenvServerWorking' => !empty(getenv('PATH')),
721
+                'isReadOnlyConfig' => $this->isReadOnlyConfig(),
722
+                'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
723
+                'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
724
+                'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
725
+                'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
726
+                'cronInfo' => $this->getLastCronInfo(),
727
+                'cronErrors' => $this->getCronErrors(),
728
+                'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
729
+                'isMemcacheConfigured' => $this->isMemcacheConfigured(),
730
+                'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
731
+                'isRandomnessSecure' => $this->isRandomnessSecure(),
732
+                'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
733
+                'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
734
+                'phpSupported' => $this->isPhpSupported(),
735
+                'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
736
+                'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
737
+                'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
738
+                'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
739
+                'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
740
+                'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
741
+                'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
742
+                'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
743
+                'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
744
+                'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
745
+                'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
746
+                'missingIndexes' => $this->hasMissingIndexes(),
747
+                'missingColumns' => $this->hasMissingColumns(),
748
+                'isSqliteUsed' => $this->isSqliteUsed(),
749
+                'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
750
+                'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
751
+                'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
752
+                'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
753
+                'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
754
+                'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
755
+                'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
756
+                'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
757
+                'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
758
+                PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
759
+                PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
760
+                LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
761
+                CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
762
+                'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
763
+            ]
764
+        );
765
+    }
766 766
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Controller/AWorkflowController.php 1 patch
Indentation   +124 added lines, -124 removed lines patch added patch discarded remove patch
@@ -37,128 +37,128 @@
 block discarded – undo
37 37
 
38 38
 abstract class AWorkflowController extends OCSController {
39 39
 
40
-	/** @var Manager */
41
-	protected $manager;
42
-
43
-	public function __construct(
44
-		$appName,
45
-		IRequest $request,
46
-		Manager $manager
47
-	) {
48
-		parent::__construct($appName, $request);
49
-
50
-		$this->manager = $manager;
51
-	}
52
-
53
-	/**
54
-	 * @throws OCSForbiddenException
55
-	 */
56
-	abstract protected function getScopeContext(): ScopeContext;
57
-
58
-	/**
59
-	 * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json"
60
-	 *
61
-	 * @throws OCSForbiddenException
62
-	 */
63
-	public function index(): DataResponse {
64
-		$operationsByClass = $this->manager->getAllOperations($this->getScopeContext());
65
-
66
-		foreach ($operationsByClass as &$operations) {
67
-			foreach ($operations as &$operation) {
68
-				$operation = $this->manager->formatOperation($operation);
69
-			}
70
-		}
71
-
72
-		return new DataResponse($operationsByClass);
73
-	}
74
-
75
-	/**
76
-	 * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json"
77
-	 *
78
-	 * @throws OCSForbiddenException
79
-	 */
80
-	public function show(string $id): DataResponse {
81
-		$context = $this->getScopeContext();
82
-
83
-		// The ID corresponds to a class name
84
-		$operations = $this->manager->getOperations($id, $context);
85
-
86
-		foreach ($operations as &$operation) {
87
-			$operation = $this->manager->formatOperation($operation);
88
-		}
89
-
90
-		return new DataResponse($operations);
91
-	}
92
-
93
-	/**
94
-	 * @throws OCSBadRequestException
95
-	 * @throws OCSForbiddenException
96
-	 * @throws OCSException
97
-	 */
98
-	public function create(
99
-		string $class,
100
-		string $name,
101
-		array $checks,
102
-		string $operation,
103
-		string $entity,
104
-		array $events
105
-	): DataResponse {
106
-		$context = $this->getScopeContext();
107
-		try {
108
-			$operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events);
109
-			$operation = $this->manager->formatOperation($operation);
110
-			return new DataResponse($operation);
111
-		} catch (\UnexpectedValueException $e) {
112
-			throw new OCSBadRequestException($e->getMessage(), $e);
113
-		} catch (\DomainException $e) {
114
-			throw new OCSForbiddenException($e->getMessage(), $e);
115
-		} catch (Exception $e) {
116
-			throw new OCSException('An internal error occurred', $e->getCode(), $e);
117
-		}
118
-	}
119
-
120
-	/**
121
-	 * @throws OCSBadRequestException
122
-	 * @throws OCSForbiddenException
123
-	 * @throws OCSException
124
-	 */
125
-	public function update(
126
-		int $id,
127
-		string $name,
128
-		array $checks,
129
-		string $operation,
130
-		string $entity,
131
-		array $events
132
-	): DataResponse {
133
-		try {
134
-			$context = $this->getScopeContext();
135
-			$operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events);
136
-			$operation = $this->manager->formatOperation($operation);
137
-			return new DataResponse($operation);
138
-		} catch (\UnexpectedValueException $e) {
139
-			throw new OCSBadRequestException($e->getMessage(), $e);
140
-		} catch (\DomainException $e) {
141
-			throw new OCSForbiddenException($e->getMessage(), $e);
142
-		} catch (Exception $e) {
143
-			throw new OCSException('An internal error occurred', $e->getCode(), $e);
144
-		}
145
-	}
146
-
147
-	/**
148
-	 * @throws OCSBadRequestException
149
-	 * @throws OCSForbiddenException
150
-	 * @throws OCSException
151
-	 */
152
-	public function destroy(int $id): DataResponse {
153
-		try {
154
-			$deleted = $this->manager->deleteOperation($id, $this->getScopeContext());
155
-			return new DataResponse($deleted);
156
-		} catch (\UnexpectedValueException $e) {
157
-			throw new OCSBadRequestException($e->getMessage(), $e);
158
-		} catch (\DomainException $e) {
159
-			throw new OCSForbiddenException($e->getMessage(), $e);
160
-		} catch (Exception $e) {
161
-			throw new OCSException('An internal error occurred', $e->getCode(), $e);
162
-		}
163
-	}
40
+    /** @var Manager */
41
+    protected $manager;
42
+
43
+    public function __construct(
44
+        $appName,
45
+        IRequest $request,
46
+        Manager $manager
47
+    ) {
48
+        parent::__construct($appName, $request);
49
+
50
+        $this->manager = $manager;
51
+    }
52
+
53
+    /**
54
+     * @throws OCSForbiddenException
55
+     */
56
+    abstract protected function getScopeContext(): ScopeContext;
57
+
58
+    /**
59
+     * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json"
60
+     *
61
+     * @throws OCSForbiddenException
62
+     */
63
+    public function index(): DataResponse {
64
+        $operationsByClass = $this->manager->getAllOperations($this->getScopeContext());
65
+
66
+        foreach ($operationsByClass as &$operations) {
67
+            foreach ($operations as &$operation) {
68
+                $operation = $this->manager->formatOperation($operation);
69
+            }
70
+        }
71
+
72
+        return new DataResponse($operationsByClass);
73
+    }
74
+
75
+    /**
76
+     * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json"
77
+     *
78
+     * @throws OCSForbiddenException
79
+     */
80
+    public function show(string $id): DataResponse {
81
+        $context = $this->getScopeContext();
82
+
83
+        // The ID corresponds to a class name
84
+        $operations = $this->manager->getOperations($id, $context);
85
+
86
+        foreach ($operations as &$operation) {
87
+            $operation = $this->manager->formatOperation($operation);
88
+        }
89
+
90
+        return new DataResponse($operations);
91
+    }
92
+
93
+    /**
94
+     * @throws OCSBadRequestException
95
+     * @throws OCSForbiddenException
96
+     * @throws OCSException
97
+     */
98
+    public function create(
99
+        string $class,
100
+        string $name,
101
+        array $checks,
102
+        string $operation,
103
+        string $entity,
104
+        array $events
105
+    ): DataResponse {
106
+        $context = $this->getScopeContext();
107
+        try {
108
+            $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events);
109
+            $operation = $this->manager->formatOperation($operation);
110
+            return new DataResponse($operation);
111
+        } catch (\UnexpectedValueException $e) {
112
+            throw new OCSBadRequestException($e->getMessage(), $e);
113
+        } catch (\DomainException $e) {
114
+            throw new OCSForbiddenException($e->getMessage(), $e);
115
+        } catch (Exception $e) {
116
+            throw new OCSException('An internal error occurred', $e->getCode(), $e);
117
+        }
118
+    }
119
+
120
+    /**
121
+     * @throws OCSBadRequestException
122
+     * @throws OCSForbiddenException
123
+     * @throws OCSException
124
+     */
125
+    public function update(
126
+        int $id,
127
+        string $name,
128
+        array $checks,
129
+        string $operation,
130
+        string $entity,
131
+        array $events
132
+    ): DataResponse {
133
+        try {
134
+            $context = $this->getScopeContext();
135
+            $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events);
136
+            $operation = $this->manager->formatOperation($operation);
137
+            return new DataResponse($operation);
138
+        } catch (\UnexpectedValueException $e) {
139
+            throw new OCSBadRequestException($e->getMessage(), $e);
140
+        } catch (\DomainException $e) {
141
+            throw new OCSForbiddenException($e->getMessage(), $e);
142
+        } catch (Exception $e) {
143
+            throw new OCSException('An internal error occurred', $e->getCode(), $e);
144
+        }
145
+    }
146
+
147
+    /**
148
+     * @throws OCSBadRequestException
149
+     * @throws OCSForbiddenException
150
+     * @throws OCSException
151
+     */
152
+    public function destroy(int $id): DataResponse {
153
+        try {
154
+            $deleted = $this->manager->deleteOperation($id, $this->getScopeContext());
155
+            return new DataResponse($deleted);
156
+        } catch (\UnexpectedValueException $e) {
157
+            throw new OCSBadRequestException($e->getMessage(), $e);
158
+        } catch (\DomainException $e) {
159
+            throw new OCSForbiddenException($e->getMessage(), $e);
160
+        } catch (Exception $e) {
161
+            throw new OCSException('An internal error occurred', $e->getCode(), $e);
162
+        }
163
+    }
164 164
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php 1 patch
Indentation   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -33,46 +33,46 @@
 block discarded – undo
33 33
 
34 34
 class PopulateNewlyIntroducedDatabaseFields implements IRepairStep {
35 35
 
36
-	/** @var IDBConnection */
37
-	private $dbc;
36
+    /** @var IDBConnection */
37
+    private $dbc;
38 38
 
39
-	public function __construct(IDBConnection $dbc) {
40
-		$this->dbc = $dbc;
41
-	}
39
+    public function __construct(IDBConnection $dbc) {
40
+        $this->dbc = $dbc;
41
+    }
42 42
 
43
-	public function getName() {
44
-		return 'Populating added database structures for workflows';
45
-	}
43
+    public function getName() {
44
+        return 'Populating added database structures for workflows';
45
+    }
46 46
 
47
-	public function run(IOutput $output) {
48
-		$result = $this->getIdsWithoutScope();
47
+    public function run(IOutput $output) {
48
+        $result = $this->getIdsWithoutScope();
49 49
 
50
-		$this->populateScopeTable($result);
50
+        $this->populateScopeTable($result);
51 51
 
52
-		$result->closeCursor();
53
-	}
52
+        $result->closeCursor();
53
+    }
54 54
 
55
-	protected function populateScopeTable(IResult $ids): void {
56
-		$qb = $this->dbc->getQueryBuilder();
55
+    protected function populateScopeTable(IResult $ids): void {
56
+        $qb = $this->dbc->getQueryBuilder();
57 57
 
58
-		$insertQuery = $qb->insert('flow_operations_scope');
59
-		while ($id = $ids->fetchOne()) {
60
-			$insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]);
61
-			$insertQuery->execute();
62
-		}
63
-	}
58
+        $insertQuery = $qb->insert('flow_operations_scope');
59
+        while ($id = $ids->fetchOne()) {
60
+            $insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]);
61
+            $insertQuery->execute();
62
+        }
63
+    }
64 64
 
65
-	protected function getIdsWithoutScope(): IResult {
66
-		$qb = $this->dbc->getQueryBuilder();
67
-		$selectQuery = $qb->select('o.id')
68
-			->from('flow_operations', 'o')
69
-			->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
70
-			->where($qb->expr()->isNull('s.operation_id'));
71
-		// The left join operation is not necessary, usually, but it's a safe-guard
72
-		// in case the repair step is executed multiple times for whatever reason.
65
+    protected function getIdsWithoutScope(): IResult {
66
+        $qb = $this->dbc->getQueryBuilder();
67
+        $selectQuery = $qb->select('o.id')
68
+            ->from('flow_operations', 'o')
69
+            ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
70
+            ->where($qb->expr()->isNull('s.operation_id'));
71
+        // The left join operation is not necessary, usually, but it's a safe-guard
72
+        // in case the repair step is executed multiple times for whatever reason.
73 73
 
74
-		/** @var IResult $result */
75
-		$result = $selectQuery->execute();
76
-		return $result;
77
-	}
74
+        /** @var IResult $result */
75
+        $result = $selectQuery->execute();
76
+        return $result;
77
+    }
78 78
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Manager.php 2 patches
Indentation   +666 added lines, -666 removed lines patch added patch discarded remove patch
@@ -62,670 +62,670 @@
 block discarded – undo
62 62
 
63 63
 class Manager implements IManager {
64 64
 
65
-	/** @var IStorage */
66
-	protected $storage;
67
-
68
-	/** @var string */
69
-	protected $path;
70
-
71
-	/** @var object */
72
-	protected $entity;
73
-
74
-	/** @var array[] */
75
-	protected $operations = [];
76
-
77
-	/** @var array[] */
78
-	protected $checks = [];
79
-
80
-	/** @var IDBConnection */
81
-	protected $connection;
82
-
83
-	/** @var IServerContainer|\OC\Server */
84
-	protected $container;
85
-
86
-	/** @var IL10N */
87
-	protected $l;
88
-
89
-	/** @var LegacyDispatcher */
90
-	protected $legacyEventDispatcher;
91
-
92
-	/** @var IEntity[] */
93
-	protected $registeredEntities = [];
94
-
95
-	/** @var IOperation[] */
96
-	protected $registeredOperators = [];
97
-
98
-	/** @var ICheck[] */
99
-	protected $registeredChecks = [];
100
-
101
-	/** @var ILogger */
102
-	protected $logger;
103
-
104
-	/** @var CappedMemoryCache */
105
-	protected $operationsByScope = [];
106
-
107
-	/** @var IUserSession */
108
-	protected $session;
109
-
110
-	/** @var IEventDispatcher */
111
-	private $dispatcher;
112
-
113
-	/** @var IConfig */
114
-	private $config;
115
-
116
-	public function __construct(
117
-		IDBConnection $connection,
118
-		IServerContainer $container,
119
-		IL10N $l,
120
-		LegacyDispatcher $eventDispatcher,
121
-		ILogger $logger,
122
-		IUserSession $session,
123
-		IEventDispatcher $dispatcher,
124
-		IConfig $config
125
-	) {
126
-		$this->connection = $connection;
127
-		$this->container = $container;
128
-		$this->l = $l;
129
-		$this->legacyEventDispatcher = $eventDispatcher;
130
-		$this->logger = $logger;
131
-		$this->operationsByScope = new CappedMemoryCache(64);
132
-		$this->session = $session;
133
-		$this->dispatcher = $dispatcher;
134
-		$this->config = $config;
135
-	}
136
-
137
-	public function getRuleMatcher(): IRuleMatcher {
138
-		return new RuleMatcher(
139
-			$this->session,
140
-			$this->container,
141
-			$this->l,
142
-			$this,
143
-			$this->container->query(Logger::class)
144
-		);
145
-	}
146
-
147
-	public function getAllConfiguredEvents() {
148
-		$query = $this->connection->getQueryBuilder();
149
-
150
-		$query->select('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR))
151
-			->from('flow_operations')
152
-			->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
153
-			->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
154
-
155
-		$result = $query->execute();
156
-		$operations = [];
157
-		while ($row = $result->fetch()) {
158
-			$eventNames = \json_decode($row['events']);
159
-
160
-			$operation = $row['class'];
161
-			$entity = $row['entity'];
162
-
163
-			$operations[$operation] = $operations[$row['class']] ?? [];
164
-			$operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
165
-
166
-			$operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
167
-		}
168
-		$result->closeCursor();
169
-
170
-		return $operations;
171
-	}
172
-
173
-	/**
174
-	 * @param string $operationClass
175
-	 * @return ScopeContext[]
176
-	 */
177
-	public function getAllConfiguredScopesForOperation(string $operationClass): array {
178
-		static $scopesByOperation = [];
179
-		if (isset($scopesByOperation[$operationClass])) {
180
-			return $scopesByOperation[$operationClass];
181
-		}
182
-
183
-		$query = $this->connection->getQueryBuilder();
184
-
185
-		$query->selectDistinct('s.type')
186
-			->addSelect('s.value')
187
-			->from('flow_operations', 'o')
188
-			->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
189
-			->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
190
-
191
-		$query->setParameters(['operationClass' => $operationClass]);
192
-		$result = $query->execute();
193
-
194
-		$scopesByOperation[$operationClass] = [];
195
-		while ($row = $result->fetch()) {
196
-			$scope = new ScopeContext($row['type'], $row['value']);
197
-			$scopesByOperation[$operationClass][$scope->getHash()] = $scope;
198
-		}
199
-
200
-		return $scopesByOperation[$operationClass];
201
-	}
202
-
203
-	public function getAllOperations(ScopeContext $scopeContext): array {
204
-		if (isset($this->operations[$scopeContext->getHash()])) {
205
-			return $this->operations[$scopeContext->getHash()];
206
-		}
207
-
208
-		$query = $this->connection->getQueryBuilder();
209
-
210
-		$query->select('o.*')
211
-			->selectAlias('s.type', 'scope_type')
212
-			->selectAlias('s.value', 'scope_actor_id')
213
-			->from('flow_operations', 'o')
214
-			->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
215
-			->where($query->expr()->eq('s.type', $query->createParameter('scope')));
216
-
217
-		if ($scopeContext->getScope() === IManager::SCOPE_USER) {
218
-			$query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
219
-		}
220
-
221
-		$query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
222
-		$result = $query->execute();
223
-
224
-		$this->operations[$scopeContext->getHash()] = [];
225
-		while ($row = $result->fetch()) {
226
-			if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
227
-				$this->operations[$scopeContext->getHash()][$row['class']] = [];
228
-			}
229
-			$this->operations[$scopeContext->getHash()][$row['class']][] = $row;
230
-		}
231
-
232
-		return $this->operations[$scopeContext->getHash()];
233
-	}
234
-
235
-	public function getOperations(string $class, ScopeContext $scopeContext): array {
236
-		if (!isset($this->operations[$scopeContext->getHash()])) {
237
-			$this->getAllOperations($scopeContext);
238
-		}
239
-		return $this->operations[$scopeContext->getHash()][$class] ?? [];
240
-	}
241
-
242
-	/**
243
-	 * @param int $id
244
-	 * @return array
245
-	 * @throws \UnexpectedValueException
246
-	 */
247
-	protected function getOperation($id) {
248
-		$query = $this->connection->getQueryBuilder();
249
-		$query->select('*')
250
-			->from('flow_operations')
251
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
252
-		$result = $query->execute();
253
-		$row = $result->fetch();
254
-		$result->closeCursor();
255
-
256
-		if ($row) {
257
-			return $row;
258
-		}
259
-
260
-		throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
261
-	}
262
-
263
-	protected function insertOperation(
264
-		string $class,
265
-		string $name,
266
-		array $checkIds,
267
-		string $operation,
268
-		string $entity,
269
-		array $events
270
-	): int {
271
-		$query = $this->connection->getQueryBuilder();
272
-		$query->insert('flow_operations')
273
-			->values([
274
-				'class' => $query->createNamedParameter($class),
275
-				'name' => $query->createNamedParameter($name),
276
-				'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
277
-				'operation' => $query->createNamedParameter($operation),
278
-				'entity' => $query->createNamedParameter($entity),
279
-				'events' => $query->createNamedParameter(json_encode($events))
280
-			]);
281
-		$query->execute();
282
-
283
-		return $query->getLastInsertId();
284
-	}
285
-
286
-	/**
287
-	 * @param string $class
288
-	 * @param string $name
289
-	 * @param array[] $checks
290
-	 * @param string $operation
291
-	 * @return array The added operation
292
-	 * @throws \UnexpectedValueException
293
-	 * @throw Exception
294
-	 */
295
-	public function addOperation(
296
-		string $class,
297
-		string $name,
298
-		array $checks,
299
-		string $operation,
300
-		ScopeContext $scope,
301
-		string $entity,
302
-		array $events
303
-	) {
304
-		$this->validateOperation($class, $name, $checks, $operation, $entity, $events);
305
-
306
-		$this->connection->beginTransaction();
307
-
308
-		try {
309
-			$checkIds = [];
310
-			foreach ($checks as $check) {
311
-				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
312
-			}
313
-
314
-			$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
315
-			$this->addScope($id, $scope);
316
-
317
-			$this->connection->commit();
318
-		} catch (Exception $e) {
319
-			$this->connection->rollBack();
320
-			throw $e;
321
-		}
322
-
323
-		return $this->getOperation($id);
324
-	}
325
-
326
-	protected function canModify(int $id, ScopeContext $scopeContext):bool {
327
-		if (isset($this->operationsByScope[$scopeContext->getHash()])) {
328
-			return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
329
-		}
330
-
331
-		$qb = $this->connection->getQueryBuilder();
332
-		$qb = $qb->select('o.id')
333
-			->from('flow_operations', 'o')
334
-			->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
335
-			->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
336
-
337
-		if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
338
-			$qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
339
-		}
340
-
341
-		$qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
342
-		$result = $qb->execute();
343
-
344
-		$this->operationsByScope[$scopeContext->getHash()] = [];
345
-		while ($opId = $result->fetchOne()) {
346
-			$this->operationsByScope[$scopeContext->getHash()][] = (int)$opId;
347
-		}
348
-		$result->closeCursor();
349
-
350
-		return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
351
-	}
352
-
353
-	/**
354
-	 * @param int $id
355
-	 * @param string $name
356
-	 * @param array[] $checks
357
-	 * @param string $operation
358
-	 * @return array The updated operation
359
-	 * @throws \UnexpectedValueException
360
-	 * @throws \DomainException
361
-	 * @throws Exception
362
-	 */
363
-	public function updateOperation(
364
-		int $id,
365
-		string $name,
366
-		array $checks,
367
-		string $operation,
368
-		ScopeContext $scopeContext,
369
-		string $entity,
370
-		array $events
371
-	): array {
372
-		if (!$this->canModify($id, $scopeContext)) {
373
-			throw new \DomainException('Target operation not within scope');
374
-		};
375
-		$row = $this->getOperation($id);
376
-		$this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
377
-
378
-		$checkIds = [];
379
-		try {
380
-			$this->connection->beginTransaction();
381
-			foreach ($checks as $check) {
382
-				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
383
-			}
384
-
385
-			$query = $this->connection->getQueryBuilder();
386
-			$query->update('flow_operations')
387
-				->set('name', $query->createNamedParameter($name))
388
-				->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
389
-				->set('operation', $query->createNamedParameter($operation))
390
-				->set('entity', $query->createNamedParameter($entity))
391
-				->set('events', $query->createNamedParameter(json_encode($events)))
392
-				->where($query->expr()->eq('id', $query->createNamedParameter($id)));
393
-			$query->execute();
394
-			$this->connection->commit();
395
-		} catch (Exception $e) {
396
-			$this->connection->rollBack();
397
-			throw $e;
398
-		}
399
-		unset($this->operations[$scopeContext->getHash()]);
400
-
401
-		return $this->getOperation($id);
402
-	}
403
-
404
-	/**
405
-	 * @param int $id
406
-	 * @return bool
407
-	 * @throws \UnexpectedValueException
408
-	 * @throws Exception
409
-	 * @throws \DomainException
410
-	 */
411
-	public function deleteOperation($id, ScopeContext $scopeContext) {
412
-		if (!$this->canModify($id, $scopeContext)) {
413
-			throw new \DomainException('Target operation not within scope');
414
-		};
415
-		$query = $this->connection->getQueryBuilder();
416
-		try {
417
-			$this->connection->beginTransaction();
418
-			$result = (bool)$query->delete('flow_operations')
419
-				->where($query->expr()->eq('id', $query->createNamedParameter($id)))
420
-				->execute();
421
-			if ($result) {
422
-				$qb = $this->connection->getQueryBuilder();
423
-				$result &= (bool)$qb->delete('flow_operations_scope')
424
-					->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
425
-					->execute();
426
-			}
427
-			$this->connection->commit();
428
-		} catch (Exception $e) {
429
-			$this->connection->rollBack();
430
-			throw $e;
431
-		}
432
-
433
-		if (isset($this->operations[$scopeContext->getHash()])) {
434
-			unset($this->operations[$scopeContext->getHash()]);
435
-		}
436
-
437
-		return $result;
438
-	}
439
-
440
-	protected function validateEvents(string $entity, array $events, IOperation $operation) {
441
-		try {
442
-			/** @var IEntity $instance */
443
-			$instance = $this->container->query($entity);
444
-		} catch (QueryException $e) {
445
-			throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
446
-		}
447
-
448
-		if (!$instance instanceof IEntity) {
449
-			throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
450
-		}
451
-
452
-		if (empty($events)) {
453
-			if (!$operation instanceof IComplexOperation) {
454
-				throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
455
-			}
456
-			return;
457
-		}
458
-
459
-		$availableEvents = [];
460
-		foreach ($instance->getEvents() as $event) {
461
-			/** @var IEntityEvent $event */
462
-			$availableEvents[] = $event->getEventName();
463
-		}
464
-
465
-		$diff = array_diff($events, $availableEvents);
466
-		if (!empty($diff)) {
467
-			throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
468
-		}
469
-	}
470
-
471
-	/**
472
-	 * @param string $class
473
-	 * @param string $name
474
-	 * @param array[] $checks
475
-	 * @param string $operation
476
-	 * @throws \UnexpectedValueException
477
-	 */
478
-	public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
479
-		try {
480
-			/** @var IOperation $instance */
481
-			$instance = $this->container->query($class);
482
-		} catch (QueryException $e) {
483
-			throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
484
-		}
485
-
486
-		if (!($instance instanceof IOperation)) {
487
-			throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
488
-		}
489
-
490
-		$this->validateEvents($entity, $events, $instance);
491
-
492
-		if (count($checks) === 0) {
493
-			throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
494
-		}
495
-
496
-		if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
497
-			throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
498
-		}
499
-
500
-		$instance->validateOperation($name, $checks, $operation);
501
-
502
-		foreach ($checks as $check) {
503
-			if (!is_string($check['class'])) {
504
-				throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
505
-			}
506
-
507
-			try {
508
-				/** @var ICheck $instance */
509
-				$instance = $this->container->query($check['class']);
510
-			} catch (QueryException $e) {
511
-				throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
512
-			}
513
-
514
-			if (!($instance instanceof ICheck)) {
515
-				throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
516
-			}
517
-
518
-			if (!empty($instance->supportedEntities())
519
-				&& !in_array($entity, $instance->supportedEntities())
520
-			) {
521
-				throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
522
-			}
523
-
524
-			if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
525
-				throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
526
-			}
527
-
528
-			$instance->validateCheck($check['operator'], $check['value']);
529
-		}
530
-	}
531
-
532
-	/**
533
-	 * @param int[] $checkIds
534
-	 * @return array[]
535
-	 */
536
-	public function getChecks(array $checkIds) {
537
-		$checkIds = array_map('intval', $checkIds);
538
-
539
-		$checks = [];
540
-		foreach ($checkIds as $i => $checkId) {
541
-			if (isset($this->checks[$checkId])) {
542
-				$checks[$checkId] = $this->checks[$checkId];
543
-				unset($checkIds[$i]);
544
-			}
545
-		}
546
-
547
-		if (empty($checkIds)) {
548
-			return $checks;
549
-		}
550
-
551
-		$query = $this->connection->getQueryBuilder();
552
-		$query->select('*')
553
-			->from('flow_checks')
554
-			->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
555
-		$result = $query->execute();
556
-
557
-		while ($row = $result->fetch()) {
558
-			$this->checks[(int) $row['id']] = $row;
559
-			$checks[(int) $row['id']] = $row;
560
-		}
561
-		$result->closeCursor();
562
-
563
-		$checkIds = array_diff($checkIds, array_keys($checks));
564
-
565
-		if (!empty($checkIds)) {
566
-			$missingCheck = array_pop($checkIds);
567
-			throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
568
-		}
569
-
570
-		return $checks;
571
-	}
572
-
573
-	/**
574
-	 * @param string $class
575
-	 * @param string $operator
576
-	 * @param string $value
577
-	 * @return int Check unique ID
578
-	 */
579
-	protected function addCheck($class, $operator, $value) {
580
-		$hash = md5($class . '::' . $operator . '::' . $value);
581
-
582
-		$query = $this->connection->getQueryBuilder();
583
-		$query->select('id')
584
-			->from('flow_checks')
585
-			->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
586
-		$result = $query->execute();
587
-
588
-		if ($row = $result->fetch()) {
589
-			$result->closeCursor();
590
-			return (int) $row['id'];
591
-		}
592
-
593
-		$query = $this->connection->getQueryBuilder();
594
-		$query->insert('flow_checks')
595
-			->values([
596
-				'class' => $query->createNamedParameter($class),
597
-				'operator' => $query->createNamedParameter($operator),
598
-				'value' => $query->createNamedParameter($value),
599
-				'hash' => $query->createNamedParameter($hash),
600
-			]);
601
-		$query->execute();
602
-
603
-		return $query->getLastInsertId();
604
-	}
605
-
606
-	protected function addScope(int $operationId, ScopeContext $scope): void {
607
-		$query = $this->connection->getQueryBuilder();
608
-
609
-		$insertQuery = $query->insert('flow_operations_scope');
610
-		$insertQuery->values([
611
-			'operation_id' => $query->createNamedParameter($operationId),
612
-			'type' => $query->createNamedParameter($scope->getScope()),
613
-			'value' => $query->createNamedParameter($scope->getScopeId()),
614
-		]);
615
-		$insertQuery->execute();
616
-	}
617
-
618
-	public function formatOperation(array $operation): array {
619
-		$checkIds = json_decode($operation['checks'], true);
620
-		$checks = $this->getChecks($checkIds);
621
-
622
-		$operation['checks'] = [];
623
-		foreach ($checks as $check) {
624
-			// Remove internal values
625
-			unset($check['id']);
626
-			unset($check['hash']);
627
-
628
-			$operation['checks'][] = $check;
629
-		}
630
-		$operation['events'] = json_decode($operation['events'], true) ?? [];
631
-
632
-
633
-		return $operation;
634
-	}
635
-
636
-	/**
637
-	 * @return IEntity[]
638
-	 */
639
-	public function getEntitiesList(): array {
640
-		$this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
641
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
642
-
643
-		return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
644
-	}
645
-
646
-	/**
647
-	 * @return IOperation[]
648
-	 */
649
-	public function getOperatorList(): array {
650
-		$this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
651
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
652
-
653
-		return array_merge($this->getBuildInOperators(), $this->registeredOperators);
654
-	}
655
-
656
-	/**
657
-	 * @return ICheck[]
658
-	 */
659
-	public function getCheckList(): array {
660
-		$this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
661
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
662
-
663
-		return array_merge($this->getBuildInChecks(), $this->registeredChecks);
664
-	}
665
-
666
-	public function registerEntity(IEntity $entity): void {
667
-		$this->registeredEntities[get_class($entity)] = $entity;
668
-	}
669
-
670
-	public function registerOperation(IOperation $operator): void {
671
-		$this->registeredOperators[get_class($operator)] = $operator;
672
-	}
673
-
674
-	public function registerCheck(ICheck $check): void {
675
-		$this->registeredChecks[get_class($check)] = $check;
676
-	}
677
-
678
-	/**
679
-	 * @return IEntity[]
680
-	 */
681
-	protected function getBuildInEntities(): array {
682
-		try {
683
-			return [
684
-				File::class => $this->container->query(File::class),
685
-			];
686
-		} catch (QueryException $e) {
687
-			$this->logger->logException($e);
688
-			return [];
689
-		}
690
-	}
691
-
692
-	/**
693
-	 * @return IOperation[]
694
-	 */
695
-	protected function getBuildInOperators(): array {
696
-		try {
697
-			return [
698
-				// None yet
699
-			];
700
-		} catch (QueryException $e) {
701
-			$this->logger->logException($e);
702
-			return [];
703
-		}
704
-	}
705
-
706
-	/**
707
-	 * @return ICheck[]
708
-	 */
709
-	protected function getBuildInChecks(): array {
710
-		try {
711
-			return [
712
-				$this->container->query(FileMimeType::class),
713
-				$this->container->query(FileName::class),
714
-				$this->container->query(FileSize::class),
715
-				$this->container->query(FileSystemTags::class),
716
-				$this->container->query(RequestRemoteAddress::class),
717
-				$this->container->query(RequestTime::class),
718
-				$this->container->query(RequestURL::class),
719
-				$this->container->query(RequestUserAgent::class),
720
-				$this->container->query(UserGroupMembership::class),
721
-			];
722
-		} catch (QueryException $e) {
723
-			$this->logger->logException($e);
724
-			return [];
725
-		}
726
-	}
727
-
728
-	public function isUserScopeEnabled(): bool {
729
-		return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
730
-	}
65
+    /** @var IStorage */
66
+    protected $storage;
67
+
68
+    /** @var string */
69
+    protected $path;
70
+
71
+    /** @var object */
72
+    protected $entity;
73
+
74
+    /** @var array[] */
75
+    protected $operations = [];
76
+
77
+    /** @var array[] */
78
+    protected $checks = [];
79
+
80
+    /** @var IDBConnection */
81
+    protected $connection;
82
+
83
+    /** @var IServerContainer|\OC\Server */
84
+    protected $container;
85
+
86
+    /** @var IL10N */
87
+    protected $l;
88
+
89
+    /** @var LegacyDispatcher */
90
+    protected $legacyEventDispatcher;
91
+
92
+    /** @var IEntity[] */
93
+    protected $registeredEntities = [];
94
+
95
+    /** @var IOperation[] */
96
+    protected $registeredOperators = [];
97
+
98
+    /** @var ICheck[] */
99
+    protected $registeredChecks = [];
100
+
101
+    /** @var ILogger */
102
+    protected $logger;
103
+
104
+    /** @var CappedMemoryCache */
105
+    protected $operationsByScope = [];
106
+
107
+    /** @var IUserSession */
108
+    protected $session;
109
+
110
+    /** @var IEventDispatcher */
111
+    private $dispatcher;
112
+
113
+    /** @var IConfig */
114
+    private $config;
115
+
116
+    public function __construct(
117
+        IDBConnection $connection,
118
+        IServerContainer $container,
119
+        IL10N $l,
120
+        LegacyDispatcher $eventDispatcher,
121
+        ILogger $logger,
122
+        IUserSession $session,
123
+        IEventDispatcher $dispatcher,
124
+        IConfig $config
125
+    ) {
126
+        $this->connection = $connection;
127
+        $this->container = $container;
128
+        $this->l = $l;
129
+        $this->legacyEventDispatcher = $eventDispatcher;
130
+        $this->logger = $logger;
131
+        $this->operationsByScope = new CappedMemoryCache(64);
132
+        $this->session = $session;
133
+        $this->dispatcher = $dispatcher;
134
+        $this->config = $config;
135
+    }
136
+
137
+    public function getRuleMatcher(): IRuleMatcher {
138
+        return new RuleMatcher(
139
+            $this->session,
140
+            $this->container,
141
+            $this->l,
142
+            $this,
143
+            $this->container->query(Logger::class)
144
+        );
145
+    }
146
+
147
+    public function getAllConfiguredEvents() {
148
+        $query = $this->connection->getQueryBuilder();
149
+
150
+        $query->select('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR))
151
+            ->from('flow_operations')
152
+            ->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
153
+            ->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
154
+
155
+        $result = $query->execute();
156
+        $operations = [];
157
+        while ($row = $result->fetch()) {
158
+            $eventNames = \json_decode($row['events']);
159
+
160
+            $operation = $row['class'];
161
+            $entity = $row['entity'];
162
+
163
+            $operations[$operation] = $operations[$row['class']] ?? [];
164
+            $operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
165
+
166
+            $operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
167
+        }
168
+        $result->closeCursor();
169
+
170
+        return $operations;
171
+    }
172
+
173
+    /**
174
+     * @param string $operationClass
175
+     * @return ScopeContext[]
176
+     */
177
+    public function getAllConfiguredScopesForOperation(string $operationClass): array {
178
+        static $scopesByOperation = [];
179
+        if (isset($scopesByOperation[$operationClass])) {
180
+            return $scopesByOperation[$operationClass];
181
+        }
182
+
183
+        $query = $this->connection->getQueryBuilder();
184
+
185
+        $query->selectDistinct('s.type')
186
+            ->addSelect('s.value')
187
+            ->from('flow_operations', 'o')
188
+            ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
189
+            ->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
190
+
191
+        $query->setParameters(['operationClass' => $operationClass]);
192
+        $result = $query->execute();
193
+
194
+        $scopesByOperation[$operationClass] = [];
195
+        while ($row = $result->fetch()) {
196
+            $scope = new ScopeContext($row['type'], $row['value']);
197
+            $scopesByOperation[$operationClass][$scope->getHash()] = $scope;
198
+        }
199
+
200
+        return $scopesByOperation[$operationClass];
201
+    }
202
+
203
+    public function getAllOperations(ScopeContext $scopeContext): array {
204
+        if (isset($this->operations[$scopeContext->getHash()])) {
205
+            return $this->operations[$scopeContext->getHash()];
206
+        }
207
+
208
+        $query = $this->connection->getQueryBuilder();
209
+
210
+        $query->select('o.*')
211
+            ->selectAlias('s.type', 'scope_type')
212
+            ->selectAlias('s.value', 'scope_actor_id')
213
+            ->from('flow_operations', 'o')
214
+            ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
215
+            ->where($query->expr()->eq('s.type', $query->createParameter('scope')));
216
+
217
+        if ($scopeContext->getScope() === IManager::SCOPE_USER) {
218
+            $query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
219
+        }
220
+
221
+        $query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
222
+        $result = $query->execute();
223
+
224
+        $this->operations[$scopeContext->getHash()] = [];
225
+        while ($row = $result->fetch()) {
226
+            if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
227
+                $this->operations[$scopeContext->getHash()][$row['class']] = [];
228
+            }
229
+            $this->operations[$scopeContext->getHash()][$row['class']][] = $row;
230
+        }
231
+
232
+        return $this->operations[$scopeContext->getHash()];
233
+    }
234
+
235
+    public function getOperations(string $class, ScopeContext $scopeContext): array {
236
+        if (!isset($this->operations[$scopeContext->getHash()])) {
237
+            $this->getAllOperations($scopeContext);
238
+        }
239
+        return $this->operations[$scopeContext->getHash()][$class] ?? [];
240
+    }
241
+
242
+    /**
243
+     * @param int $id
244
+     * @return array
245
+     * @throws \UnexpectedValueException
246
+     */
247
+    protected function getOperation($id) {
248
+        $query = $this->connection->getQueryBuilder();
249
+        $query->select('*')
250
+            ->from('flow_operations')
251
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
252
+        $result = $query->execute();
253
+        $row = $result->fetch();
254
+        $result->closeCursor();
255
+
256
+        if ($row) {
257
+            return $row;
258
+        }
259
+
260
+        throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
261
+    }
262
+
263
+    protected function insertOperation(
264
+        string $class,
265
+        string $name,
266
+        array $checkIds,
267
+        string $operation,
268
+        string $entity,
269
+        array $events
270
+    ): int {
271
+        $query = $this->connection->getQueryBuilder();
272
+        $query->insert('flow_operations')
273
+            ->values([
274
+                'class' => $query->createNamedParameter($class),
275
+                'name' => $query->createNamedParameter($name),
276
+                'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
277
+                'operation' => $query->createNamedParameter($operation),
278
+                'entity' => $query->createNamedParameter($entity),
279
+                'events' => $query->createNamedParameter(json_encode($events))
280
+            ]);
281
+        $query->execute();
282
+
283
+        return $query->getLastInsertId();
284
+    }
285
+
286
+    /**
287
+     * @param string $class
288
+     * @param string $name
289
+     * @param array[] $checks
290
+     * @param string $operation
291
+     * @return array The added operation
292
+     * @throws \UnexpectedValueException
293
+     * @throw Exception
294
+     */
295
+    public function addOperation(
296
+        string $class,
297
+        string $name,
298
+        array $checks,
299
+        string $operation,
300
+        ScopeContext $scope,
301
+        string $entity,
302
+        array $events
303
+    ) {
304
+        $this->validateOperation($class, $name, $checks, $operation, $entity, $events);
305
+
306
+        $this->connection->beginTransaction();
307
+
308
+        try {
309
+            $checkIds = [];
310
+            foreach ($checks as $check) {
311
+                $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
312
+            }
313
+
314
+            $id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
315
+            $this->addScope($id, $scope);
316
+
317
+            $this->connection->commit();
318
+        } catch (Exception $e) {
319
+            $this->connection->rollBack();
320
+            throw $e;
321
+        }
322
+
323
+        return $this->getOperation($id);
324
+    }
325
+
326
+    protected function canModify(int $id, ScopeContext $scopeContext):bool {
327
+        if (isset($this->operationsByScope[$scopeContext->getHash()])) {
328
+            return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
329
+        }
330
+
331
+        $qb = $this->connection->getQueryBuilder();
332
+        $qb = $qb->select('o.id')
333
+            ->from('flow_operations', 'o')
334
+            ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
335
+            ->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
336
+
337
+        if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
338
+            $qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
339
+        }
340
+
341
+        $qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
342
+        $result = $qb->execute();
343
+
344
+        $this->operationsByScope[$scopeContext->getHash()] = [];
345
+        while ($opId = $result->fetchOne()) {
346
+            $this->operationsByScope[$scopeContext->getHash()][] = (int)$opId;
347
+        }
348
+        $result->closeCursor();
349
+
350
+        return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
351
+    }
352
+
353
+    /**
354
+     * @param int $id
355
+     * @param string $name
356
+     * @param array[] $checks
357
+     * @param string $operation
358
+     * @return array The updated operation
359
+     * @throws \UnexpectedValueException
360
+     * @throws \DomainException
361
+     * @throws Exception
362
+     */
363
+    public function updateOperation(
364
+        int $id,
365
+        string $name,
366
+        array $checks,
367
+        string $operation,
368
+        ScopeContext $scopeContext,
369
+        string $entity,
370
+        array $events
371
+    ): array {
372
+        if (!$this->canModify($id, $scopeContext)) {
373
+            throw new \DomainException('Target operation not within scope');
374
+        };
375
+        $row = $this->getOperation($id);
376
+        $this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
377
+
378
+        $checkIds = [];
379
+        try {
380
+            $this->connection->beginTransaction();
381
+            foreach ($checks as $check) {
382
+                $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
383
+            }
384
+
385
+            $query = $this->connection->getQueryBuilder();
386
+            $query->update('flow_operations')
387
+                ->set('name', $query->createNamedParameter($name))
388
+                ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
389
+                ->set('operation', $query->createNamedParameter($operation))
390
+                ->set('entity', $query->createNamedParameter($entity))
391
+                ->set('events', $query->createNamedParameter(json_encode($events)))
392
+                ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
393
+            $query->execute();
394
+            $this->connection->commit();
395
+        } catch (Exception $e) {
396
+            $this->connection->rollBack();
397
+            throw $e;
398
+        }
399
+        unset($this->operations[$scopeContext->getHash()]);
400
+
401
+        return $this->getOperation($id);
402
+    }
403
+
404
+    /**
405
+     * @param int $id
406
+     * @return bool
407
+     * @throws \UnexpectedValueException
408
+     * @throws Exception
409
+     * @throws \DomainException
410
+     */
411
+    public function deleteOperation($id, ScopeContext $scopeContext) {
412
+        if (!$this->canModify($id, $scopeContext)) {
413
+            throw new \DomainException('Target operation not within scope');
414
+        };
415
+        $query = $this->connection->getQueryBuilder();
416
+        try {
417
+            $this->connection->beginTransaction();
418
+            $result = (bool)$query->delete('flow_operations')
419
+                ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
420
+                ->execute();
421
+            if ($result) {
422
+                $qb = $this->connection->getQueryBuilder();
423
+                $result &= (bool)$qb->delete('flow_operations_scope')
424
+                    ->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
425
+                    ->execute();
426
+            }
427
+            $this->connection->commit();
428
+        } catch (Exception $e) {
429
+            $this->connection->rollBack();
430
+            throw $e;
431
+        }
432
+
433
+        if (isset($this->operations[$scopeContext->getHash()])) {
434
+            unset($this->operations[$scopeContext->getHash()]);
435
+        }
436
+
437
+        return $result;
438
+    }
439
+
440
+    protected function validateEvents(string $entity, array $events, IOperation $operation) {
441
+        try {
442
+            /** @var IEntity $instance */
443
+            $instance = $this->container->query($entity);
444
+        } catch (QueryException $e) {
445
+            throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
446
+        }
447
+
448
+        if (!$instance instanceof IEntity) {
449
+            throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
450
+        }
451
+
452
+        if (empty($events)) {
453
+            if (!$operation instanceof IComplexOperation) {
454
+                throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
455
+            }
456
+            return;
457
+        }
458
+
459
+        $availableEvents = [];
460
+        foreach ($instance->getEvents() as $event) {
461
+            /** @var IEntityEvent $event */
462
+            $availableEvents[] = $event->getEventName();
463
+        }
464
+
465
+        $diff = array_diff($events, $availableEvents);
466
+        if (!empty($diff)) {
467
+            throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
468
+        }
469
+    }
470
+
471
+    /**
472
+     * @param string $class
473
+     * @param string $name
474
+     * @param array[] $checks
475
+     * @param string $operation
476
+     * @throws \UnexpectedValueException
477
+     */
478
+    public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
479
+        try {
480
+            /** @var IOperation $instance */
481
+            $instance = $this->container->query($class);
482
+        } catch (QueryException $e) {
483
+            throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
484
+        }
485
+
486
+        if (!($instance instanceof IOperation)) {
487
+            throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
488
+        }
489
+
490
+        $this->validateEvents($entity, $events, $instance);
491
+
492
+        if (count($checks) === 0) {
493
+            throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
494
+        }
495
+
496
+        if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
497
+            throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
498
+        }
499
+
500
+        $instance->validateOperation($name, $checks, $operation);
501
+
502
+        foreach ($checks as $check) {
503
+            if (!is_string($check['class'])) {
504
+                throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
505
+            }
506
+
507
+            try {
508
+                /** @var ICheck $instance */
509
+                $instance = $this->container->query($check['class']);
510
+            } catch (QueryException $e) {
511
+                throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
512
+            }
513
+
514
+            if (!($instance instanceof ICheck)) {
515
+                throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
516
+            }
517
+
518
+            if (!empty($instance->supportedEntities())
519
+                && !in_array($entity, $instance->supportedEntities())
520
+            ) {
521
+                throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
522
+            }
523
+
524
+            if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
525
+                throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
526
+            }
527
+
528
+            $instance->validateCheck($check['operator'], $check['value']);
529
+        }
530
+    }
531
+
532
+    /**
533
+     * @param int[] $checkIds
534
+     * @return array[]
535
+     */
536
+    public function getChecks(array $checkIds) {
537
+        $checkIds = array_map('intval', $checkIds);
538
+
539
+        $checks = [];
540
+        foreach ($checkIds as $i => $checkId) {
541
+            if (isset($this->checks[$checkId])) {
542
+                $checks[$checkId] = $this->checks[$checkId];
543
+                unset($checkIds[$i]);
544
+            }
545
+        }
546
+
547
+        if (empty($checkIds)) {
548
+            return $checks;
549
+        }
550
+
551
+        $query = $this->connection->getQueryBuilder();
552
+        $query->select('*')
553
+            ->from('flow_checks')
554
+            ->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
555
+        $result = $query->execute();
556
+
557
+        while ($row = $result->fetch()) {
558
+            $this->checks[(int) $row['id']] = $row;
559
+            $checks[(int) $row['id']] = $row;
560
+        }
561
+        $result->closeCursor();
562
+
563
+        $checkIds = array_diff($checkIds, array_keys($checks));
564
+
565
+        if (!empty($checkIds)) {
566
+            $missingCheck = array_pop($checkIds);
567
+            throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
568
+        }
569
+
570
+        return $checks;
571
+    }
572
+
573
+    /**
574
+     * @param string $class
575
+     * @param string $operator
576
+     * @param string $value
577
+     * @return int Check unique ID
578
+     */
579
+    protected function addCheck($class, $operator, $value) {
580
+        $hash = md5($class . '::' . $operator . '::' . $value);
581
+
582
+        $query = $this->connection->getQueryBuilder();
583
+        $query->select('id')
584
+            ->from('flow_checks')
585
+            ->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
586
+        $result = $query->execute();
587
+
588
+        if ($row = $result->fetch()) {
589
+            $result->closeCursor();
590
+            return (int) $row['id'];
591
+        }
592
+
593
+        $query = $this->connection->getQueryBuilder();
594
+        $query->insert('flow_checks')
595
+            ->values([
596
+                'class' => $query->createNamedParameter($class),
597
+                'operator' => $query->createNamedParameter($operator),
598
+                'value' => $query->createNamedParameter($value),
599
+                'hash' => $query->createNamedParameter($hash),
600
+            ]);
601
+        $query->execute();
602
+
603
+        return $query->getLastInsertId();
604
+    }
605
+
606
+    protected function addScope(int $operationId, ScopeContext $scope): void {
607
+        $query = $this->connection->getQueryBuilder();
608
+
609
+        $insertQuery = $query->insert('flow_operations_scope');
610
+        $insertQuery->values([
611
+            'operation_id' => $query->createNamedParameter($operationId),
612
+            'type' => $query->createNamedParameter($scope->getScope()),
613
+            'value' => $query->createNamedParameter($scope->getScopeId()),
614
+        ]);
615
+        $insertQuery->execute();
616
+    }
617
+
618
+    public function formatOperation(array $operation): array {
619
+        $checkIds = json_decode($operation['checks'], true);
620
+        $checks = $this->getChecks($checkIds);
621
+
622
+        $operation['checks'] = [];
623
+        foreach ($checks as $check) {
624
+            // Remove internal values
625
+            unset($check['id']);
626
+            unset($check['hash']);
627
+
628
+            $operation['checks'][] = $check;
629
+        }
630
+        $operation['events'] = json_decode($operation['events'], true) ?? [];
631
+
632
+
633
+        return $operation;
634
+    }
635
+
636
+    /**
637
+     * @return IEntity[]
638
+     */
639
+    public function getEntitiesList(): array {
640
+        $this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
641
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
642
+
643
+        return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
644
+    }
645
+
646
+    /**
647
+     * @return IOperation[]
648
+     */
649
+    public function getOperatorList(): array {
650
+        $this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
651
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
652
+
653
+        return array_merge($this->getBuildInOperators(), $this->registeredOperators);
654
+    }
655
+
656
+    /**
657
+     * @return ICheck[]
658
+     */
659
+    public function getCheckList(): array {
660
+        $this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
661
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
662
+
663
+        return array_merge($this->getBuildInChecks(), $this->registeredChecks);
664
+    }
665
+
666
+    public function registerEntity(IEntity $entity): void {
667
+        $this->registeredEntities[get_class($entity)] = $entity;
668
+    }
669
+
670
+    public function registerOperation(IOperation $operator): void {
671
+        $this->registeredOperators[get_class($operator)] = $operator;
672
+    }
673
+
674
+    public function registerCheck(ICheck $check): void {
675
+        $this->registeredChecks[get_class($check)] = $check;
676
+    }
677
+
678
+    /**
679
+     * @return IEntity[]
680
+     */
681
+    protected function getBuildInEntities(): array {
682
+        try {
683
+            return [
684
+                File::class => $this->container->query(File::class),
685
+            ];
686
+        } catch (QueryException $e) {
687
+            $this->logger->logException($e);
688
+            return [];
689
+        }
690
+    }
691
+
692
+    /**
693
+     * @return IOperation[]
694
+     */
695
+    protected function getBuildInOperators(): array {
696
+        try {
697
+            return [
698
+                // None yet
699
+            ];
700
+        } catch (QueryException $e) {
701
+            $this->logger->logException($e);
702
+            return [];
703
+        }
704
+    }
705
+
706
+    /**
707
+     * @return ICheck[]
708
+     */
709
+    protected function getBuildInChecks(): array {
710
+        try {
711
+            return [
712
+                $this->container->query(FileMimeType::class),
713
+                $this->container->query(FileName::class),
714
+                $this->container->query(FileSize::class),
715
+                $this->container->query(FileSystemTags::class),
716
+                $this->container->query(RequestRemoteAddress::class),
717
+                $this->container->query(RequestTime::class),
718
+                $this->container->query(RequestURL::class),
719
+                $this->container->query(RequestUserAgent::class),
720
+                $this->container->query(UserGroupMembership::class),
721
+            ];
722
+        } catch (QueryException $e) {
723
+            $this->logger->logException($e);
724
+            return [];
725
+        }
726
+    }
727
+
728
+    public function isUserScopeEnabled(): bool {
729
+        return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
730
+    }
731 731
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -343,7 +343,7 @@  discard block
 block discarded – undo
343 343
 
344 344
 		$this->operationsByScope[$scopeContext->getHash()] = [];
345 345
 		while ($opId = $result->fetchOne()) {
346
-			$this->operationsByScope[$scopeContext->getHash()][] = (int)$opId;
346
+			$this->operationsByScope[$scopeContext->getHash()][] = (int) $opId;
347 347
 		}
348 348
 		$result->closeCursor();
349 349
 
@@ -415,12 +415,12 @@  discard block
 block discarded – undo
415 415
 		$query = $this->connection->getQueryBuilder();
416 416
 		try {
417 417
 			$this->connection->beginTransaction();
418
-			$result = (bool)$query->delete('flow_operations')
418
+			$result = (bool) $query->delete('flow_operations')
419 419
 				->where($query->expr()->eq('id', $query->createNamedParameter($id)))
420 420
 				->execute();
421 421
 			if ($result) {
422 422
 				$qb = $this->connection->getQueryBuilder();
423
-				$result &= (bool)$qb->delete('flow_operations_scope')
423
+				$result &= (bool) $qb->delete('flow_operations_scope')
424 424
 					->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
425 425
 					->execute();
426 426
 			}
@@ -493,7 +493,7 @@  discard block
 block discarded – undo
493 493
 			throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
494 494
 		}
495 495
 
496
-		if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
496
+		if (strlen((string) $operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
497 497
 			throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
498 498
 		}
499 499
 
@@ -521,7 +521,7 @@  discard block
 block discarded – undo
521 521
 				throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
522 522
 			}
523 523
 
524
-			if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
524
+			if (strlen((string) $check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
525 525
 				throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
526 526
 			}
527 527
 
@@ -577,7 +577,7 @@  discard block
 block discarded – undo
577 577
 	 * @return int Check unique ID
578 578
 	 */
579 579
 	protected function addCheck($class, $operator, $value) {
580
-		$hash = md5($class . '::' . $operator . '::' . $value);
580
+		$hash = md5($class.'::'.$operator.'::'.$value);
581 581
 
582 582
 		$query = $this->connection->getQueryBuilder();
583 583
 		$query->select('id')
Please login to merge, or discard this patch.
apps/files_sharing/lib/External/Manager.php 2 patches
Indentation   +559 added lines, -559 removed lines patch added patch discarded remove patch
@@ -52,602 +52,602 @@
 block discarded – undo
52 52
 use OCP\Share\IShare;
53 53
 
54 54
 class Manager {
55
-	public const STORAGE = '\OCA\Files_Sharing\External\Storage';
56
-
57
-	/** @var string|null */
58
-	private $uid;
59
-
60
-	/** @var IDBConnection */
61
-	private $connection;
62
-
63
-	/** @var \OC\Files\Mount\Manager */
64
-	private $mountManager;
65
-
66
-	/** @var IStorageFactory */
67
-	private $storageLoader;
68
-
69
-	/** @var IClientService */
70
-	private $clientService;
71
-
72
-	/** @var IManager */
73
-	private $notificationManager;
74
-
75
-	/** @var IDiscoveryService */
76
-	private $discoveryService;
77
-
78
-	/** @var ICloudFederationProviderManager */
79
-	private $cloudFederationProviderManager;
80
-
81
-	/** @var ICloudFederationFactory */
82
-	private $cloudFederationFactory;
83
-
84
-	/** @var IGroupManager  */
85
-	private $groupManager;
86
-
87
-	/** @var IUserManager */
88
-	private $userManager;
89
-
90
-	/** @var IEventDispatcher */
91
-	private $eventDispatcher;
92
-
93
-	public function __construct(IDBConnection $connection,
94
-								\OC\Files\Mount\Manager $mountManager,
95
-								IStorageFactory $storageLoader,
96
-								IClientService $clientService,
97
-								IManager $notificationManager,
98
-								IDiscoveryService $discoveryService,
99
-								ICloudFederationProviderManager $cloudFederationProviderManager,
100
-								ICloudFederationFactory $cloudFederationFactory,
101
-								IGroupManager $groupManager,
102
-								IUserManager $userManager,
103
-								?string $uid,
104
-								IEventDispatcher $eventDispatcher) {
105
-		$this->connection = $connection;
106
-		$this->mountManager = $mountManager;
107
-		$this->storageLoader = $storageLoader;
108
-		$this->clientService = $clientService;
109
-		$this->uid = $uid;
110
-		$this->notificationManager = $notificationManager;
111
-		$this->discoveryService = $discoveryService;
112
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
113
-		$this->cloudFederationFactory = $cloudFederationFactory;
114
-		$this->groupManager = $groupManager;
115
-		$this->userManager = $userManager;
116
-		$this->eventDispatcher = $eventDispatcher;
117
-	}
118
-
119
-	/**
120
-	 * add new server-to-server share
121
-	 *
122
-	 * @param string $remote
123
-	 * @param string $token
124
-	 * @param string $password
125
-	 * @param string $name
126
-	 * @param string $owner
127
-	 * @param int $shareType
128
-	 * @param boolean $accepted
129
-	 * @param string $user
130
-	 * @param string $remoteId
131
-	 * @param int $parent
132
-	 * @return Mount|null
133
-	 * @throws \Doctrine\DBAL\Exception
134
-	 */
135
-	public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) {
136
-		$user = $user ? $user : $this->uid;
137
-		$accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
138
-		$name = Filesystem::normalizePath('/' . $name);
139
-
140
-		if ($accepted !== IShare::STATUS_ACCEPTED) {
141
-			// To avoid conflicts with the mount point generation later,
142
-			// we only use a temporary mount point name here. The real
143
-			// mount point name will be generated when accepting the share,
144
-			// using the original share item name.
145
-			$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
146
-			$mountPoint = $tmpMountPointName;
147
-			$hash = md5($tmpMountPointName);
148
-			$data = [
149
-				'remote' => $remote,
150
-				'share_token' => $token,
151
-				'password' => $password,
152
-				'name' => $name,
153
-				'owner' => $owner,
154
-				'user' => $user,
155
-				'mountpoint' => $mountPoint,
156
-				'mountpoint_hash' => $hash,
157
-				'accepted' => $accepted,
158
-				'remote_id' => $remoteId,
159
-				'share_type' => $shareType,
160
-			];
161
-
162
-			$i = 1;
163
-			while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
164
-				// The external share already exists for the user
165
-				$data['mountpoint'] = $tmpMountPointName . '-' . $i;
166
-				$data['mountpoint_hash'] = md5($data['mountpoint']);
167
-				$i++;
168
-			}
169
-			return null;
170
-		}
171
-
172
-		$mountPoint = Files::buildNotExistingFileName('/', $name);
173
-		$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
174
-		$hash = md5($mountPoint);
175
-
176
-		$this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
177
-
178
-		$options = [
179
-			'remote' => $remote,
180
-			'token' => $token,
181
-			'password' => $password,
182
-			'mountpoint' => $mountPoint,
183
-			'owner' => $owner
184
-		];
185
-		return $this->mountShare($options);
186
-	}
187
-
188
-	/**
189
-	 * write remote share to the database
190
-	 *
191
-	 * @param $remote
192
-	 * @param $token
193
-	 * @param $password
194
-	 * @param $name
195
-	 * @param $owner
196
-	 * @param $user
197
-	 * @param $mountPoint
198
-	 * @param $hash
199
-	 * @param $accepted
200
-	 * @param $remoteId
201
-	 * @param $parent
202
-	 * @param $shareType
203
-	 *
204
-	 * @return void
205
-	 * @throws \Doctrine\DBAL\Driver\Exception
206
-	 */
207
-	private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType): void {
208
-		$query = $this->connection->prepare('
55
+    public const STORAGE = '\OCA\Files_Sharing\External\Storage';
56
+
57
+    /** @var string|null */
58
+    private $uid;
59
+
60
+    /** @var IDBConnection */
61
+    private $connection;
62
+
63
+    /** @var \OC\Files\Mount\Manager */
64
+    private $mountManager;
65
+
66
+    /** @var IStorageFactory */
67
+    private $storageLoader;
68
+
69
+    /** @var IClientService */
70
+    private $clientService;
71
+
72
+    /** @var IManager */
73
+    private $notificationManager;
74
+
75
+    /** @var IDiscoveryService */
76
+    private $discoveryService;
77
+
78
+    /** @var ICloudFederationProviderManager */
79
+    private $cloudFederationProviderManager;
80
+
81
+    /** @var ICloudFederationFactory */
82
+    private $cloudFederationFactory;
83
+
84
+    /** @var IGroupManager  */
85
+    private $groupManager;
86
+
87
+    /** @var IUserManager */
88
+    private $userManager;
89
+
90
+    /** @var IEventDispatcher */
91
+    private $eventDispatcher;
92
+
93
+    public function __construct(IDBConnection $connection,
94
+                                \OC\Files\Mount\Manager $mountManager,
95
+                                IStorageFactory $storageLoader,
96
+                                IClientService $clientService,
97
+                                IManager $notificationManager,
98
+                                IDiscoveryService $discoveryService,
99
+                                ICloudFederationProviderManager $cloudFederationProviderManager,
100
+                                ICloudFederationFactory $cloudFederationFactory,
101
+                                IGroupManager $groupManager,
102
+                                IUserManager $userManager,
103
+                                ?string $uid,
104
+                                IEventDispatcher $eventDispatcher) {
105
+        $this->connection = $connection;
106
+        $this->mountManager = $mountManager;
107
+        $this->storageLoader = $storageLoader;
108
+        $this->clientService = $clientService;
109
+        $this->uid = $uid;
110
+        $this->notificationManager = $notificationManager;
111
+        $this->discoveryService = $discoveryService;
112
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
113
+        $this->cloudFederationFactory = $cloudFederationFactory;
114
+        $this->groupManager = $groupManager;
115
+        $this->userManager = $userManager;
116
+        $this->eventDispatcher = $eventDispatcher;
117
+    }
118
+
119
+    /**
120
+     * add new server-to-server share
121
+     *
122
+     * @param string $remote
123
+     * @param string $token
124
+     * @param string $password
125
+     * @param string $name
126
+     * @param string $owner
127
+     * @param int $shareType
128
+     * @param boolean $accepted
129
+     * @param string $user
130
+     * @param string $remoteId
131
+     * @param int $parent
132
+     * @return Mount|null
133
+     * @throws \Doctrine\DBAL\Exception
134
+     */
135
+    public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) {
136
+        $user = $user ? $user : $this->uid;
137
+        $accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
138
+        $name = Filesystem::normalizePath('/' . $name);
139
+
140
+        if ($accepted !== IShare::STATUS_ACCEPTED) {
141
+            // To avoid conflicts with the mount point generation later,
142
+            // we only use a temporary mount point name here. The real
143
+            // mount point name will be generated when accepting the share,
144
+            // using the original share item name.
145
+            $tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
146
+            $mountPoint = $tmpMountPointName;
147
+            $hash = md5($tmpMountPointName);
148
+            $data = [
149
+                'remote' => $remote,
150
+                'share_token' => $token,
151
+                'password' => $password,
152
+                'name' => $name,
153
+                'owner' => $owner,
154
+                'user' => $user,
155
+                'mountpoint' => $mountPoint,
156
+                'mountpoint_hash' => $hash,
157
+                'accepted' => $accepted,
158
+                'remote_id' => $remoteId,
159
+                'share_type' => $shareType,
160
+            ];
161
+
162
+            $i = 1;
163
+            while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
164
+                // The external share already exists for the user
165
+                $data['mountpoint'] = $tmpMountPointName . '-' . $i;
166
+                $data['mountpoint_hash'] = md5($data['mountpoint']);
167
+                $i++;
168
+            }
169
+            return null;
170
+        }
171
+
172
+        $mountPoint = Files::buildNotExistingFileName('/', $name);
173
+        $mountPoint = Filesystem::normalizePath('/' . $mountPoint);
174
+        $hash = md5($mountPoint);
175
+
176
+        $this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
177
+
178
+        $options = [
179
+            'remote' => $remote,
180
+            'token' => $token,
181
+            'password' => $password,
182
+            'mountpoint' => $mountPoint,
183
+            'owner' => $owner
184
+        ];
185
+        return $this->mountShare($options);
186
+    }
187
+
188
+    /**
189
+     * write remote share to the database
190
+     *
191
+     * @param $remote
192
+     * @param $token
193
+     * @param $password
194
+     * @param $name
195
+     * @param $owner
196
+     * @param $user
197
+     * @param $mountPoint
198
+     * @param $hash
199
+     * @param $accepted
200
+     * @param $remoteId
201
+     * @param $parent
202
+     * @param $shareType
203
+     *
204
+     * @return void
205
+     * @throws \Doctrine\DBAL\Driver\Exception
206
+     */
207
+    private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType): void {
208
+        $query = $this->connection->prepare('
209 209
 				INSERT INTO `*PREFIX*share_external`
210 210
 					(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`, `parent`, `share_type`)
211 211
 				VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
212 212
 			');
213
-		$query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]);
214
-	}
215
-
216
-	/**
217
-	 * get share
218
-	 *
219
-	 * @param int $id share id
220
-	 * @return mixed share of false
221
-	 */
222
-	public function getShare($id) {
223
-		$getShare = $this->connection->prepare('
213
+        $query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]);
214
+    }
215
+
216
+    /**
217
+     * get share
218
+     *
219
+     * @param int $id share id
220
+     * @return mixed share of false
221
+     */
222
+    public function getShare($id) {
223
+        $getShare = $this->connection->prepare('
224 224
 			SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash`
225 225
 			FROM  `*PREFIX*share_external`
226 226
 			WHERE `id` = ?');
227
-		$result = $getShare->execute([$id]);
228
-		$share = $result->fetch();
229
-		$result->closeCursor();
230
-		$validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
231
-
232
-		// check if the user is allowed to access it
233
-		if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
234
-			return $share;
235
-		} elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
236
-			$user = $this->userManager->get($this->uid);
237
-			if ($this->groupManager->get($share['user'])->inGroup($user)) {
238
-				return $share;
239
-			}
240
-		}
241
-
242
-		return false;
243
-	}
244
-
245
-	/**
246
-	 * accept server-to-server share
247
-	 *
248
-	 * @param int $id
249
-	 * @return bool True if the share could be accepted, false otherwise
250
-	 */
251
-	public function acceptShare($id) {
252
-		$share = $this->getShare($id);
253
-		$result = false;
254
-
255
-		if ($share) {
256
-			\OC_Util::setupFS($this->uid);
257
-			$shareFolder = Helper::getShareFolder();
258
-			$mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
259
-			$mountPoint = Filesystem::normalizePath($mountPoint);
260
-			$hash = md5($mountPoint);
261
-			$userShareAccepted = false;
262
-
263
-			if ((int)$share['share_type'] === IShare::TYPE_USER) {
264
-				$acceptShare = $this->connection->prepare('
227
+        $result = $getShare->execute([$id]);
228
+        $share = $result->fetch();
229
+        $result->closeCursor();
230
+        $validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
231
+
232
+        // check if the user is allowed to access it
233
+        if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
234
+            return $share;
235
+        } elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
236
+            $user = $this->userManager->get($this->uid);
237
+            if ($this->groupManager->get($share['user'])->inGroup($user)) {
238
+                return $share;
239
+            }
240
+        }
241
+
242
+        return false;
243
+    }
244
+
245
+    /**
246
+     * accept server-to-server share
247
+     *
248
+     * @param int $id
249
+     * @return bool True if the share could be accepted, false otherwise
250
+     */
251
+    public function acceptShare($id) {
252
+        $share = $this->getShare($id);
253
+        $result = false;
254
+
255
+        if ($share) {
256
+            \OC_Util::setupFS($this->uid);
257
+            $shareFolder = Helper::getShareFolder();
258
+            $mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
259
+            $mountPoint = Filesystem::normalizePath($mountPoint);
260
+            $hash = md5($mountPoint);
261
+            $userShareAccepted = false;
262
+
263
+            if ((int)$share['share_type'] === IShare::TYPE_USER) {
264
+                $acceptShare = $this->connection->prepare('
265 265
 				UPDATE `*PREFIX*share_external`
266 266
 				SET `accepted` = ?,
267 267
 					`mountpoint` = ?,
268 268
 					`mountpoint_hash` = ?
269 269
 				WHERE `id` = ? AND `user` = ?');
270
-				$userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]);
271
-			} else {
272
-				try {
273
-					$this->writeShareToDb(
274
-						$share['remote'],
275
-						$share['share_token'],
276
-						$share['password'],
277
-						$share['name'],
278
-						$share['owner'],
279
-						$this->uid,
280
-						$mountPoint, $hash, 1,
281
-						$share['remote_id'],
282
-						$id,
283
-						$share['share_type']);
284
-					$result = true;
285
-				} catch (Exception $e) {
286
-					$result = false;
287
-				}
288
-			}
289
-			if ($userShareAccepted !== false) {
290
-				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
291
-				$event = new FederatedShareAddedEvent($share['remote']);
292
-				$this->eventDispatcher->dispatchTyped($event);
293
-				$result = true;
294
-			}
295
-		}
296
-
297
-		// Make sure the user has no notification for something that does not exist anymore.
298
-		$this->processNotification($id);
299
-
300
-		return $result;
301
-	}
302
-
303
-	/**
304
-	 * decline server-to-server share
305
-	 *
306
-	 * @param int $id
307
-	 * @return bool True if the share could be declined, false otherwise
308
-	 */
309
-	public function declineShare($id) {
310
-		$share = $this->getShare($id);
311
-		$result = false;
312
-
313
-		if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
314
-			$removeShare = $this->connection->prepare('
270
+                $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]);
271
+            } else {
272
+                try {
273
+                    $this->writeShareToDb(
274
+                        $share['remote'],
275
+                        $share['share_token'],
276
+                        $share['password'],
277
+                        $share['name'],
278
+                        $share['owner'],
279
+                        $this->uid,
280
+                        $mountPoint, $hash, 1,
281
+                        $share['remote_id'],
282
+                        $id,
283
+                        $share['share_type']);
284
+                    $result = true;
285
+                } catch (Exception $e) {
286
+                    $result = false;
287
+                }
288
+            }
289
+            if ($userShareAccepted !== false) {
290
+                $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
291
+                $event = new FederatedShareAddedEvent($share['remote']);
292
+                $this->eventDispatcher->dispatchTyped($event);
293
+                $result = true;
294
+            }
295
+        }
296
+
297
+        // Make sure the user has no notification for something that does not exist anymore.
298
+        $this->processNotification($id);
299
+
300
+        return $result;
301
+    }
302
+
303
+    /**
304
+     * decline server-to-server share
305
+     *
306
+     * @param int $id
307
+     * @return bool True if the share could be declined, false otherwise
308
+     */
309
+    public function declineShare($id) {
310
+        $share = $this->getShare($id);
311
+        $result = false;
312
+
313
+        if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
314
+            $removeShare = $this->connection->prepare('
315 315
 				DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
316
-			$removeShare->execute([$id, $this->uid]);
317
-			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
318
-
319
-			$this->processNotification($id);
320
-			$result = true;
321
-		} elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
322
-			try {
323
-				$this->writeShareToDb(
324
-					$share['remote'],
325
-					$share['share_token'],
326
-					$share['password'],
327
-					$share['name'],
328
-					$share['owner'],
329
-					$this->uid,
330
-					$share['mountpoint'],
331
-					$share['mountpoint_hash'],
332
-					0,
333
-					$share['remote_id'],
334
-					$id,
335
-					$share['share_type']);
336
-				$result = true;
337
-			} catch (Exception $e) {
338
-				$result = false;
339
-			}
340
-			$this->processNotification($id);
341
-		}
342
-
343
-		return $result;
344
-	}
345
-
346
-	/**
347
-	 * @param int $remoteShare
348
-	 */
349
-	public function processNotification($remoteShare) {
350
-		$filter = $this->notificationManager->createNotification();
351
-		$filter->setApp('files_sharing')
352
-			->setUser($this->uid)
353
-			->setObject('remote_share', (int) $remoteShare);
354
-		$this->notificationManager->markProcessed($filter);
355
-	}
356
-
357
-	/**
358
-	 * inform remote server whether server-to-server share was accepted/declined
359
-	 *
360
-	 * @param string $remote
361
-	 * @param string $token
362
-	 * @param string $remoteId Share id on the remote host
363
-	 * @param string $feedback
364
-	 * @return boolean
365
-	 */
366
-	private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
367
-		$result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback);
368
-
369
-		if ($result === true) {
370
-			return true;
371
-		}
372
-
373
-		$federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
374
-		$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
375
-
376
-		$url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
377
-		$fields = ['token' => $token];
378
-
379
-		$client = $this->clientService->newClient();
380
-
381
-		try {
382
-			$response = $client->post(
383
-				$url,
384
-				[
385
-					'body' => $fields,
386
-					'connect_timeout' => 10,
387
-				]
388
-			);
389
-		} catch (\Exception $e) {
390
-			return false;
391
-		}
392
-
393
-		$status = json_decode($response->getBody(), true);
394
-
395
-		return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
396
-	}
397
-
398
-	/**
399
-	 * try send accept message to ocm end-point
400
-	 *
401
-	 * @param string $remoteDomain
402
-	 * @param string $token
403
-	 * @param string $remoteId id of the share
404
-	 * @param string $feedback
405
-	 * @return bool
406
-	 */
407
-	protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) {
408
-		switch ($feedback) {
409
-			case 'accept':
410
-				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
411
-				$notification->setMessage(
412
-					'SHARE_ACCEPTED',
413
-					'file',
414
-					$remoteId,
415
-					[
416
-						'sharedSecret' => $token,
417
-						'message' => 'Recipient accept the share'
418
-					]
419
-
420
-				);
421
-				return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
422
-			case 'decline':
423
-				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
424
-				$notification->setMessage(
425
-					'SHARE_DECLINED',
426
-					'file',
427
-					$remoteId,
428
-					[
429
-						'sharedSecret' => $token,
430
-						'message' => 'Recipient declined the share'
431
-					]
432
-
433
-				);
434
-				return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
435
-		}
436
-
437
-		return false;
438
-	}
439
-
440
-
441
-	/**
442
-	 * remove '/user/files' from the path and trailing slashes
443
-	 *
444
-	 * @param string $path
445
-	 * @return string
446
-	 */
447
-	protected function stripPath($path) {
448
-		$prefix = '/' . $this->uid . '/files';
449
-		return rtrim(substr($path, strlen($prefix)), '/');
450
-	}
451
-
452
-	public function getMount($data) {
453
-		$data['manager'] = $this;
454
-		$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
455
-		$data['mountpoint'] = $mountPoint;
456
-		$data['certificateManager'] = \OC::$server->getCertificateManager();
457
-		return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
458
-	}
459
-
460
-	/**
461
-	 * @param array $data
462
-	 * @return Mount
463
-	 */
464
-	protected function mountShare($data) {
465
-		$mount = $this->getMount($data);
466
-		$this->mountManager->addMount($mount);
467
-		return $mount;
468
-	}
469
-
470
-	/**
471
-	 * @return \OC\Files\Mount\Manager
472
-	 */
473
-	public function getMountManager() {
474
-		return $this->mountManager;
475
-	}
476
-
477
-	/**
478
-	 * @param string $source
479
-	 * @param string $target
480
-	 * @return bool
481
-	 */
482
-	public function setMountPoint($source, $target) {
483
-		$source = $this->stripPath($source);
484
-		$target = $this->stripPath($target);
485
-		$sourceHash = md5($source);
486
-		$targetHash = md5($target);
487
-
488
-		$query = $this->connection->prepare('
316
+            $removeShare->execute([$id, $this->uid]);
317
+            $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
318
+
319
+            $this->processNotification($id);
320
+            $result = true;
321
+        } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
322
+            try {
323
+                $this->writeShareToDb(
324
+                    $share['remote'],
325
+                    $share['share_token'],
326
+                    $share['password'],
327
+                    $share['name'],
328
+                    $share['owner'],
329
+                    $this->uid,
330
+                    $share['mountpoint'],
331
+                    $share['mountpoint_hash'],
332
+                    0,
333
+                    $share['remote_id'],
334
+                    $id,
335
+                    $share['share_type']);
336
+                $result = true;
337
+            } catch (Exception $e) {
338
+                $result = false;
339
+            }
340
+            $this->processNotification($id);
341
+        }
342
+
343
+        return $result;
344
+    }
345
+
346
+    /**
347
+     * @param int $remoteShare
348
+     */
349
+    public function processNotification($remoteShare) {
350
+        $filter = $this->notificationManager->createNotification();
351
+        $filter->setApp('files_sharing')
352
+            ->setUser($this->uid)
353
+            ->setObject('remote_share', (int) $remoteShare);
354
+        $this->notificationManager->markProcessed($filter);
355
+    }
356
+
357
+    /**
358
+     * inform remote server whether server-to-server share was accepted/declined
359
+     *
360
+     * @param string $remote
361
+     * @param string $token
362
+     * @param string $remoteId Share id on the remote host
363
+     * @param string $feedback
364
+     * @return boolean
365
+     */
366
+    private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
367
+        $result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback);
368
+
369
+        if ($result === true) {
370
+            return true;
371
+        }
372
+
373
+        $federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
374
+        $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
375
+
376
+        $url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
377
+        $fields = ['token' => $token];
378
+
379
+        $client = $this->clientService->newClient();
380
+
381
+        try {
382
+            $response = $client->post(
383
+                $url,
384
+                [
385
+                    'body' => $fields,
386
+                    'connect_timeout' => 10,
387
+                ]
388
+            );
389
+        } catch (\Exception $e) {
390
+            return false;
391
+        }
392
+
393
+        $status = json_decode($response->getBody(), true);
394
+
395
+        return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
396
+    }
397
+
398
+    /**
399
+     * try send accept message to ocm end-point
400
+     *
401
+     * @param string $remoteDomain
402
+     * @param string $token
403
+     * @param string $remoteId id of the share
404
+     * @param string $feedback
405
+     * @return bool
406
+     */
407
+    protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) {
408
+        switch ($feedback) {
409
+            case 'accept':
410
+                $notification = $this->cloudFederationFactory->getCloudFederationNotification();
411
+                $notification->setMessage(
412
+                    'SHARE_ACCEPTED',
413
+                    'file',
414
+                    $remoteId,
415
+                    [
416
+                        'sharedSecret' => $token,
417
+                        'message' => 'Recipient accept the share'
418
+                    ]
419
+
420
+                );
421
+                return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
422
+            case 'decline':
423
+                $notification = $this->cloudFederationFactory->getCloudFederationNotification();
424
+                $notification->setMessage(
425
+                    'SHARE_DECLINED',
426
+                    'file',
427
+                    $remoteId,
428
+                    [
429
+                        'sharedSecret' => $token,
430
+                        'message' => 'Recipient declined the share'
431
+                    ]
432
+
433
+                );
434
+                return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
435
+        }
436
+
437
+        return false;
438
+    }
439
+
440
+
441
+    /**
442
+     * remove '/user/files' from the path and trailing slashes
443
+     *
444
+     * @param string $path
445
+     * @return string
446
+     */
447
+    protected function stripPath($path) {
448
+        $prefix = '/' . $this->uid . '/files';
449
+        return rtrim(substr($path, strlen($prefix)), '/');
450
+    }
451
+
452
+    public function getMount($data) {
453
+        $data['manager'] = $this;
454
+        $mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
455
+        $data['mountpoint'] = $mountPoint;
456
+        $data['certificateManager'] = \OC::$server->getCertificateManager();
457
+        return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
458
+    }
459
+
460
+    /**
461
+     * @param array $data
462
+     * @return Mount
463
+     */
464
+    protected function mountShare($data) {
465
+        $mount = $this->getMount($data);
466
+        $this->mountManager->addMount($mount);
467
+        return $mount;
468
+    }
469
+
470
+    /**
471
+     * @return \OC\Files\Mount\Manager
472
+     */
473
+    public function getMountManager() {
474
+        return $this->mountManager;
475
+    }
476
+
477
+    /**
478
+     * @param string $source
479
+     * @param string $target
480
+     * @return bool
481
+     */
482
+    public function setMountPoint($source, $target) {
483
+        $source = $this->stripPath($source);
484
+        $target = $this->stripPath($target);
485
+        $sourceHash = md5($source);
486
+        $targetHash = md5($target);
487
+
488
+        $query = $this->connection->prepare('
489 489
 			UPDATE `*PREFIX*share_external`
490 490
 			SET `mountpoint` = ?, `mountpoint_hash` = ?
491 491
 			WHERE `mountpoint_hash` = ?
492 492
 			AND `user` = ?
493 493
 		');
494
-		$result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
494
+        $result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
495 495
 
496
-		return $result;
497
-	}
496
+        return $result;
497
+    }
498 498
 
499
-	public function removeShare($mountPoint): bool {
500
-		$mountPointObj = $this->mountManager->find($mountPoint);
501
-		$id = $mountPointObj->getStorage()->getCache()->getId('');
499
+    public function removeShare($mountPoint): bool {
500
+        $mountPointObj = $this->mountManager->find($mountPoint);
501
+        $id = $mountPointObj->getStorage()->getCache()->getId('');
502 502
 
503
-		$mountPoint = $this->stripPath($mountPoint);
504
-		$hash = md5($mountPoint);
503
+        $mountPoint = $this->stripPath($mountPoint);
504
+        $hash = md5($mountPoint);
505 505
 
506
-		try {
507
-			$getShare = $this->connection->prepare('
506
+        try {
507
+            $getShare = $this->connection->prepare('
508 508
 				SELECT `remote`, `share_token`, `remote_id`, `share_type`, `id`
509 509
 				FROM  `*PREFIX*share_external`
510 510
 				WHERE `mountpoint_hash` = ? AND `user` = ?');
511
-			$result = $getShare->execute([$hash, $this->uid]);
512
-			$share = $result->fetch();
513
-			$result->closeCursor();
514
-			if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
515
-				try {
516
-					$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
517
-				} catch (\Throwable $e) {
518
-					// if we fail to notify the remote (probably cause the remote is down)
519
-					// we still want the share to be gone to prevent undeletable remotes
520
-				}
521
-
522
-				$query = $this->connection->prepare('
511
+            $result = $getShare->execute([$hash, $this->uid]);
512
+            $share = $result->fetch();
513
+            $result->closeCursor();
514
+            if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
515
+                try {
516
+                    $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
517
+                } catch (\Throwable $e) {
518
+                    // if we fail to notify the remote (probably cause the remote is down)
519
+                    // we still want the share to be gone to prevent undeletable remotes
520
+                }
521
+
522
+                $query = $this->connection->prepare('
523 523
 				DELETE FROM `*PREFIX*share_external`
524 524
 				WHERE `id` = ?
525 525
 				');
526
-				$deleteResult = $query->execute([(int)$share['id']]);
527
-				$deleteResult->closeCursor();
528
-			} elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
529
-				$query = $this->connection->prepare('
526
+                $deleteResult = $query->execute([(int)$share['id']]);
527
+                $deleteResult->closeCursor();
528
+            } elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
529
+                $query = $this->connection->prepare('
530 530
 					UPDATE `*PREFIX*share_external`
531 531
 					SET `accepted` = ?
532 532
 					WHERE `id` = ?');
533
-				$updateResult = $query->execute([0, (int)$share['id']]);
534
-				$updateResult->closeCursor();
535
-			}
536
-
537
-			$this->removeReShares($id);
538
-		} catch (\Doctrine\DBAL\Exception $ex) {
539
-			return false;
540
-		}
541
-
542
-		return true;
543
-	}
544
-
545
-	/**
546
-	 * remove re-shares from share table and mapping in the federated_reshares table
547
-	 *
548
-	 * @param $mountPointId
549
-	 */
550
-	protected function removeReShares($mountPointId) {
551
-		$selectQuery = $this->connection->getQueryBuilder();
552
-		$query = $this->connection->getQueryBuilder();
553
-		$selectQuery->select('id')->from('share')
554
-			->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
555
-		$select = $selectQuery->getSQL();
556
-
557
-
558
-		$query->delete('federated_reshares')
559
-			->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
560
-		$query->execute();
561
-
562
-		$deleteReShares = $this->connection->getQueryBuilder();
563
-		$deleteReShares->delete('share')
564
-			->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
565
-		$deleteReShares->execute();
566
-	}
567
-
568
-	/**
569
-	 * remove all shares for user $uid if the user was deleted
570
-	 *
571
-	 * @param string $uid
572
-	 */
573
-	public function removeUserShares($uid): bool {
574
-		try {
575
-			$getShare = $this->connection->prepare('
533
+                $updateResult = $query->execute([0, (int)$share['id']]);
534
+                $updateResult->closeCursor();
535
+            }
536
+
537
+            $this->removeReShares($id);
538
+        } catch (\Doctrine\DBAL\Exception $ex) {
539
+            return false;
540
+        }
541
+
542
+        return true;
543
+    }
544
+
545
+    /**
546
+     * remove re-shares from share table and mapping in the federated_reshares table
547
+     *
548
+     * @param $mountPointId
549
+     */
550
+    protected function removeReShares($mountPointId) {
551
+        $selectQuery = $this->connection->getQueryBuilder();
552
+        $query = $this->connection->getQueryBuilder();
553
+        $selectQuery->select('id')->from('share')
554
+            ->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
555
+        $select = $selectQuery->getSQL();
556
+
557
+
558
+        $query->delete('federated_reshares')
559
+            ->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
560
+        $query->execute();
561
+
562
+        $deleteReShares = $this->connection->getQueryBuilder();
563
+        $deleteReShares->delete('share')
564
+            ->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
565
+        $deleteReShares->execute();
566
+    }
567
+
568
+    /**
569
+     * remove all shares for user $uid if the user was deleted
570
+     *
571
+     * @param string $uid
572
+     */
573
+    public function removeUserShares($uid): bool {
574
+        try {
575
+            $getShare = $this->connection->prepare('
576 576
 				SELECT `remote`, `share_token`, `remote_id`
577 577
 				FROM  `*PREFIX*share_external`
578 578
 				WHERE `user` = ?');
579
-			$result = $getShare->execute([$uid]);
580
-			$shares = $result->fetchAll();
581
-			$result->closeCursor();
582
-			foreach ($shares as $share) {
583
-				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
584
-			}
585
-
586
-			$query = $this->connection->prepare('
579
+            $result = $getShare->execute([$uid]);
580
+            $shares = $result->fetchAll();
581
+            $result->closeCursor();
582
+            foreach ($shares as $share) {
583
+                $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
584
+            }
585
+
586
+            $query = $this->connection->prepare('
587 587
 				DELETE FROM `*PREFIX*share_external`
588 588
 				WHERE `user` = ?
589 589
 			');
590
-			$deleteResult = $query->execute([$uid]);
591
-			$deleteResult->closeCursor();
592
-		} catch (\Doctrine\DBAL\Exception $ex) {
593
-			return false;
594
-		}
595
-
596
-		return true;
597
-	}
598
-
599
-	/**
600
-	 * return a list of shares which are not yet accepted by the user
601
-	 *
602
-	 * @return array list of open server-to-server shares
603
-	 */
604
-	public function getOpenShares() {
605
-		return $this->getShares(false);
606
-	}
607
-
608
-	/**
609
-	 * return a list of shares which are accepted by the user
610
-	 *
611
-	 * @return array list of accepted server-to-server shares
612
-	 */
613
-	public function getAcceptedShares() {
614
-		return $this->getShares(true);
615
-	}
616
-
617
-	/**
618
-	 * return a list of shares for the user
619
-	 *
620
-	 * @param bool|null $accepted True for accepted only,
621
-	 *                            false for not accepted,
622
-	 *                            null for all shares of the user
623
-	 * @return array list of open server-to-server shares
624
-	 */
625
-	private function getShares($accepted) {
626
-		$user = $this->userManager->get($this->uid);
627
-		$groups = $this->groupManager->getUserGroups($user);
628
-		$userGroups = [];
629
-		foreach ($groups as $group) {
630
-			$userGroups[] = $group->getGID();
631
-		}
632
-
633
-		$query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
590
+            $deleteResult = $query->execute([$uid]);
591
+            $deleteResult->closeCursor();
592
+        } catch (\Doctrine\DBAL\Exception $ex) {
593
+            return false;
594
+        }
595
+
596
+        return true;
597
+    }
598
+
599
+    /**
600
+     * return a list of shares which are not yet accepted by the user
601
+     *
602
+     * @return array list of open server-to-server shares
603
+     */
604
+    public function getOpenShares() {
605
+        return $this->getShares(false);
606
+    }
607
+
608
+    /**
609
+     * return a list of shares which are accepted by the user
610
+     *
611
+     * @return array list of accepted server-to-server shares
612
+     */
613
+    public function getAcceptedShares() {
614
+        return $this->getShares(true);
615
+    }
616
+
617
+    /**
618
+     * return a list of shares for the user
619
+     *
620
+     * @param bool|null $accepted True for accepted only,
621
+     *                            false for not accepted,
622
+     *                            null for all shares of the user
623
+     * @return array list of open server-to-server shares
624
+     */
625
+    private function getShares($accepted) {
626
+        $user = $this->userManager->get($this->uid);
627
+        $groups = $this->groupManager->getUserGroups($user);
628
+        $userGroups = [];
629
+        foreach ($groups as $group) {
630
+            $userGroups[] = $group->getGID();
631
+        }
632
+
633
+        $query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
634 634
 		          FROM `*PREFIX*share_external`
635 635
 				  WHERE (`user` = ? OR `user` IN (?))';
636
-		$parameters = [$this->uid, implode(',',$userGroups)];
637
-		if (!is_null($accepted)) {
638
-			$query .= ' AND `accepted` = ?';
639
-			$parameters[] = (int) $accepted;
640
-		}
641
-		$query .= ' ORDER BY `id` ASC';
642
-
643
-		$sharesQuery = $this->connection->prepare($query);
644
-		try {
645
-			$result = $sharesQuery->execute($parameters);
646
-			$shares = $result->fetchAll();
647
-			$result->closeCursor();
648
-			return $shares;
649
-		} catch (\Doctrine\DBAL\Exception $e) {
650
-			return [];
651
-		}
652
-	}
636
+        $parameters = [$this->uid, implode(',',$userGroups)];
637
+        if (!is_null($accepted)) {
638
+            $query .= ' AND `accepted` = ?';
639
+            $parameters[] = (int) $accepted;
640
+        }
641
+        $query .= ' ORDER BY `id` ASC';
642
+
643
+        $sharesQuery = $this->connection->prepare($query);
644
+        try {
645
+            $result = $sharesQuery->execute($parameters);
646
+            $shares = $result->fetchAll();
647
+            $result->closeCursor();
648
+            return $shares;
649
+        } catch (\Doctrine\DBAL\Exception $e) {
650
+            return [];
651
+        }
652
+    }
653 653
 }
Please login to merge, or discard this patch.
Spacing   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -135,14 +135,14 @@  discard block
 block discarded – undo
135 135
 	public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) {
136 136
 		$user = $user ? $user : $this->uid;
137 137
 		$accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
138
-		$name = Filesystem::normalizePath('/' . $name);
138
+		$name = Filesystem::normalizePath('/'.$name);
139 139
 
140 140
 		if ($accepted !== IShare::STATUS_ACCEPTED) {
141 141
 			// To avoid conflicts with the mount point generation later,
142 142
 			// we only use a temporary mount point name here. The real
143 143
 			// mount point name will be generated when accepting the share,
144 144
 			// using the original share item name.
145
-			$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
145
+			$tmpMountPointName = '{{TemporaryMountPointName#'.$name.'}}';
146 146
 			$mountPoint = $tmpMountPointName;
147 147
 			$hash = md5($tmpMountPointName);
148 148
 			$data = [
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 			$i = 1;
163 163
 			while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
164 164
 				// The external share already exists for the user
165
-				$data['mountpoint'] = $tmpMountPointName . '-' . $i;
165
+				$data['mountpoint'] = $tmpMountPointName.'-'.$i;
166 166
 				$data['mountpoint_hash'] = md5($data['mountpoint']);
167 167
 				$i++;
168 168
 			}
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 		}
171 171
 
172 172
 		$mountPoint = Files::buildNotExistingFileName('/', $name);
173
-		$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
173
+		$mountPoint = Filesystem::normalizePath('/'.$mountPoint);
174 174
 		$hash = md5($mountPoint);
175 175
 
176 176
 		$this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
@@ -230,9 +230,9 @@  discard block
 block discarded – undo
230 230
 		$validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
231 231
 
232 232
 		// check if the user is allowed to access it
233
-		if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
233
+		if ($validShare && (int) $share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
234 234
 			return $share;
235
-		} elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
235
+		} elseif ($validShare && (int) $share['share_type'] === IShare::TYPE_GROUP) {
236 236
 			$user = $this->userManager->get($this->uid);
237 237
 			if ($this->groupManager->get($share['user'])->inGroup($user)) {
238 238
 				return $share;
@@ -260,7 +260,7 @@  discard block
 block discarded – undo
260 260
 			$hash = md5($mountPoint);
261 261
 			$userShareAccepted = false;
262 262
 
263
-			if ((int)$share['share_type'] === IShare::TYPE_USER) {
263
+			if ((int) $share['share_type'] === IShare::TYPE_USER) {
264 264
 				$acceptShare = $this->connection->prepare('
265 265
 				UPDATE `*PREFIX*share_external`
266 266
 				SET `accepted` = ?,
@@ -310,7 +310,7 @@  discard block
 block discarded – undo
310 310
 		$share = $this->getShare($id);
311 311
 		$result = false;
312 312
 
313
-		if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
313
+		if ($share && (int) $share['share_type'] === IShare::TYPE_USER) {
314 314
 			$removeShare = $this->connection->prepare('
315 315
 				DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
316 316
 			$removeShare->execute([$id, $this->uid]);
@@ -318,7 +318,7 @@  discard block
 block discarded – undo
318 318
 
319 319
 			$this->processNotification($id);
320 320
 			$result = true;
321
-		} elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
321
+		} elseif ($share && (int) $share['share_type'] === IShare::TYPE_GROUP) {
322 322
 			try {
323 323
 				$this->writeShareToDb(
324 324
 					$share['remote'],
@@ -373,7 +373,7 @@  discard block
 block discarded – undo
373 373
 		$federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
374 374
 		$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
375 375
 
376
-		$url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
376
+		$url = rtrim($remote, '/').$endpoint.'/'.$remoteId.'/'.$feedback.'?format='.Share::RESPONSE_FORMAT;
377 377
 		$fields = ['token' => $token];
378 378
 
379 379
 		$client = $this->clientService->newClient();
@@ -445,13 +445,13 @@  discard block
 block discarded – undo
445 445
 	 * @return string
446 446
 	 */
447 447
 	protected function stripPath($path) {
448
-		$prefix = '/' . $this->uid . '/files';
448
+		$prefix = '/'.$this->uid.'/files';
449 449
 		return rtrim(substr($path, strlen($prefix)), '/');
450 450
 	}
451 451
 
452 452
 	public function getMount($data) {
453 453
 		$data['manager'] = $this;
454
-		$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
454
+		$mountPoint = '/'.$this->uid.'/files'.$data['mountpoint'];
455 455
 		$data['mountpoint'] = $mountPoint;
456 456
 		$data['certificateManager'] = \OC::$server->getCertificateManager();
457 457
 		return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
@@ -491,7 +491,7 @@  discard block
 block discarded – undo
491 491
 			WHERE `mountpoint_hash` = ?
492 492
 			AND `user` = ?
493 493
 		');
494
-		$result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
494
+		$result = (bool) $query->execute([$target, $targetHash, $sourceHash, $this->uid]);
495 495
 
496 496
 		return $result;
497 497
 	}
@@ -511,7 +511,7 @@  discard block
 block discarded – undo
511 511
 			$result = $getShare->execute([$hash, $this->uid]);
512 512
 			$share = $result->fetch();
513 513
 			$result->closeCursor();
514
-			if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
514
+			if ($share !== false && (int) $share['share_type'] === IShare::TYPE_USER) {
515 515
 				try {
516 516
 					$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
517 517
 				} catch (\Throwable $e) {
@@ -523,14 +523,14 @@  discard block
 block discarded – undo
523 523
 				DELETE FROM `*PREFIX*share_external`
524 524
 				WHERE `id` = ?
525 525
 				');
526
-				$deleteResult = $query->execute([(int)$share['id']]);
526
+				$deleteResult = $query->execute([(int) $share['id']]);
527 527
 				$deleteResult->closeCursor();
528
-			} elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
528
+			} elseif ($share !== false && (int) $share['share_type'] === IShare::TYPE_GROUP) {
529 529
 				$query = $this->connection->prepare('
530 530
 					UPDATE `*PREFIX*share_external`
531 531
 					SET `accepted` = ?
532 532
 					WHERE `id` = ?');
533
-				$updateResult = $query->execute([0, (int)$share['id']]);
533
+				$updateResult = $query->execute([0, (int) $share['id']]);
534 534
 				$updateResult->closeCursor();
535 535
 			}
536 536
 
@@ -556,7 +556,7 @@  discard block
 block discarded – undo
556 556
 
557 557
 
558 558
 		$query->delete('federated_reshares')
559
-			->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
559
+			->where($query->expr()->in('share_id', $query->createFunction('('.$select.')')));
560 560
 		$query->execute();
561 561
 
562 562
 		$deleteReShares = $this->connection->getQueryBuilder();
@@ -633,7 +633,7 @@  discard block
 block discarded – undo
633 633
 		$query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
634 634
 		          FROM `*PREFIX*share_external`
635 635
 				  WHERE (`user` = ? OR `user` IN (?))';
636
-		$parameters = [$this->uid, implode(',',$userGroups)];
636
+		$parameters = [$this->uid, implode(',', $userGroups)];
637 637
 		if (!is_null($accepted)) {
638 638
 			$query .= ' AND `accepted` = ?';
639 639
 			$parameters[] = (int) $accepted;
Please login to merge, or discard this patch.
apps/files_sharing/lib/Command/CleanupRemoteStorages.php 1 patch
Indentation   +141 added lines, -141 removed lines patch added patch discarded remove patch
@@ -37,145 +37,145 @@
 block discarded – undo
37 37
  */
38 38
 class CleanupRemoteStorages extends Command {
39 39
 
40
-	/**
41
-	 * @var IDBConnection
42
-	 */
43
-	protected $connection;
44
-
45
-	public function __construct(IDBConnection $connection) {
46
-		$this->connection = $connection;
47
-		parent::__construct();
48
-	}
49
-
50
-	protected function configure() {
51
-		$this
52
-			->setName('sharing:cleanup-remote-storages')
53
-			->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table')
54
-			->addOption(
55
-				'dry-run',
56
-				null,
57
-				InputOption::VALUE_NONE,
58
-				'only show which storages would be deleted'
59
-			);
60
-	}
61
-
62
-	public function execute(InputInterface $input, OutputInterface $output): int {
63
-		$remoteStorages = $this->getRemoteStorages();
64
-
65
-		$output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked');
66
-
67
-		$remoteShareIds = $this->getRemoteShareIds();
68
-
69
-		$output->writeln(count($remoteShareIds) . ' remote share(s) exist');
70
-
71
-		foreach ($remoteShareIds as $id => $remoteShareId) {
72
-			if (isset($remoteStorages[$remoteShareId])) {
73
-				if ($input->getOption('dry-run') || $output->isVerbose()) {
74
-					$output->writeln("<info>$remoteShareId belongs to remote share $id</info>");
75
-				}
76
-
77
-				unset($remoteStorages[$remoteShareId]);
78
-			} else {
79
-				$output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>");
80
-			}
81
-		}
82
-
83
-		if (empty($remoteStorages)) {
84
-			$output->writeln('<info>no storages deleted</info>');
85
-		} else {
86
-			$dryRun = $input->getOption('dry-run');
87
-			foreach ($remoteStorages as $id => $numericId) {
88
-				if ($dryRun) {
89
-					$output->writeln("<error>$id [$numericId] can be deleted</error>");
90
-					$this->countFiles($numericId, $output);
91
-				} else {
92
-					$this->deleteStorage($id, $numericId, $output);
93
-				}
94
-			}
95
-		}
96
-		return 0;
97
-	}
98
-
99
-	public function countFiles($numericId, OutputInterface $output) {
100
-		$queryBuilder = $this->connection->getQueryBuilder();
101
-		$queryBuilder->select($queryBuilder->func()->count('fileid'))
102
-			->from('filecache')
103
-			->where($queryBuilder->expr()->eq(
104
-				'storage',
105
-				$queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
106
-				IQueryBuilder::PARAM_STR)
107
-			);
108
-		$result = $queryBuilder->execute();
109
-		$count = $result->fetchOne();
110
-		$output->writeln("$count files can be deleted for storage $numericId");
111
-	}
112
-
113
-	public function deleteStorage($id, $numericId, OutputInterface $output) {
114
-		$queryBuilder = $this->connection->getQueryBuilder();
115
-		$queryBuilder->delete('storages')
116
-			->where($queryBuilder->expr()->eq(
117
-				'id',
118
-				$queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR),
119
-				IQueryBuilder::PARAM_STR)
120
-			);
121
-		$output->write("deleting $id [$numericId] ... ");
122
-		$count = $queryBuilder->execute();
123
-		$output->writeln("deleted $count storage");
124
-		$this->deleteFiles($numericId, $output);
125
-	}
126
-
127
-	public function deleteFiles($numericId, OutputInterface $output) {
128
-		$queryBuilder = $this->connection->getQueryBuilder();
129
-		$queryBuilder->delete('filecache')
130
-			->where($queryBuilder->expr()->eq(
131
-				'storage',
132
-				$queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
133
-				IQueryBuilder::PARAM_STR)
134
-			);
135
-		$output->write("deleting files for storage $numericId ... ");
136
-		$count = $queryBuilder->execute();
137
-		$output->writeln("deleted $count files");
138
-	}
139
-
140
-	public function getRemoteStorages() {
141
-		$queryBuilder = $this->connection->getQueryBuilder();
142
-		$queryBuilder->select(['id', 'numeric_id'])
143
-			->from('storages')
144
-			->where($queryBuilder->expr()->like(
145
-				'id',
146
-				// match all 'shared::' + 32 characters storages
147
-				$queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)),
148
-				IQueryBuilder::PARAM_STR)
149
-			)
150
-			->andWhere($queryBuilder->expr()->notLike(
151
-				'id',
152
-				// but not the ones starting with a '/', they are for normal shares
153
-				$queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'),
154
-				IQueryBuilder::PARAM_STR)
155
-			)->orderBy('numeric_id');
156
-		$query = $queryBuilder->execute();
157
-
158
-		$remoteStorages = [];
159
-
160
-		while ($row = $query->fetch()) {
161
-			$remoteStorages[$row['id']] = $row['numeric_id'];
162
-		}
163
-
164
-		return $remoteStorages;
165
-	}
166
-
167
-	public function getRemoteShareIds() {
168
-		$queryBuilder = $this->connection->getQueryBuilder();
169
-		$queryBuilder->select(['id', 'share_token', 'remote'])
170
-			->from('share_external');
171
-		$query = $queryBuilder->execute();
172
-
173
-		$remoteShareIds = [];
174
-
175
-		while ($row = $query->fetch()) {
176
-			$remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $row['remote']);
177
-		}
178
-
179
-		return $remoteShareIds;
180
-	}
40
+    /**
41
+     * @var IDBConnection
42
+     */
43
+    protected $connection;
44
+
45
+    public function __construct(IDBConnection $connection) {
46
+        $this->connection = $connection;
47
+        parent::__construct();
48
+    }
49
+
50
+    protected function configure() {
51
+        $this
52
+            ->setName('sharing:cleanup-remote-storages')
53
+            ->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table')
54
+            ->addOption(
55
+                'dry-run',
56
+                null,
57
+                InputOption::VALUE_NONE,
58
+                'only show which storages would be deleted'
59
+            );
60
+    }
61
+
62
+    public function execute(InputInterface $input, OutputInterface $output): int {
63
+        $remoteStorages = $this->getRemoteStorages();
64
+
65
+        $output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked');
66
+
67
+        $remoteShareIds = $this->getRemoteShareIds();
68
+
69
+        $output->writeln(count($remoteShareIds) . ' remote share(s) exist');
70
+
71
+        foreach ($remoteShareIds as $id => $remoteShareId) {
72
+            if (isset($remoteStorages[$remoteShareId])) {
73
+                if ($input->getOption('dry-run') || $output->isVerbose()) {
74
+                    $output->writeln("<info>$remoteShareId belongs to remote share $id</info>");
75
+                }
76
+
77
+                unset($remoteStorages[$remoteShareId]);
78
+            } else {
79
+                $output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>");
80
+            }
81
+        }
82
+
83
+        if (empty($remoteStorages)) {
84
+            $output->writeln('<info>no storages deleted</info>');
85
+        } else {
86
+            $dryRun = $input->getOption('dry-run');
87
+            foreach ($remoteStorages as $id => $numericId) {
88
+                if ($dryRun) {
89
+                    $output->writeln("<error>$id [$numericId] can be deleted</error>");
90
+                    $this->countFiles($numericId, $output);
91
+                } else {
92
+                    $this->deleteStorage($id, $numericId, $output);
93
+                }
94
+            }
95
+        }
96
+        return 0;
97
+    }
98
+
99
+    public function countFiles($numericId, OutputInterface $output) {
100
+        $queryBuilder = $this->connection->getQueryBuilder();
101
+        $queryBuilder->select($queryBuilder->func()->count('fileid'))
102
+            ->from('filecache')
103
+            ->where($queryBuilder->expr()->eq(
104
+                'storage',
105
+                $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
106
+                IQueryBuilder::PARAM_STR)
107
+            );
108
+        $result = $queryBuilder->execute();
109
+        $count = $result->fetchOne();
110
+        $output->writeln("$count files can be deleted for storage $numericId");
111
+    }
112
+
113
+    public function deleteStorage($id, $numericId, OutputInterface $output) {
114
+        $queryBuilder = $this->connection->getQueryBuilder();
115
+        $queryBuilder->delete('storages')
116
+            ->where($queryBuilder->expr()->eq(
117
+                'id',
118
+                $queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR),
119
+                IQueryBuilder::PARAM_STR)
120
+            );
121
+        $output->write("deleting $id [$numericId] ... ");
122
+        $count = $queryBuilder->execute();
123
+        $output->writeln("deleted $count storage");
124
+        $this->deleteFiles($numericId, $output);
125
+    }
126
+
127
+    public function deleteFiles($numericId, OutputInterface $output) {
128
+        $queryBuilder = $this->connection->getQueryBuilder();
129
+        $queryBuilder->delete('filecache')
130
+            ->where($queryBuilder->expr()->eq(
131
+                'storage',
132
+                $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
133
+                IQueryBuilder::PARAM_STR)
134
+            );
135
+        $output->write("deleting files for storage $numericId ... ");
136
+        $count = $queryBuilder->execute();
137
+        $output->writeln("deleted $count files");
138
+    }
139
+
140
+    public function getRemoteStorages() {
141
+        $queryBuilder = $this->connection->getQueryBuilder();
142
+        $queryBuilder->select(['id', 'numeric_id'])
143
+            ->from('storages')
144
+            ->where($queryBuilder->expr()->like(
145
+                'id',
146
+                // match all 'shared::' + 32 characters storages
147
+                $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)),
148
+                IQueryBuilder::PARAM_STR)
149
+            )
150
+            ->andWhere($queryBuilder->expr()->notLike(
151
+                'id',
152
+                // but not the ones starting with a '/', they are for normal shares
153
+                $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'),
154
+                IQueryBuilder::PARAM_STR)
155
+            )->orderBy('numeric_id');
156
+        $query = $queryBuilder->execute();
157
+
158
+        $remoteStorages = [];
159
+
160
+        while ($row = $query->fetch()) {
161
+            $remoteStorages[$row['id']] = $row['numeric_id'];
162
+        }
163
+
164
+        return $remoteStorages;
165
+    }
166
+
167
+    public function getRemoteShareIds() {
168
+        $queryBuilder = $this->connection->getQueryBuilder();
169
+        $queryBuilder->select(['id', 'share_token', 'remote'])
170
+            ->from('share_external');
171
+        $query = $queryBuilder->execute();
172
+
173
+        $remoteShareIds = [];
174
+
175
+        while ($row = $query->fetch()) {
176
+            $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $row['remote']);
177
+        }
178
+
179
+        return $remoteShareIds;
180
+    }
181 181
 }
Please login to merge, or discard this patch.
lib/private/Repair.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -80,200 +80,200 @@
 block discarded – undo
80 80
 
81 81
 class Repair implements IOutput {
82 82
 
83
-	/** @var IRepairStep[] */
84
-	private $repairSteps;
83
+    /** @var IRepairStep[] */
84
+    private $repairSteps;
85 85
 
86
-	/** @var EventDispatcherInterface */
87
-	private $dispatcher;
86
+    /** @var EventDispatcherInterface */
87
+    private $dispatcher;
88 88
 
89
-	/** @var string */
90
-	private $currentStep;
89
+    /** @var string */
90
+    private $currentStep;
91 91
 
92
-	/**
93
-	 * Creates a new repair step runner
94
-	 *
95
-	 * @param IRepairStep[] $repairSteps array of RepairStep instances
96
-	 * @param EventDispatcherInterface $dispatcher
97
-	 */
98
-	public function __construct(array $repairSteps, EventDispatcherInterface $dispatcher) {
99
-		$this->repairSteps = $repairSteps;
100
-		$this->dispatcher = $dispatcher;
101
-	}
92
+    /**
93
+     * Creates a new repair step runner
94
+     *
95
+     * @param IRepairStep[] $repairSteps array of RepairStep instances
96
+     * @param EventDispatcherInterface $dispatcher
97
+     */
98
+    public function __construct(array $repairSteps, EventDispatcherInterface $dispatcher) {
99
+        $this->repairSteps = $repairSteps;
100
+        $this->dispatcher = $dispatcher;
101
+    }
102 102
 
103
-	/**
104
-	 * Run a series of repair steps for common problems
105
-	 */
106
-	public function run() {
107
-		if (count($this->repairSteps) === 0) {
108
-			$this->emit('\OC\Repair', 'info', ['No repair steps available']);
103
+    /**
104
+     * Run a series of repair steps for common problems
105
+     */
106
+    public function run() {
107
+        if (count($this->repairSteps) === 0) {
108
+            $this->emit('\OC\Repair', 'info', ['No repair steps available']);
109 109
 
110
-			return;
111
-		}
112
-		// run each repair step
113
-		foreach ($this->repairSteps as $step) {
114
-			$this->currentStep = $step->getName();
115
-			$this->emit('\OC\Repair', 'step', [$this->currentStep]);
116
-			$step->run($this);
117
-		}
118
-	}
110
+            return;
111
+        }
112
+        // run each repair step
113
+        foreach ($this->repairSteps as $step) {
114
+            $this->currentStep = $step->getName();
115
+            $this->emit('\OC\Repair', 'step', [$this->currentStep]);
116
+            $step->run($this);
117
+        }
118
+    }
119 119
 
120
-	/**
121
-	 * Add repair step
122
-	 *
123
-	 * @param IRepairStep|string $repairStep repair step
124
-	 * @throws \Exception
125
-	 */
126
-	public function addStep($repairStep) {
127
-		if (is_string($repairStep)) {
128
-			try {
129
-				$s = \OC::$server->query($repairStep);
130
-			} catch (QueryException $e) {
131
-				if (class_exists($repairStep)) {
132
-					$s = new $repairStep();
133
-				} else {
134
-					throw new \Exception("Repair step '$repairStep' is unknown");
135
-				}
136
-			}
120
+    /**
121
+     * Add repair step
122
+     *
123
+     * @param IRepairStep|string $repairStep repair step
124
+     * @throws \Exception
125
+     */
126
+    public function addStep($repairStep) {
127
+        if (is_string($repairStep)) {
128
+            try {
129
+                $s = \OC::$server->query($repairStep);
130
+            } catch (QueryException $e) {
131
+                if (class_exists($repairStep)) {
132
+                    $s = new $repairStep();
133
+                } else {
134
+                    throw new \Exception("Repair step '$repairStep' is unknown");
135
+                }
136
+            }
137 137
 
138
-			if ($s instanceof IRepairStep) {
139
-				$this->repairSteps[] = $s;
140
-			} else {
141
-				throw new \Exception("Repair step '$repairStep' is not of type \\OCP\\Migration\\IRepairStep");
142
-			}
143
-		} else {
144
-			$this->repairSteps[] = $repairStep;
145
-		}
146
-	}
138
+            if ($s instanceof IRepairStep) {
139
+                $this->repairSteps[] = $s;
140
+            } else {
141
+                throw new \Exception("Repair step '$repairStep' is not of type \\OCP\\Migration\\IRepairStep");
142
+            }
143
+        } else {
144
+            $this->repairSteps[] = $repairStep;
145
+        }
146
+    }
147 147
 
148
-	/**
149
-	 * Returns the default repair steps to be run on the
150
-	 * command line or after an upgrade.
151
-	 *
152
-	 * @return IRepairStep[]
153
-	 */
154
-	public static function getRepairSteps() {
155
-		return [
156
-			new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false),
157
-			new RepairMimeTypes(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
158
-			new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()),
159
-			new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
160
-			new MoveUpdaterStepFile(\OC::$server->getConfig()),
161
-			new MoveAvatars(
162
-				\OC::$server->getJobList(),
163
-				\OC::$server->getConfig()
164
-			),
165
-			new CleanPreviews(
166
-				\OC::$server->getJobList(),
167
-				\OC::$server->getUserManager(),
168
-				\OC::$server->getConfig()
169
-			),
170
-			new FixMountStorages(\OC::$server->getDatabaseConnection()),
171
-			new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
172
-			new InstallCoreBundle(
173
-				\OC::$server->query(BundleFetcher::class),
174
-				\OC::$server->getConfig(),
175
-				\OC::$server->query(Installer::class)
176
-			),
177
-			new AddLogRotateJob(\OC::$server->getJobList()),
178
-			new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)),
179
-			new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)),
180
-			new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()),
181
-			new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()),
182
-			new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->getLogger()),
183
-			new AddClenupLoginFlowV2BackgroundJob(\OC::$server->getJobList()),
184
-			new RemoveLinkShares(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getGroupManager(), \OC::$server->getNotificationManager(), \OC::$server->query(ITimeFactory::class)),
185
-			new ClearCollectionsAccessCache(\OC::$server->getConfig(), \OC::$server->query(IManager::class)),
186
-			\OC::$server->query(ResetGeneratedAvatarFlag::class),
187
-			\OC::$server->query(EncryptionLegacyCipher::class),
188
-			\OC::$server->query(EncryptionMigration::class),
189
-			\OC::$server->get(ShippedDashboardEnable::class),
190
-			\OC::$server->get(AddBruteForceCleanupJob::class),
191
-			\OC::$server->get(AddCheckForUserCertificatesJob::class),
192
-		];
193
-	}
148
+    /**
149
+     * Returns the default repair steps to be run on the
150
+     * command line or after an upgrade.
151
+     *
152
+     * @return IRepairStep[]
153
+     */
154
+    public static function getRepairSteps() {
155
+        return [
156
+            new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false),
157
+            new RepairMimeTypes(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
158
+            new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()),
159
+            new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
160
+            new MoveUpdaterStepFile(\OC::$server->getConfig()),
161
+            new MoveAvatars(
162
+                \OC::$server->getJobList(),
163
+                \OC::$server->getConfig()
164
+            ),
165
+            new CleanPreviews(
166
+                \OC::$server->getJobList(),
167
+                \OC::$server->getUserManager(),
168
+                \OC::$server->getConfig()
169
+            ),
170
+            new FixMountStorages(\OC::$server->getDatabaseConnection()),
171
+            new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
172
+            new InstallCoreBundle(
173
+                \OC::$server->query(BundleFetcher::class),
174
+                \OC::$server->getConfig(),
175
+                \OC::$server->query(Installer::class)
176
+            ),
177
+            new AddLogRotateJob(\OC::$server->getJobList()),
178
+            new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)),
179
+            new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)),
180
+            new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()),
181
+            new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()),
182
+            new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->getLogger()),
183
+            new AddClenupLoginFlowV2BackgroundJob(\OC::$server->getJobList()),
184
+            new RemoveLinkShares(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getGroupManager(), \OC::$server->getNotificationManager(), \OC::$server->query(ITimeFactory::class)),
185
+            new ClearCollectionsAccessCache(\OC::$server->getConfig(), \OC::$server->query(IManager::class)),
186
+            \OC::$server->query(ResetGeneratedAvatarFlag::class),
187
+            \OC::$server->query(EncryptionLegacyCipher::class),
188
+            \OC::$server->query(EncryptionMigration::class),
189
+            \OC::$server->get(ShippedDashboardEnable::class),
190
+            \OC::$server->get(AddBruteForceCleanupJob::class),
191
+            \OC::$server->get(AddCheckForUserCertificatesJob::class),
192
+        ];
193
+    }
194 194
 
195
-	/**
196
-	 * Returns expensive repair steps to be run on the
197
-	 * command line with a special option.
198
-	 *
199
-	 * @return IRepairStep[]
200
-	 */
201
-	public static function getExpensiveRepairSteps() {
202
-		return [
203
-			new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
204
-			\OC::$server->get(ValidatePhoneNumber::class),
205
-		];
206
-	}
195
+    /**
196
+     * Returns expensive repair steps to be run on the
197
+     * command line with a special option.
198
+     *
199
+     * @return IRepairStep[]
200
+     */
201
+    public static function getExpensiveRepairSteps() {
202
+        return [
203
+            new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
204
+            \OC::$server->get(ValidatePhoneNumber::class),
205
+        ];
206
+    }
207 207
 
208
-	/**
209
-	 * Returns the repair steps to be run before an
210
-	 * upgrade.
211
-	 *
212
-	 * @return IRepairStep[]
213
-	 */
214
-	public static function getBeforeUpgradeRepairSteps() {
215
-		/** @var Connection $connection */
216
-		$connection = \OC::$server->get(Connection::class);
217
-		/** @var ConnectionAdapter $connectionAdapter */
218
-		$connectionAdapter = \OC::$server->get(ConnectionAdapter::class);
219
-		$config = \OC::$server->getConfig();
220
-		$steps = [
221
-			new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connectionAdapter, true),
222
-			new SqliteAutoincrement($connection),
223
-			new SaveAccountsTableData($connectionAdapter, $config),
224
-			new DropAccountTermsTable($connectionAdapter)
225
-		];
208
+    /**
209
+     * Returns the repair steps to be run before an
210
+     * upgrade.
211
+     *
212
+     * @return IRepairStep[]
213
+     */
214
+    public static function getBeforeUpgradeRepairSteps() {
215
+        /** @var Connection $connection */
216
+        $connection = \OC::$server->get(Connection::class);
217
+        /** @var ConnectionAdapter $connectionAdapter */
218
+        $connectionAdapter = \OC::$server->get(ConnectionAdapter::class);
219
+        $config = \OC::$server->getConfig();
220
+        $steps = [
221
+            new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connectionAdapter, true),
222
+            new SqliteAutoincrement($connection),
223
+            new SaveAccountsTableData($connectionAdapter, $config),
224
+            new DropAccountTermsTable($connectionAdapter)
225
+        ];
226 226
 
227
-		return $steps;
228
-	}
227
+        return $steps;
228
+    }
229 229
 
230
-	/**
231
-	 * @param string $scope
232
-	 * @param string $method
233
-	 * @param array $arguments
234
-	 */
235
-	public function emit($scope, $method, array $arguments = []) {
236
-		if (!is_null($this->dispatcher)) {
237
-			$this->dispatcher->dispatch("$scope::$method",
238
-				new GenericEvent("$scope::$method", $arguments));
239
-		}
240
-	}
230
+    /**
231
+     * @param string $scope
232
+     * @param string $method
233
+     * @param array $arguments
234
+     */
235
+    public function emit($scope, $method, array $arguments = []) {
236
+        if (!is_null($this->dispatcher)) {
237
+            $this->dispatcher->dispatch("$scope::$method",
238
+                new GenericEvent("$scope::$method", $arguments));
239
+        }
240
+    }
241 241
 
242
-	public function info($string) {
243
-		// for now just emit as we did in the past
244
-		$this->emit('\OC\Repair', 'info', [$string]);
245
-	}
242
+    public function info($string) {
243
+        // for now just emit as we did in the past
244
+        $this->emit('\OC\Repair', 'info', [$string]);
245
+    }
246 246
 
247
-	/**
248
-	 * @param string $message
249
-	 */
250
-	public function warning($message) {
251
-		// for now just emit as we did in the past
252
-		$this->emit('\OC\Repair', 'warning', [$message]);
253
-	}
247
+    /**
248
+     * @param string $message
249
+     */
250
+    public function warning($message) {
251
+        // for now just emit as we did in the past
252
+        $this->emit('\OC\Repair', 'warning', [$message]);
253
+    }
254 254
 
255
-	/**
256
-	 * @param int $max
257
-	 */
258
-	public function startProgress($max = 0) {
259
-		// for now just emit as we did in the past
260
-		$this->emit('\OC\Repair', 'startProgress', [$max, $this->currentStep]);
261
-	}
255
+    /**
256
+     * @param int $max
257
+     */
258
+    public function startProgress($max = 0) {
259
+        // for now just emit as we did in the past
260
+        $this->emit('\OC\Repair', 'startProgress', [$max, $this->currentStep]);
261
+    }
262 262
 
263
-	/**
264
-	 * @param int $step
265
-	 * @param string $description
266
-	 */
267
-	public function advance($step = 1, $description = '') {
268
-		// for now just emit as we did in the past
269
-		$this->emit('\OC\Repair', 'advance', [$step, $description]);
270
-	}
263
+    /**
264
+     * @param int $step
265
+     * @param string $description
266
+     */
267
+    public function advance($step = 1, $description = '') {
268
+        // for now just emit as we did in the past
269
+        $this->emit('\OC\Repair', 'advance', [$step, $description]);
270
+    }
271 271
 
272
-	/**
273
-	 * @param int $max
274
-	 */
275
-	public function finishProgress() {
276
-		// for now just emit as we did in the past
277
-		$this->emit('\OC\Repair', 'finishProgress', []);
278
-	}
272
+    /**
273
+     * @param int $max
274
+     */
275
+    public function finishProgress() {
276
+        // for now just emit as we did in the past
277
+        $this->emit('\OC\Repair', 'finishProgress', []);
278
+    }
279 279
 }
Please login to merge, or discard this patch.
lib/private/User/Database.php 1 patch
Indentation   +418 added lines, -418 removed lines patch added patch discarded remove patch
@@ -77,422 +77,422 @@
 block discarded – undo
77 77
  * Class for user management in a SQL Database (e.g. MySQL, SQLite)
78 78
  */
79 79
 class Database extends ABackend implements
80
-	ICreateUserBackend,
81
-			   ISetPasswordBackend,
82
-			   ISetDisplayNameBackend,
83
-			   IGetDisplayNameBackend,
84
-			   ICheckPasswordBackend,
85
-			   IGetHomeBackend,
86
-			   ICountUsersBackend,
87
-			   IGetRealUIDBackend {
88
-	/** @var CappedMemoryCache */
89
-	private $cache;
90
-
91
-	/** @var IEventDispatcher */
92
-	private $eventDispatcher;
93
-
94
-	/** @var IDBConnection */
95
-	private $dbConn;
96
-
97
-	/** @var string */
98
-	private $table;
99
-
100
-	/**
101
-	 * \OC\User\Database constructor.
102
-	 *
103
-	 * @param IEventDispatcher $eventDispatcher
104
-	 * @param string $table
105
-	 */
106
-	public function __construct($eventDispatcher = null, $table = 'users') {
107
-		$this->cache = new CappedMemoryCache();
108
-		$this->table = $table;
109
-		$this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class);
110
-	}
111
-
112
-	/**
113
-	 * FIXME: This function should not be required!
114
-	 */
115
-	private function fixDI() {
116
-		if ($this->dbConn === null) {
117
-			$this->dbConn = \OC::$server->getDatabaseConnection();
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * Create a new user
123
-	 *
124
-	 * @param string $uid The username of the user to create
125
-	 * @param string $password The password of the new user
126
-	 * @return bool
127
-	 *
128
-	 * Creates a new user. Basic checking of username is done in OC_User
129
-	 * itself, not in its subclasses.
130
-	 */
131
-	public function createUser(string $uid, string $password): bool {
132
-		$this->fixDI();
133
-
134
-		if (!$this->userExists($uid)) {
135
-			$this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
136
-
137
-			$qb = $this->dbConn->getQueryBuilder();
138
-			$qb->insert($this->table)
139
-				->values([
140
-					'uid' => $qb->createNamedParameter($uid),
141
-					'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
142
-					'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
143
-				]);
144
-
145
-			$result = $qb->execute();
146
-
147
-			// Clear cache
148
-			unset($this->cache[$uid]);
149
-
150
-			return $result ? true : false;
151
-		}
152
-
153
-		return false;
154
-	}
155
-
156
-	/**
157
-	 * delete a user
158
-	 *
159
-	 * @param string $uid The username of the user to delete
160
-	 * @return bool
161
-	 *
162
-	 * Deletes a user
163
-	 */
164
-	public function deleteUser($uid) {
165
-		$this->fixDI();
166
-
167
-		// Delete user-group-relation
168
-		$query = $this->dbConn->getQueryBuilder();
169
-		$query->delete($this->table)
170
-			->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
171
-		$result = $query->execute();
172
-
173
-		if (isset($this->cache[$uid])) {
174
-			unset($this->cache[$uid]);
175
-		}
176
-
177
-		return $result ? true : false;
178
-	}
179
-
180
-	private function updatePassword(string $uid, string $passwordHash): bool {
181
-		$query = $this->dbConn->getQueryBuilder();
182
-		$query->update($this->table)
183
-			->set('password', $query->createNamedParameter($passwordHash))
184
-			->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
185
-		$result = $query->execute();
186
-
187
-		return $result ? true : false;
188
-	}
189
-
190
-	/**
191
-	 * Set password
192
-	 *
193
-	 * @param string $uid The username
194
-	 * @param string $password The new password
195
-	 * @return bool
196
-	 *
197
-	 * Change the password of a user
198
-	 */
199
-	public function setPassword(string $uid, string $password): bool {
200
-		$this->fixDI();
201
-
202
-		if ($this->userExists($uid)) {
203
-			$this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
204
-
205
-			$hasher = \OC::$server->getHasher();
206
-			$hashedPassword = $hasher->hash($password);
207
-
208
-			return $this->updatePassword($uid, $hashedPassword);
209
-		}
210
-
211
-		return false;
212
-	}
213
-
214
-	/**
215
-	 * Set display name
216
-	 *
217
-	 * @param string $uid The username
218
-	 * @param string $displayName The new display name
219
-	 * @return bool
220
-	 *
221
-	 * Change the display name of a user
222
-	 */
223
-	public function setDisplayName(string $uid, string $displayName): bool {
224
-		$this->fixDI();
225
-
226
-		if ($this->userExists($uid)) {
227
-			$query = $this->dbConn->getQueryBuilder();
228
-			$query->update($this->table)
229
-				->set('displayname', $query->createNamedParameter($displayName))
230
-				->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
231
-			$query->execute();
232
-
233
-			$this->cache[$uid]['displayname'] = $displayName;
234
-
235
-			return true;
236
-		}
237
-
238
-		return false;
239
-	}
240
-
241
-	/**
242
-	 * get display name of the user
243
-	 *
244
-	 * @param string $uid user ID of the user
245
-	 * @return string display name
246
-	 */
247
-	public function getDisplayName($uid): string {
248
-		$uid = (string)$uid;
249
-		$this->loadUser($uid);
250
-		return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname'];
251
-	}
252
-
253
-	/**
254
-	 * Get a list of all display names and user ids.
255
-	 *
256
-	 * @param string $search
257
-	 * @param string|null $limit
258
-	 * @param string|null $offset
259
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
260
-	 */
261
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
262
-		$limit = $this->fixLimit($limit);
263
-
264
-		$this->fixDI();
265
-
266
-		$query = $this->dbConn->getQueryBuilder();
267
-
268
-		$query->select('uid', 'displayname')
269
-			->from($this->table, 'u')
270
-			->leftJoin('u', 'preferences', 'p', $query->expr()->andX(
271
-				$query->expr()->eq('userid', 'uid'),
272
-				$query->expr()->eq('appid', $query->expr()->literal('settings')),
273
-				$query->expr()->eq('configkey', $query->expr()->literal('email')))
274
-			)
275
-			// sqlite doesn't like re-using a single named parameter here
276
-			->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
277
-			->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
278
-			->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
279
-			->orderBy($query->func()->lower('displayname'), 'ASC')
280
-			->orderBy('uid_lower', 'ASC')
281
-			->setMaxResults($limit)
282
-			->setFirstResult($offset);
283
-
284
-		$result = $query->execute();
285
-		$displayNames = [];
286
-		while ($row = $result->fetch()) {
287
-			$displayNames[(string)$row['uid']] = (string)$row['displayname'];
288
-		}
289
-
290
-		return $displayNames;
291
-	}
292
-
293
-	/**
294
-	 * Check if the password is correct
295
-	 *
296
-	 * @param string $loginName The loginname
297
-	 * @param string $password The password
298
-	 * @return string
299
-	 *
300
-	 * Check if the password is correct without logging in the user
301
-	 * returns the user id or false
302
-	 */
303
-	public function checkPassword(string $loginName, string $password) {
304
-		$this->fixDI();
305
-
306
-		$qb = $this->dbConn->getQueryBuilder();
307
-		$qb->select('uid', 'password')
308
-			->from($this->table)
309
-			->where(
310
-				$qb->expr()->eq(
311
-					'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName))
312
-				)
313
-			);
314
-		$result = $qb->execute();
315
-		$row = $result->fetch();
316
-		$result->closeCursor();
317
-
318
-		if ($row) {
319
-			$storedHash = $row['password'];
320
-			$newHash = '';
321
-			if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
322
-				if (!empty($newHash)) {
323
-					$this->updatePassword($loginName, $newHash);
324
-				}
325
-				return (string)$row['uid'];
326
-			}
327
-		}
328
-
329
-		return false;
330
-	}
331
-
332
-	/**
333
-	 * Load an user in the cache
334
-	 *
335
-	 * @param string $uid the username
336
-	 * @return boolean true if user was found, false otherwise
337
-	 */
338
-	private function loadUser($uid) {
339
-		$this->fixDI();
340
-
341
-		$uid = (string)$uid;
342
-		if (!isset($this->cache[$uid])) {
343
-			//guests $uid could be NULL or ''
344
-			if ($uid === '') {
345
-				$this->cache[$uid] = false;
346
-				return true;
347
-			}
348
-
349
-			$qb = $this->dbConn->getQueryBuilder();
350
-			$qb->select('uid', 'displayname')
351
-				->from($this->table)
352
-				->where(
353
-					$qb->expr()->eq(
354
-						'uid_lower', $qb->createNamedParameter(mb_strtolower($uid))
355
-					)
356
-				);
357
-			$result = $qb->execute();
358
-			$row = $result->fetch();
359
-			$result->closeCursor();
360
-
361
-			$this->cache[$uid] = false;
362
-
363
-			// "uid" is primary key, so there can only be a single result
364
-			if ($row !== false) {
365
-				$this->cache[$uid]['uid'] = (string)$row['uid'];
366
-				$this->cache[$uid]['displayname'] = (string)$row['displayname'];
367
-			} else {
368
-				return false;
369
-			}
370
-		}
371
-
372
-		return true;
373
-	}
374
-
375
-	/**
376
-	 * Get a list of all users
377
-	 *
378
-	 * @param string $search
379
-	 * @param null|int $limit
380
-	 * @param null|int $offset
381
-	 * @return string[] an array of all uids
382
-	 */
383
-	public function getUsers($search = '', $limit = null, $offset = null) {
384
-		$limit = $this->fixLimit($limit);
385
-
386
-		$users = $this->getDisplayNames($search, $limit, $offset);
387
-		$userIds = array_map(function ($uid) {
388
-			return (string)$uid;
389
-		}, array_keys($users));
390
-		sort($userIds, SORT_STRING | SORT_FLAG_CASE);
391
-		return $userIds;
392
-	}
393
-
394
-	/**
395
-	 * check if a user exists
396
-	 *
397
-	 * @param string $uid the username
398
-	 * @return boolean
399
-	 */
400
-	public function userExists($uid) {
401
-		$this->loadUser($uid);
402
-		return $this->cache[$uid] !== false;
403
-	}
404
-
405
-	/**
406
-	 * get the user's home directory
407
-	 *
408
-	 * @param string $uid the username
409
-	 * @return string|false
410
-	 */
411
-	public function getHome(string $uid) {
412
-		if ($this->userExists($uid)) {
413
-			return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid;
414
-		}
415
-
416
-		return false;
417
-	}
418
-
419
-	/**
420
-	 * @return bool
421
-	 */
422
-	public function hasUserListings() {
423
-		return true;
424
-	}
425
-
426
-	/**
427
-	 * counts the users in the database
428
-	 *
429
-	 * @return int|bool
430
-	 */
431
-	public function countUsers() {
432
-		$this->fixDI();
433
-
434
-		$query = $this->dbConn->getQueryBuilder();
435
-		$query->select($query->func()->count('uid'))
436
-			->from($this->table);
437
-		$result = $query->execute();
438
-
439
-		return $result->fetchOne();
440
-	}
441
-
442
-	/**
443
-	 * returns the username for the given login name in the correct casing
444
-	 *
445
-	 * @param string $loginName
446
-	 * @return string|false
447
-	 */
448
-	public function loginName2UserName($loginName) {
449
-		if ($this->userExists($loginName)) {
450
-			return $this->cache[$loginName]['uid'];
451
-		}
452
-
453
-		return false;
454
-	}
455
-
456
-	/**
457
-	 * Backend name to be shown in user management
458
-	 *
459
-	 * @return string the name of the backend to be shown
460
-	 */
461
-	public function getBackendName() {
462
-		return 'Database';
463
-	}
464
-
465
-	public static function preLoginNameUsedAsUserName($param) {
466
-		if (!isset($param['uid'])) {
467
-			throw new \Exception('key uid is expected to be set in $param');
468
-		}
469
-
470
-		$backends = \OC::$server->getUserManager()->getBackends();
471
-		foreach ($backends as $backend) {
472
-			if ($backend instanceof Database) {
473
-				/** @var \OC\User\Database $backend */
474
-				$uid = $backend->loginName2UserName($param['uid']);
475
-				if ($uid !== false) {
476
-					$param['uid'] = $uid;
477
-					return;
478
-				}
479
-			}
480
-		}
481
-	}
482
-
483
-	public function getRealUID(string $uid): string {
484
-		if (!$this->userExists($uid)) {
485
-			throw new \RuntimeException($uid . ' does not exist');
486
-		}
487
-
488
-		return $this->cache[$uid]['uid'];
489
-	}
490
-
491
-	private function fixLimit($limit) {
492
-		if (is_int($limit) && $limit >= 0) {
493
-			return $limit;
494
-		}
495
-
496
-		return null;
497
-	}
80
+    ICreateUserBackend,
81
+                ISetPasswordBackend,
82
+                ISetDisplayNameBackend,
83
+                IGetDisplayNameBackend,
84
+                ICheckPasswordBackend,
85
+                IGetHomeBackend,
86
+                ICountUsersBackend,
87
+                IGetRealUIDBackend {
88
+    /** @var CappedMemoryCache */
89
+    private $cache;
90
+
91
+    /** @var IEventDispatcher */
92
+    private $eventDispatcher;
93
+
94
+    /** @var IDBConnection */
95
+    private $dbConn;
96
+
97
+    /** @var string */
98
+    private $table;
99
+
100
+    /**
101
+     * \OC\User\Database constructor.
102
+     *
103
+     * @param IEventDispatcher $eventDispatcher
104
+     * @param string $table
105
+     */
106
+    public function __construct($eventDispatcher = null, $table = 'users') {
107
+        $this->cache = new CappedMemoryCache();
108
+        $this->table = $table;
109
+        $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class);
110
+    }
111
+
112
+    /**
113
+     * FIXME: This function should not be required!
114
+     */
115
+    private function fixDI() {
116
+        if ($this->dbConn === null) {
117
+            $this->dbConn = \OC::$server->getDatabaseConnection();
118
+        }
119
+    }
120
+
121
+    /**
122
+     * Create a new user
123
+     *
124
+     * @param string $uid The username of the user to create
125
+     * @param string $password The password of the new user
126
+     * @return bool
127
+     *
128
+     * Creates a new user. Basic checking of username is done in OC_User
129
+     * itself, not in its subclasses.
130
+     */
131
+    public function createUser(string $uid, string $password): bool {
132
+        $this->fixDI();
133
+
134
+        if (!$this->userExists($uid)) {
135
+            $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
136
+
137
+            $qb = $this->dbConn->getQueryBuilder();
138
+            $qb->insert($this->table)
139
+                ->values([
140
+                    'uid' => $qb->createNamedParameter($uid),
141
+                    'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
142
+                    'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
143
+                ]);
144
+
145
+            $result = $qb->execute();
146
+
147
+            // Clear cache
148
+            unset($this->cache[$uid]);
149
+
150
+            return $result ? true : false;
151
+        }
152
+
153
+        return false;
154
+    }
155
+
156
+    /**
157
+     * delete a user
158
+     *
159
+     * @param string $uid The username of the user to delete
160
+     * @return bool
161
+     *
162
+     * Deletes a user
163
+     */
164
+    public function deleteUser($uid) {
165
+        $this->fixDI();
166
+
167
+        // Delete user-group-relation
168
+        $query = $this->dbConn->getQueryBuilder();
169
+        $query->delete($this->table)
170
+            ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
171
+        $result = $query->execute();
172
+
173
+        if (isset($this->cache[$uid])) {
174
+            unset($this->cache[$uid]);
175
+        }
176
+
177
+        return $result ? true : false;
178
+    }
179
+
180
+    private function updatePassword(string $uid, string $passwordHash): bool {
181
+        $query = $this->dbConn->getQueryBuilder();
182
+        $query->update($this->table)
183
+            ->set('password', $query->createNamedParameter($passwordHash))
184
+            ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
185
+        $result = $query->execute();
186
+
187
+        return $result ? true : false;
188
+    }
189
+
190
+    /**
191
+     * Set password
192
+     *
193
+     * @param string $uid The username
194
+     * @param string $password The new password
195
+     * @return bool
196
+     *
197
+     * Change the password of a user
198
+     */
199
+    public function setPassword(string $uid, string $password): bool {
200
+        $this->fixDI();
201
+
202
+        if ($this->userExists($uid)) {
203
+            $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
204
+
205
+            $hasher = \OC::$server->getHasher();
206
+            $hashedPassword = $hasher->hash($password);
207
+
208
+            return $this->updatePassword($uid, $hashedPassword);
209
+        }
210
+
211
+        return false;
212
+    }
213
+
214
+    /**
215
+     * Set display name
216
+     *
217
+     * @param string $uid The username
218
+     * @param string $displayName The new display name
219
+     * @return bool
220
+     *
221
+     * Change the display name of a user
222
+     */
223
+    public function setDisplayName(string $uid, string $displayName): bool {
224
+        $this->fixDI();
225
+
226
+        if ($this->userExists($uid)) {
227
+            $query = $this->dbConn->getQueryBuilder();
228
+            $query->update($this->table)
229
+                ->set('displayname', $query->createNamedParameter($displayName))
230
+                ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
231
+            $query->execute();
232
+
233
+            $this->cache[$uid]['displayname'] = $displayName;
234
+
235
+            return true;
236
+        }
237
+
238
+        return false;
239
+    }
240
+
241
+    /**
242
+     * get display name of the user
243
+     *
244
+     * @param string $uid user ID of the user
245
+     * @return string display name
246
+     */
247
+    public function getDisplayName($uid): string {
248
+        $uid = (string)$uid;
249
+        $this->loadUser($uid);
250
+        return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname'];
251
+    }
252
+
253
+    /**
254
+     * Get a list of all display names and user ids.
255
+     *
256
+     * @param string $search
257
+     * @param string|null $limit
258
+     * @param string|null $offset
259
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
260
+     */
261
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
262
+        $limit = $this->fixLimit($limit);
263
+
264
+        $this->fixDI();
265
+
266
+        $query = $this->dbConn->getQueryBuilder();
267
+
268
+        $query->select('uid', 'displayname')
269
+            ->from($this->table, 'u')
270
+            ->leftJoin('u', 'preferences', 'p', $query->expr()->andX(
271
+                $query->expr()->eq('userid', 'uid'),
272
+                $query->expr()->eq('appid', $query->expr()->literal('settings')),
273
+                $query->expr()->eq('configkey', $query->expr()->literal('email')))
274
+            )
275
+            // sqlite doesn't like re-using a single named parameter here
276
+            ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
277
+            ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
278
+            ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
279
+            ->orderBy($query->func()->lower('displayname'), 'ASC')
280
+            ->orderBy('uid_lower', 'ASC')
281
+            ->setMaxResults($limit)
282
+            ->setFirstResult($offset);
283
+
284
+        $result = $query->execute();
285
+        $displayNames = [];
286
+        while ($row = $result->fetch()) {
287
+            $displayNames[(string)$row['uid']] = (string)$row['displayname'];
288
+        }
289
+
290
+        return $displayNames;
291
+    }
292
+
293
+    /**
294
+     * Check if the password is correct
295
+     *
296
+     * @param string $loginName The loginname
297
+     * @param string $password The password
298
+     * @return string
299
+     *
300
+     * Check if the password is correct without logging in the user
301
+     * returns the user id or false
302
+     */
303
+    public function checkPassword(string $loginName, string $password) {
304
+        $this->fixDI();
305
+
306
+        $qb = $this->dbConn->getQueryBuilder();
307
+        $qb->select('uid', 'password')
308
+            ->from($this->table)
309
+            ->where(
310
+                $qb->expr()->eq(
311
+                    'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName))
312
+                )
313
+            );
314
+        $result = $qb->execute();
315
+        $row = $result->fetch();
316
+        $result->closeCursor();
317
+
318
+        if ($row) {
319
+            $storedHash = $row['password'];
320
+            $newHash = '';
321
+            if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
322
+                if (!empty($newHash)) {
323
+                    $this->updatePassword($loginName, $newHash);
324
+                }
325
+                return (string)$row['uid'];
326
+            }
327
+        }
328
+
329
+        return false;
330
+    }
331
+
332
+    /**
333
+     * Load an user in the cache
334
+     *
335
+     * @param string $uid the username
336
+     * @return boolean true if user was found, false otherwise
337
+     */
338
+    private function loadUser($uid) {
339
+        $this->fixDI();
340
+
341
+        $uid = (string)$uid;
342
+        if (!isset($this->cache[$uid])) {
343
+            //guests $uid could be NULL or ''
344
+            if ($uid === '') {
345
+                $this->cache[$uid] = false;
346
+                return true;
347
+            }
348
+
349
+            $qb = $this->dbConn->getQueryBuilder();
350
+            $qb->select('uid', 'displayname')
351
+                ->from($this->table)
352
+                ->where(
353
+                    $qb->expr()->eq(
354
+                        'uid_lower', $qb->createNamedParameter(mb_strtolower($uid))
355
+                    )
356
+                );
357
+            $result = $qb->execute();
358
+            $row = $result->fetch();
359
+            $result->closeCursor();
360
+
361
+            $this->cache[$uid] = false;
362
+
363
+            // "uid" is primary key, so there can only be a single result
364
+            if ($row !== false) {
365
+                $this->cache[$uid]['uid'] = (string)$row['uid'];
366
+                $this->cache[$uid]['displayname'] = (string)$row['displayname'];
367
+            } else {
368
+                return false;
369
+            }
370
+        }
371
+
372
+        return true;
373
+    }
374
+
375
+    /**
376
+     * Get a list of all users
377
+     *
378
+     * @param string $search
379
+     * @param null|int $limit
380
+     * @param null|int $offset
381
+     * @return string[] an array of all uids
382
+     */
383
+    public function getUsers($search = '', $limit = null, $offset = null) {
384
+        $limit = $this->fixLimit($limit);
385
+
386
+        $users = $this->getDisplayNames($search, $limit, $offset);
387
+        $userIds = array_map(function ($uid) {
388
+            return (string)$uid;
389
+        }, array_keys($users));
390
+        sort($userIds, SORT_STRING | SORT_FLAG_CASE);
391
+        return $userIds;
392
+    }
393
+
394
+    /**
395
+     * check if a user exists
396
+     *
397
+     * @param string $uid the username
398
+     * @return boolean
399
+     */
400
+    public function userExists($uid) {
401
+        $this->loadUser($uid);
402
+        return $this->cache[$uid] !== false;
403
+    }
404
+
405
+    /**
406
+     * get the user's home directory
407
+     *
408
+     * @param string $uid the username
409
+     * @return string|false
410
+     */
411
+    public function getHome(string $uid) {
412
+        if ($this->userExists($uid)) {
413
+            return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid;
414
+        }
415
+
416
+        return false;
417
+    }
418
+
419
+    /**
420
+     * @return bool
421
+     */
422
+    public function hasUserListings() {
423
+        return true;
424
+    }
425
+
426
+    /**
427
+     * counts the users in the database
428
+     *
429
+     * @return int|bool
430
+     */
431
+    public function countUsers() {
432
+        $this->fixDI();
433
+
434
+        $query = $this->dbConn->getQueryBuilder();
435
+        $query->select($query->func()->count('uid'))
436
+            ->from($this->table);
437
+        $result = $query->execute();
438
+
439
+        return $result->fetchOne();
440
+    }
441
+
442
+    /**
443
+     * returns the username for the given login name in the correct casing
444
+     *
445
+     * @param string $loginName
446
+     * @return string|false
447
+     */
448
+    public function loginName2UserName($loginName) {
449
+        if ($this->userExists($loginName)) {
450
+            return $this->cache[$loginName]['uid'];
451
+        }
452
+
453
+        return false;
454
+    }
455
+
456
+    /**
457
+     * Backend name to be shown in user management
458
+     *
459
+     * @return string the name of the backend to be shown
460
+     */
461
+    public function getBackendName() {
462
+        return 'Database';
463
+    }
464
+
465
+    public static function preLoginNameUsedAsUserName($param) {
466
+        if (!isset($param['uid'])) {
467
+            throw new \Exception('key uid is expected to be set in $param');
468
+        }
469
+
470
+        $backends = \OC::$server->getUserManager()->getBackends();
471
+        foreach ($backends as $backend) {
472
+            if ($backend instanceof Database) {
473
+                /** @var \OC\User\Database $backend */
474
+                $uid = $backend->loginName2UserName($param['uid']);
475
+                if ($uid !== false) {
476
+                    $param['uid'] = $uid;
477
+                    return;
478
+                }
479
+            }
480
+        }
481
+    }
482
+
483
+    public function getRealUID(string $uid): string {
484
+        if (!$this->userExists($uid)) {
485
+            throw new \RuntimeException($uid . ' does not exist');
486
+        }
487
+
488
+        return $this->cache[$uid]['uid'];
489
+    }
490
+
491
+    private function fixLimit($limit) {
492
+        if (is_int($limit) && $limit >= 0) {
493
+            return $limit;
494
+        }
495
+
496
+        return null;
497
+    }
498 498
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 2 patches
Indentation   +601 added lines, -601 removed lines patch added patch discarded remove patch
@@ -68,605 +68,605 @@
 block discarded – undo
68 68
  * @package OC\User
69 69
  */
70 70
 class Manager extends PublicEmitter implements IUserManager {
71
-	/**
72
-	 * @var \OCP\UserInterface[] $backends
73
-	 */
74
-	private $backends = [];
75
-
76
-	/**
77
-	 * @var \OC\User\User[] $cachedUsers
78
-	 */
79
-	private $cachedUsers = [];
80
-
81
-	/** @var IConfig */
82
-	private $config;
83
-
84
-	/** @var EventDispatcherInterface */
85
-	private $dispatcher;
86
-
87
-	/** @var IEventDispatcher */
88
-	private $eventDispatcher;
89
-
90
-	public function __construct(IConfig $config,
91
-								EventDispatcherInterface $oldDispatcher,
92
-								IEventDispatcher $eventDispatcher) {
93
-		$this->config = $config;
94
-		$this->dispatcher = $oldDispatcher;
95
-		$cachedUsers = &$this->cachedUsers;
96
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
97
-			/** @var \OC\User\User $user */
98
-			unset($cachedUsers[$user->getUID()]);
99
-		});
100
-		$this->eventDispatcher = $eventDispatcher;
101
-	}
102
-
103
-	/**
104
-	 * Get the active backends
105
-	 * @return \OCP\UserInterface[]
106
-	 */
107
-	public function getBackends() {
108
-		return $this->backends;
109
-	}
110
-
111
-	/**
112
-	 * register a user backend
113
-	 *
114
-	 * @param \OCP\UserInterface $backend
115
-	 */
116
-	public function registerBackend($backend) {
117
-		$this->backends[] = $backend;
118
-	}
119
-
120
-	/**
121
-	 * remove a user backend
122
-	 *
123
-	 * @param \OCP\UserInterface $backend
124
-	 */
125
-	public function removeBackend($backend) {
126
-		$this->cachedUsers = [];
127
-		if (($i = array_search($backend, $this->backends)) !== false) {
128
-			unset($this->backends[$i]);
129
-		}
130
-	}
131
-
132
-	/**
133
-	 * remove all user backends
134
-	 */
135
-	public function clearBackends() {
136
-		$this->cachedUsers = [];
137
-		$this->backends = [];
138
-	}
139
-
140
-	/**
141
-	 * get a user by user id
142
-	 *
143
-	 * @param string $uid
144
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
145
-	 */
146
-	public function get($uid) {
147
-		if (is_null($uid) || $uid === '' || $uid === false) {
148
-			return null;
149
-		}
150
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
151
-			return $this->cachedUsers[$uid];
152
-		}
153
-		foreach ($this->backends as $backend) {
154
-			if ($backend->userExists($uid)) {
155
-				return $this->getUserObject($uid, $backend);
156
-			}
157
-		}
158
-		return null;
159
-	}
160
-
161
-	/**
162
-	 * get or construct the user object
163
-	 *
164
-	 * @param string $uid
165
-	 * @param \OCP\UserInterface $backend
166
-	 * @param bool $cacheUser If false the newly created user object will not be cached
167
-	 * @return \OC\User\User
168
-	 */
169
-	protected function getUserObject($uid, $backend, $cacheUser = true) {
170
-		if ($backend instanceof IGetRealUIDBackend) {
171
-			$uid = $backend->getRealUID($uid);
172
-		}
173
-
174
-		if (isset($this->cachedUsers[$uid])) {
175
-			return $this->cachedUsers[$uid];
176
-		}
177
-
178
-		$user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
179
-		if ($cacheUser) {
180
-			$this->cachedUsers[$uid] = $user;
181
-		}
182
-		return $user;
183
-	}
184
-
185
-	/**
186
-	 * check if a user exists
187
-	 *
188
-	 * @param string $uid
189
-	 * @return bool
190
-	 */
191
-	public function userExists($uid) {
192
-		$user = $this->get($uid);
193
-		return ($user !== null);
194
-	}
195
-
196
-	/**
197
-	 * Check if the password is valid for the user
198
-	 *
199
-	 * @param string $loginName
200
-	 * @param string $password
201
-	 * @return mixed the User object on success, false otherwise
202
-	 */
203
-	public function checkPassword($loginName, $password) {
204
-		$result = $this->checkPasswordNoLogging($loginName, $password);
205
-
206
-		if ($result === false) {
207
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
208
-		}
209
-
210
-		return $result;
211
-	}
212
-
213
-	/**
214
-	 * Check if the password is valid for the user
215
-	 *
216
-	 * @internal
217
-	 * @param string $loginName
218
-	 * @param string $password
219
-	 * @return IUser|false the User object on success, false otherwise
220
-	 */
221
-	public function checkPasswordNoLogging($loginName, $password) {
222
-		$loginName = str_replace("\0", '', $loginName);
223
-		$password = str_replace("\0", '', $password);
224
-
225
-		foreach ($this->backends as $backend) {
226
-			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
227
-				$uid = $backend->checkPassword($loginName, $password);
228
-				if ($uid !== false) {
229
-					return $this->getUserObject($uid, $backend);
230
-				}
231
-			}
232
-		}
233
-
234
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
235
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
236
-		// to contains urlencoded patterns by "accident".
237
-		$password = urldecode($password);
238
-
239
-		foreach ($this->backends as $backend) {
240
-			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
241
-				$uid = $backend->checkPassword($loginName, $password);
242
-				if ($uid !== false) {
243
-					return $this->getUserObject($uid, $backend);
244
-				}
245
-			}
246
-		}
247
-
248
-		return false;
249
-	}
250
-
251
-	/**
252
-	 * search by user id
253
-	 *
254
-	 * @param string $pattern
255
-	 * @param int $limit
256
-	 * @param int $offset
257
-	 * @return \OC\User\User[]
258
-	 */
259
-	public function search($pattern, $limit = null, $offset = null) {
260
-		$users = [];
261
-		foreach ($this->backends as $backend) {
262
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
263
-			if (is_array($backendUsers)) {
264
-				foreach ($backendUsers as $uid) {
265
-					$users[$uid] = $this->getUserObject($uid, $backend);
266
-				}
267
-			}
268
-		}
269
-
270
-		uasort($users, function ($a, $b) {
271
-			/**
272
-			 * @var \OC\User\User $a
273
-			 * @var \OC\User\User $b
274
-			 */
275
-			return strcasecmp($a->getUID(), $b->getUID());
276
-		});
277
-		return $users;
278
-	}
279
-
280
-	/**
281
-	 * search by displayName
282
-	 *
283
-	 * @param string $pattern
284
-	 * @param int $limit
285
-	 * @param int $offset
286
-	 * @return \OC\User\User[]
287
-	 */
288
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
289
-		$users = [];
290
-		foreach ($this->backends as $backend) {
291
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
292
-			if (is_array($backendUsers)) {
293
-				foreach ($backendUsers as $uid => $displayName) {
294
-					$users[] = $this->getUserObject($uid, $backend);
295
-				}
296
-			}
297
-		}
298
-
299
-		usort($users, function ($a, $b) {
300
-			/**
301
-			 * @var \OC\User\User $a
302
-			 * @var \OC\User\User $b
303
-			 */
304
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
305
-		});
306
-		return $users;
307
-	}
308
-
309
-	/**
310
-	 * @param string $uid
311
-	 * @param string $password
312
-	 * @throws \InvalidArgumentException
313
-	 * @return bool|IUser the created user or false
314
-	 */
315
-	public function createUser($uid, $password) {
316
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
317
-		if (\OC::$server->get(IRegistry::class)->delegateIsHardUserLimitReached()) {
318
-			$l = \OC::$server->getL10N('lib');
319
-			throw new HintException($l->t('The user limit has been reached and the user was not created.'));
320
-		}
321
-
322
-		$localBackends = [];
323
-		foreach ($this->backends as $backend) {
324
-			if ($backend instanceof Database) {
325
-				// First check if there is another user backend
326
-				$localBackends[] = $backend;
327
-				continue;
328
-			}
329
-
330
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
331
-				return $this->createUserFromBackend($uid, $password, $backend);
332
-			}
333
-		}
334
-
335
-		foreach ($localBackends as $backend) {
336
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
337
-				return $this->createUserFromBackend($uid, $password, $backend);
338
-			}
339
-		}
340
-
341
-		return false;
342
-	}
343
-
344
-	/**
345
-	 * @param string $uid
346
-	 * @param string $password
347
-	 * @param UserInterface $backend
348
-	 * @return IUser|null
349
-	 * @throws \InvalidArgumentException
350
-	 */
351
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
352
-		$l = \OC::$server->getL10N('lib');
353
-
354
-		// Check the name for bad characters
355
-		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
356
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
357
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
358
-				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
359
-		}
360
-
361
-		// No empty username
362
-		if (trim($uid) === '') {
363
-			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
364
-		}
365
-
366
-		// No whitespace at the beginning or at the end
367
-		if (trim($uid) !== $uid) {
368
-			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
369
-		}
370
-
371
-		// Username only consists of 1 or 2 dots (directory traversal)
372
-		if ($uid === '.' || $uid === '..') {
373
-			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
374
-		}
375
-
376
-		if (!$this->verifyUid($uid)) {
377
-			throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
378
-		}
379
-
380
-		// No empty password
381
-		if (trim($password) === '') {
382
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
383
-		}
384
-
385
-		// Check if user already exists
386
-		if ($this->userExists($uid)) {
387
-			throw new \InvalidArgumentException($l->t('The username is already being used'));
388
-		}
389
-
390
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
391
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
392
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
393
-		$state = $backend->createUser($uid, $password);
394
-		if ($state === false) {
395
-			throw new \InvalidArgumentException($l->t('Could not create user'));
396
-		}
397
-		$user = $this->getUserObject($uid, $backend);
398
-		if ($user instanceof IUser) {
399
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
400
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
401
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
402
-		}
403
-		return $user;
404
-	}
405
-
406
-	/**
407
-	 * returns how many users per backend exist (if supported by backend)
408
-	 *
409
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
410
-	 *                entry in the preferences table will be affected
411
-	 * @return array|int an array of backend class as key and count number as value
412
-	 *                if $hasLoggedIn is true only an int is returned
413
-	 */
414
-	public function countUsers($hasLoggedIn = false) {
415
-		if ($hasLoggedIn) {
416
-			return $this->countSeenUsers();
417
-		}
418
-		$userCountStatistics = [];
419
-		foreach ($this->backends as $backend) {
420
-			if ($backend->implementsActions(Backend::COUNT_USERS)) {
421
-				$backendUsers = $backend->countUsers();
422
-				if ($backendUsers !== false) {
423
-					if ($backend instanceof IUserBackend) {
424
-						$name = $backend->getBackendName();
425
-					} else {
426
-						$name = get_class($backend);
427
-					}
428
-					if (isset($userCountStatistics[$name])) {
429
-						$userCountStatistics[$name] += $backendUsers;
430
-					} else {
431
-						$userCountStatistics[$name] = $backendUsers;
432
-					}
433
-				}
434
-			}
435
-		}
436
-		return $userCountStatistics;
437
-	}
438
-
439
-	/**
440
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
441
-	 *
442
-	 * @param IGroup[] $groups an array of gid to search in
443
-	 * @return array|int an array of backend class as key and count number as value
444
-	 *                if $hasLoggedIn is true only an int is returned
445
-	 */
446
-	public function countUsersOfGroups(array $groups) {
447
-		$users = [];
448
-		foreach ($groups as $group) {
449
-			$usersIds = array_map(function ($user) {
450
-				return $user->getUID();
451
-			}, $group->getUsers());
452
-			$users = array_merge($users, $usersIds);
453
-		}
454
-		return count(array_unique($users));
455
-	}
456
-
457
-	/**
458
-	 * The callback is executed for each user on each backend.
459
-	 * If the callback returns false no further users will be retrieved.
460
-	 *
461
-	 * @param \Closure $callback
462
-	 * @param string $search
463
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
464
-	 *                in the preferences table will be affected
465
-	 * @since 9.0.0
466
-	 */
467
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
468
-		if ($onlySeen) {
469
-			$this->callForSeenUsers($callback);
470
-		} else {
471
-			foreach ($this->getBackends() as $backend) {
472
-				$limit = 500;
473
-				$offset = 0;
474
-				do {
475
-					$users = $backend->getUsers($search, $limit, $offset);
476
-					foreach ($users as $uid) {
477
-						if (!$backend->userExists($uid)) {
478
-							continue;
479
-						}
480
-						$user = $this->getUserObject($uid, $backend, false);
481
-						$return = $callback($user);
482
-						if ($return === false) {
483
-							break;
484
-						}
485
-					}
486
-					$offset += $limit;
487
-				} while (count($users) >= $limit);
488
-			}
489
-		}
490
-	}
491
-
492
-	/**
493
-	 * returns how many users are disabled
494
-	 *
495
-	 * @return int
496
-	 * @since 12.0.0
497
-	 */
498
-	public function countDisabledUsers(): int {
499
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
500
-		$queryBuilder->select($queryBuilder->func()->count('*'))
501
-			->from('preferences')
502
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
503
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
504
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
505
-
506
-
507
-		$result = $queryBuilder->execute();
508
-		$count = $result->fetchOne();
509
-		$result->closeCursor();
510
-
511
-		if ($count !== false) {
512
-			$count = (int)$count;
513
-		} else {
514
-			$count = 0;
515
-		}
516
-
517
-		return $count;
518
-	}
519
-
520
-	/**
521
-	 * returns how many users are disabled in the requested groups
522
-	 *
523
-	 * @param array $groups groupids to search
524
-	 * @return int
525
-	 * @since 14.0.0
526
-	 */
527
-	public function countDisabledUsersOfGroups(array $groups): int {
528
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
529
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
530
-			->from('preferences', 'p')
531
-			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
532
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
533
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
534
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
535
-			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
536
-
537
-		$result = $queryBuilder->execute();
538
-		$count = $result->fetchOne();
539
-		$result->closeCursor();
540
-
541
-		if ($count !== false) {
542
-			$count = (int)$count;
543
-		} else {
544
-			$count = 0;
545
-		}
546
-
547
-		return $count;
548
-	}
549
-
550
-	/**
551
-	 * returns how many users have logged in once
552
-	 *
553
-	 * @return int
554
-	 * @since 11.0.0
555
-	 */
556
-	public function countSeenUsers() {
557
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
558
-		$queryBuilder->select($queryBuilder->func()->count('*'))
559
-			->from('preferences')
560
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
561
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
562
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
563
-
564
-		$query = $queryBuilder->execute();
565
-
566
-		$result = (int)$query->fetchOne();
567
-		$query->closeCursor();
568
-
569
-		return $result;
570
-	}
571
-
572
-	/**
573
-	 * @param \Closure $callback
574
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
575
-	 * @since 11.0.0
576
-	 */
577
-	public function callForSeenUsers(\Closure $callback) {
578
-		$limit = 1000;
579
-		$offset = 0;
580
-		do {
581
-			$userIds = $this->getSeenUserIds($limit, $offset);
582
-			$offset += $limit;
583
-			foreach ($userIds as $userId) {
584
-				foreach ($this->backends as $backend) {
585
-					if ($backend->userExists($userId)) {
586
-						$user = $this->getUserObject($userId, $backend, false);
587
-						$return = $callback($user);
588
-						if ($return === false) {
589
-							return;
590
-						}
591
-						break;
592
-					}
593
-				}
594
-			}
595
-		} while (count($userIds) >= $limit);
596
-	}
597
-
598
-	/**
599
-	 * Getting all userIds that have a listLogin value requires checking the
600
-	 * value in php because on oracle you cannot use a clob in a where clause,
601
-	 * preventing us from doing a not null or length(value) > 0 check.
602
-	 *
603
-	 * @param int $limit
604
-	 * @param int $offset
605
-	 * @return string[] with user ids
606
-	 */
607
-	private function getSeenUserIds($limit = null, $offset = null) {
608
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
609
-		$queryBuilder->select(['userid'])
610
-			->from('preferences')
611
-			->where($queryBuilder->expr()->eq(
612
-				'appid', $queryBuilder->createNamedParameter('login'))
613
-			)
614
-			->andWhere($queryBuilder->expr()->eq(
615
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
616
-			)
617
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
618
-			);
619
-
620
-		if ($limit !== null) {
621
-			$queryBuilder->setMaxResults($limit);
622
-		}
623
-		if ($offset !== null) {
624
-			$queryBuilder->setFirstResult($offset);
625
-		}
626
-		$query = $queryBuilder->execute();
627
-		$result = [];
628
-
629
-		while ($row = $query->fetch()) {
630
-			$result[] = $row['userid'];
631
-		}
632
-
633
-		$query->closeCursor();
634
-
635
-		return $result;
636
-	}
637
-
638
-	/**
639
-	 * @param string $email
640
-	 * @return IUser[]
641
-	 * @since 9.1.0
642
-	 */
643
-	public function getByEmail($email) {
644
-		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
645
-
646
-		$users = array_map(function ($uid) {
647
-			return $this->get($uid);
648
-		}, $userIds);
649
-
650
-		return array_values(array_filter($users, function ($u) {
651
-			return ($u instanceof IUser);
652
-		}));
653
-	}
654
-
655
-	private function verifyUid(string $uid): bool {
656
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
657
-
658
-		if (\in_array($uid, [
659
-			'.htaccess',
660
-			'files_external',
661
-			'.ocdata',
662
-			'owncloud.log',
663
-			'nextcloud.log',
664
-			$appdata], true)) {
665
-			return false;
666
-		}
667
-
668
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
669
-
670
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
671
-	}
71
+    /**
72
+     * @var \OCP\UserInterface[] $backends
73
+     */
74
+    private $backends = [];
75
+
76
+    /**
77
+     * @var \OC\User\User[] $cachedUsers
78
+     */
79
+    private $cachedUsers = [];
80
+
81
+    /** @var IConfig */
82
+    private $config;
83
+
84
+    /** @var EventDispatcherInterface */
85
+    private $dispatcher;
86
+
87
+    /** @var IEventDispatcher */
88
+    private $eventDispatcher;
89
+
90
+    public function __construct(IConfig $config,
91
+                                EventDispatcherInterface $oldDispatcher,
92
+                                IEventDispatcher $eventDispatcher) {
93
+        $this->config = $config;
94
+        $this->dispatcher = $oldDispatcher;
95
+        $cachedUsers = &$this->cachedUsers;
96
+        $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
97
+            /** @var \OC\User\User $user */
98
+            unset($cachedUsers[$user->getUID()]);
99
+        });
100
+        $this->eventDispatcher = $eventDispatcher;
101
+    }
102
+
103
+    /**
104
+     * Get the active backends
105
+     * @return \OCP\UserInterface[]
106
+     */
107
+    public function getBackends() {
108
+        return $this->backends;
109
+    }
110
+
111
+    /**
112
+     * register a user backend
113
+     *
114
+     * @param \OCP\UserInterface $backend
115
+     */
116
+    public function registerBackend($backend) {
117
+        $this->backends[] = $backend;
118
+    }
119
+
120
+    /**
121
+     * remove a user backend
122
+     *
123
+     * @param \OCP\UserInterface $backend
124
+     */
125
+    public function removeBackend($backend) {
126
+        $this->cachedUsers = [];
127
+        if (($i = array_search($backend, $this->backends)) !== false) {
128
+            unset($this->backends[$i]);
129
+        }
130
+    }
131
+
132
+    /**
133
+     * remove all user backends
134
+     */
135
+    public function clearBackends() {
136
+        $this->cachedUsers = [];
137
+        $this->backends = [];
138
+    }
139
+
140
+    /**
141
+     * get a user by user id
142
+     *
143
+     * @param string $uid
144
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
145
+     */
146
+    public function get($uid) {
147
+        if (is_null($uid) || $uid === '' || $uid === false) {
148
+            return null;
149
+        }
150
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
151
+            return $this->cachedUsers[$uid];
152
+        }
153
+        foreach ($this->backends as $backend) {
154
+            if ($backend->userExists($uid)) {
155
+                return $this->getUserObject($uid, $backend);
156
+            }
157
+        }
158
+        return null;
159
+    }
160
+
161
+    /**
162
+     * get or construct the user object
163
+     *
164
+     * @param string $uid
165
+     * @param \OCP\UserInterface $backend
166
+     * @param bool $cacheUser If false the newly created user object will not be cached
167
+     * @return \OC\User\User
168
+     */
169
+    protected function getUserObject($uid, $backend, $cacheUser = true) {
170
+        if ($backend instanceof IGetRealUIDBackend) {
171
+            $uid = $backend->getRealUID($uid);
172
+        }
173
+
174
+        if (isset($this->cachedUsers[$uid])) {
175
+            return $this->cachedUsers[$uid];
176
+        }
177
+
178
+        $user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
179
+        if ($cacheUser) {
180
+            $this->cachedUsers[$uid] = $user;
181
+        }
182
+        return $user;
183
+    }
184
+
185
+    /**
186
+     * check if a user exists
187
+     *
188
+     * @param string $uid
189
+     * @return bool
190
+     */
191
+    public function userExists($uid) {
192
+        $user = $this->get($uid);
193
+        return ($user !== null);
194
+    }
195
+
196
+    /**
197
+     * Check if the password is valid for the user
198
+     *
199
+     * @param string $loginName
200
+     * @param string $password
201
+     * @return mixed the User object on success, false otherwise
202
+     */
203
+    public function checkPassword($loginName, $password) {
204
+        $result = $this->checkPasswordNoLogging($loginName, $password);
205
+
206
+        if ($result === false) {
207
+            \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
208
+        }
209
+
210
+        return $result;
211
+    }
212
+
213
+    /**
214
+     * Check if the password is valid for the user
215
+     *
216
+     * @internal
217
+     * @param string $loginName
218
+     * @param string $password
219
+     * @return IUser|false the User object on success, false otherwise
220
+     */
221
+    public function checkPasswordNoLogging($loginName, $password) {
222
+        $loginName = str_replace("\0", '', $loginName);
223
+        $password = str_replace("\0", '', $password);
224
+
225
+        foreach ($this->backends as $backend) {
226
+            if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
227
+                $uid = $backend->checkPassword($loginName, $password);
228
+                if ($uid !== false) {
229
+                    return $this->getUserObject($uid, $backend);
230
+                }
231
+            }
232
+        }
233
+
234
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
235
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
236
+        // to contains urlencoded patterns by "accident".
237
+        $password = urldecode($password);
238
+
239
+        foreach ($this->backends as $backend) {
240
+            if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
241
+                $uid = $backend->checkPassword($loginName, $password);
242
+                if ($uid !== false) {
243
+                    return $this->getUserObject($uid, $backend);
244
+                }
245
+            }
246
+        }
247
+
248
+        return false;
249
+    }
250
+
251
+    /**
252
+     * search by user id
253
+     *
254
+     * @param string $pattern
255
+     * @param int $limit
256
+     * @param int $offset
257
+     * @return \OC\User\User[]
258
+     */
259
+    public function search($pattern, $limit = null, $offset = null) {
260
+        $users = [];
261
+        foreach ($this->backends as $backend) {
262
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
263
+            if (is_array($backendUsers)) {
264
+                foreach ($backendUsers as $uid) {
265
+                    $users[$uid] = $this->getUserObject($uid, $backend);
266
+                }
267
+            }
268
+        }
269
+
270
+        uasort($users, function ($a, $b) {
271
+            /**
272
+             * @var \OC\User\User $a
273
+             * @var \OC\User\User $b
274
+             */
275
+            return strcasecmp($a->getUID(), $b->getUID());
276
+        });
277
+        return $users;
278
+    }
279
+
280
+    /**
281
+     * search by displayName
282
+     *
283
+     * @param string $pattern
284
+     * @param int $limit
285
+     * @param int $offset
286
+     * @return \OC\User\User[]
287
+     */
288
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
289
+        $users = [];
290
+        foreach ($this->backends as $backend) {
291
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
292
+            if (is_array($backendUsers)) {
293
+                foreach ($backendUsers as $uid => $displayName) {
294
+                    $users[] = $this->getUserObject($uid, $backend);
295
+                }
296
+            }
297
+        }
298
+
299
+        usort($users, function ($a, $b) {
300
+            /**
301
+             * @var \OC\User\User $a
302
+             * @var \OC\User\User $b
303
+             */
304
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
305
+        });
306
+        return $users;
307
+    }
308
+
309
+    /**
310
+     * @param string $uid
311
+     * @param string $password
312
+     * @throws \InvalidArgumentException
313
+     * @return bool|IUser the created user or false
314
+     */
315
+    public function createUser($uid, $password) {
316
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
317
+        if (\OC::$server->get(IRegistry::class)->delegateIsHardUserLimitReached()) {
318
+            $l = \OC::$server->getL10N('lib');
319
+            throw new HintException($l->t('The user limit has been reached and the user was not created.'));
320
+        }
321
+
322
+        $localBackends = [];
323
+        foreach ($this->backends as $backend) {
324
+            if ($backend instanceof Database) {
325
+                // First check if there is another user backend
326
+                $localBackends[] = $backend;
327
+                continue;
328
+            }
329
+
330
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
331
+                return $this->createUserFromBackend($uid, $password, $backend);
332
+            }
333
+        }
334
+
335
+        foreach ($localBackends as $backend) {
336
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
337
+                return $this->createUserFromBackend($uid, $password, $backend);
338
+            }
339
+        }
340
+
341
+        return false;
342
+    }
343
+
344
+    /**
345
+     * @param string $uid
346
+     * @param string $password
347
+     * @param UserInterface $backend
348
+     * @return IUser|null
349
+     * @throws \InvalidArgumentException
350
+     */
351
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
352
+        $l = \OC::$server->getL10N('lib');
353
+
354
+        // Check the name for bad characters
355
+        // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
356
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
357
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
358
+                . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
359
+        }
360
+
361
+        // No empty username
362
+        if (trim($uid) === '') {
363
+            throw new \InvalidArgumentException($l->t('A valid username must be provided'));
364
+        }
365
+
366
+        // No whitespace at the beginning or at the end
367
+        if (trim($uid) !== $uid) {
368
+            throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
369
+        }
370
+
371
+        // Username only consists of 1 or 2 dots (directory traversal)
372
+        if ($uid === '.' || $uid === '..') {
373
+            throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
374
+        }
375
+
376
+        if (!$this->verifyUid($uid)) {
377
+            throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
378
+        }
379
+
380
+        // No empty password
381
+        if (trim($password) === '') {
382
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
383
+        }
384
+
385
+        // Check if user already exists
386
+        if ($this->userExists($uid)) {
387
+            throw new \InvalidArgumentException($l->t('The username is already being used'));
388
+        }
389
+
390
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
391
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
392
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
393
+        $state = $backend->createUser($uid, $password);
394
+        if ($state === false) {
395
+            throw new \InvalidArgumentException($l->t('Could not create user'));
396
+        }
397
+        $user = $this->getUserObject($uid, $backend);
398
+        if ($user instanceof IUser) {
399
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
400
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
401
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
402
+        }
403
+        return $user;
404
+    }
405
+
406
+    /**
407
+     * returns how many users per backend exist (if supported by backend)
408
+     *
409
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
410
+     *                entry in the preferences table will be affected
411
+     * @return array|int an array of backend class as key and count number as value
412
+     *                if $hasLoggedIn is true only an int is returned
413
+     */
414
+    public function countUsers($hasLoggedIn = false) {
415
+        if ($hasLoggedIn) {
416
+            return $this->countSeenUsers();
417
+        }
418
+        $userCountStatistics = [];
419
+        foreach ($this->backends as $backend) {
420
+            if ($backend->implementsActions(Backend::COUNT_USERS)) {
421
+                $backendUsers = $backend->countUsers();
422
+                if ($backendUsers !== false) {
423
+                    if ($backend instanceof IUserBackend) {
424
+                        $name = $backend->getBackendName();
425
+                    } else {
426
+                        $name = get_class($backend);
427
+                    }
428
+                    if (isset($userCountStatistics[$name])) {
429
+                        $userCountStatistics[$name] += $backendUsers;
430
+                    } else {
431
+                        $userCountStatistics[$name] = $backendUsers;
432
+                    }
433
+                }
434
+            }
435
+        }
436
+        return $userCountStatistics;
437
+    }
438
+
439
+    /**
440
+     * returns how many users per backend exist in the requested groups (if supported by backend)
441
+     *
442
+     * @param IGroup[] $groups an array of gid to search in
443
+     * @return array|int an array of backend class as key and count number as value
444
+     *                if $hasLoggedIn is true only an int is returned
445
+     */
446
+    public function countUsersOfGroups(array $groups) {
447
+        $users = [];
448
+        foreach ($groups as $group) {
449
+            $usersIds = array_map(function ($user) {
450
+                return $user->getUID();
451
+            }, $group->getUsers());
452
+            $users = array_merge($users, $usersIds);
453
+        }
454
+        return count(array_unique($users));
455
+    }
456
+
457
+    /**
458
+     * The callback is executed for each user on each backend.
459
+     * If the callback returns false no further users will be retrieved.
460
+     *
461
+     * @param \Closure $callback
462
+     * @param string $search
463
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
464
+     *                in the preferences table will be affected
465
+     * @since 9.0.0
466
+     */
467
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
468
+        if ($onlySeen) {
469
+            $this->callForSeenUsers($callback);
470
+        } else {
471
+            foreach ($this->getBackends() as $backend) {
472
+                $limit = 500;
473
+                $offset = 0;
474
+                do {
475
+                    $users = $backend->getUsers($search, $limit, $offset);
476
+                    foreach ($users as $uid) {
477
+                        if (!$backend->userExists($uid)) {
478
+                            continue;
479
+                        }
480
+                        $user = $this->getUserObject($uid, $backend, false);
481
+                        $return = $callback($user);
482
+                        if ($return === false) {
483
+                            break;
484
+                        }
485
+                    }
486
+                    $offset += $limit;
487
+                } while (count($users) >= $limit);
488
+            }
489
+        }
490
+    }
491
+
492
+    /**
493
+     * returns how many users are disabled
494
+     *
495
+     * @return int
496
+     * @since 12.0.0
497
+     */
498
+    public function countDisabledUsers(): int {
499
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
500
+        $queryBuilder->select($queryBuilder->func()->count('*'))
501
+            ->from('preferences')
502
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
503
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
504
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
505
+
506
+
507
+        $result = $queryBuilder->execute();
508
+        $count = $result->fetchOne();
509
+        $result->closeCursor();
510
+
511
+        if ($count !== false) {
512
+            $count = (int)$count;
513
+        } else {
514
+            $count = 0;
515
+        }
516
+
517
+        return $count;
518
+    }
519
+
520
+    /**
521
+     * returns how many users are disabled in the requested groups
522
+     *
523
+     * @param array $groups groupids to search
524
+     * @return int
525
+     * @since 14.0.0
526
+     */
527
+    public function countDisabledUsersOfGroups(array $groups): int {
528
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
529
+        $queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
530
+            ->from('preferences', 'p')
531
+            ->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
532
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
533
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
534
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
535
+            ->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
536
+
537
+        $result = $queryBuilder->execute();
538
+        $count = $result->fetchOne();
539
+        $result->closeCursor();
540
+
541
+        if ($count !== false) {
542
+            $count = (int)$count;
543
+        } else {
544
+            $count = 0;
545
+        }
546
+
547
+        return $count;
548
+    }
549
+
550
+    /**
551
+     * returns how many users have logged in once
552
+     *
553
+     * @return int
554
+     * @since 11.0.0
555
+     */
556
+    public function countSeenUsers() {
557
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
558
+        $queryBuilder->select($queryBuilder->func()->count('*'))
559
+            ->from('preferences')
560
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
561
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
562
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
563
+
564
+        $query = $queryBuilder->execute();
565
+
566
+        $result = (int)$query->fetchOne();
567
+        $query->closeCursor();
568
+
569
+        return $result;
570
+    }
571
+
572
+    /**
573
+     * @param \Closure $callback
574
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
575
+     * @since 11.0.0
576
+     */
577
+    public function callForSeenUsers(\Closure $callback) {
578
+        $limit = 1000;
579
+        $offset = 0;
580
+        do {
581
+            $userIds = $this->getSeenUserIds($limit, $offset);
582
+            $offset += $limit;
583
+            foreach ($userIds as $userId) {
584
+                foreach ($this->backends as $backend) {
585
+                    if ($backend->userExists($userId)) {
586
+                        $user = $this->getUserObject($userId, $backend, false);
587
+                        $return = $callback($user);
588
+                        if ($return === false) {
589
+                            return;
590
+                        }
591
+                        break;
592
+                    }
593
+                }
594
+            }
595
+        } while (count($userIds) >= $limit);
596
+    }
597
+
598
+    /**
599
+     * Getting all userIds that have a listLogin value requires checking the
600
+     * value in php because on oracle you cannot use a clob in a where clause,
601
+     * preventing us from doing a not null or length(value) > 0 check.
602
+     *
603
+     * @param int $limit
604
+     * @param int $offset
605
+     * @return string[] with user ids
606
+     */
607
+    private function getSeenUserIds($limit = null, $offset = null) {
608
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
609
+        $queryBuilder->select(['userid'])
610
+            ->from('preferences')
611
+            ->where($queryBuilder->expr()->eq(
612
+                'appid', $queryBuilder->createNamedParameter('login'))
613
+            )
614
+            ->andWhere($queryBuilder->expr()->eq(
615
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
616
+            )
617
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
618
+            );
619
+
620
+        if ($limit !== null) {
621
+            $queryBuilder->setMaxResults($limit);
622
+        }
623
+        if ($offset !== null) {
624
+            $queryBuilder->setFirstResult($offset);
625
+        }
626
+        $query = $queryBuilder->execute();
627
+        $result = [];
628
+
629
+        while ($row = $query->fetch()) {
630
+            $result[] = $row['userid'];
631
+        }
632
+
633
+        $query->closeCursor();
634
+
635
+        return $result;
636
+    }
637
+
638
+    /**
639
+     * @param string $email
640
+     * @return IUser[]
641
+     * @since 9.1.0
642
+     */
643
+    public function getByEmail($email) {
644
+        $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
645
+
646
+        $users = array_map(function ($uid) {
647
+            return $this->get($uid);
648
+        }, $userIds);
649
+
650
+        return array_values(array_filter($users, function ($u) {
651
+            return ($u instanceof IUser);
652
+        }));
653
+    }
654
+
655
+    private function verifyUid(string $uid): bool {
656
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
657
+
658
+        if (\in_array($uid, [
659
+            '.htaccess',
660
+            'files_external',
661
+            '.ocdata',
662
+            'owncloud.log',
663
+            'nextcloud.log',
664
+            $appdata], true)) {
665
+            return false;
666
+        }
667
+
668
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
669
+
670
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
671
+    }
672 672
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -93,7 +93,7 @@  discard block
 block discarded – undo
93 93
 		$this->config = $config;
94 94
 		$this->dispatcher = $oldDispatcher;
95 95
 		$cachedUsers = &$this->cachedUsers;
96
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
96
+		$this->listen('\OC\User', 'postDelete', function($user) use (&$cachedUsers) {
97 97
 			/** @var \OC\User\User $user */
98 98
 			unset($cachedUsers[$user->getUID()]);
99 99
 		});
@@ -204,7 +204,7 @@  discard block
 block discarded – undo
204 204
 		$result = $this->checkPasswordNoLogging($loginName, $password);
205 205
 
206 206
 		if ($result === false) {
207
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
207
+			\OC::$server->getLogger()->warning('Login failed: \''.$loginName.'\' (Remote IP: \''.\OC::$server->getRequest()->getRemoteAddress().'\')', ['app' => 'core']);
208 208
 		}
209 209
 
210 210
 		return $result;
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
 			}
268 268
 		}
269 269
 
270
-		uasort($users, function ($a, $b) {
270
+		uasort($users, function($a, $b) {
271 271
 			/**
272 272
 			 * @var \OC\User\User $a
273 273
 			 * @var \OC\User\User $b
@@ -296,7 +296,7 @@  discard block
 block discarded – undo
296 296
 			}
297 297
 		}
298 298
 
299
-		usort($users, function ($a, $b) {
299
+		usort($users, function($a, $b) {
300 300
 			/**
301 301
 			 * @var \OC\User\User $a
302 302
 			 * @var \OC\User\User $b
@@ -446,7 +446,7 @@  discard block
 block discarded – undo
446 446
 	public function countUsersOfGroups(array $groups) {
447 447
 		$users = [];
448 448
 		foreach ($groups as $group) {
449
-			$usersIds = array_map(function ($user) {
449
+			$usersIds = array_map(function($user) {
450 450
 				return $user->getUID();
451 451
 			}, $group->getUsers());
452 452
 			$users = array_merge($users, $usersIds);
@@ -509,7 +509,7 @@  discard block
 block discarded – undo
509 509
 		$result->closeCursor();
510 510
 
511 511
 		if ($count !== false) {
512
-			$count = (int)$count;
512
+			$count = (int) $count;
513 513
 		} else {
514 514
 			$count = 0;
515 515
 		}
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
 	 */
527 527
 	public function countDisabledUsersOfGroups(array $groups): int {
528 528
 		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
529
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
529
+		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT '.$queryBuilder->getColumnName('uid').')'))
530 530
 			->from('preferences', 'p')
531 531
 			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
532 532
 			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
@@ -539,7 +539,7 @@  discard block
 block discarded – undo
539 539
 		$result->closeCursor();
540 540
 
541 541
 		if ($count !== false) {
542
-			$count = (int)$count;
542
+			$count = (int) $count;
543 543
 		} else {
544 544
 			$count = 0;
545 545
 		}
@@ -563,7 +563,7 @@  discard block
 block discarded – undo
563 563
 
564 564
 		$query = $queryBuilder->execute();
565 565
 
566
-		$result = (int)$query->fetchOne();
566
+		$result = (int) $query->fetchOne();
567 567
 		$query->closeCursor();
568 568
 
569 569
 		return $result;
@@ -643,17 +643,17 @@  discard block
 block discarded – undo
643 643
 	public function getByEmail($email) {
644 644
 		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
645 645
 
646
-		$users = array_map(function ($uid) {
646
+		$users = array_map(function($uid) {
647 647
 			return $this->get($uid);
648 648
 		}, $userIds);
649 649
 
650
-		return array_values(array_filter($users, function ($u) {
650
+		return array_values(array_filter($users, function($u) {
651 651
 			return ($u instanceof IUser);
652 652
 		}));
653 653
 	}
654 654
 
655 655
 	private function verifyUid(string $uid): bool {
656
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
656
+		$appdata = 'appdata_'.$this->config->getSystemValueString('instanceid');
657 657
 
658 658
 		if (\in_array($uid, [
659 659
 			'.htaccess',
@@ -665,8 +665,8 @@  discard block
 block discarded – undo
665 665
 			return false;
666 666
 		}
667 667
 
668
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
668
+		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT.'/data');
669 669
 
670
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
670
+		return !file_exists(rtrim($dataDirectory, '/').'/'.$uid);
671 671
 	}
672 672
 }
Please login to merge, or discard this patch.