Passed
Push — master ( 344eac...9a3cc0 )
by Christoph
12:50 queued 28s
created
core/Command/App/CheckCode.php 2 patches
Indentation   +145 added lines, -145 removed lines patch added patch discarded remove patch
@@ -44,149 +44,149 @@
 block discarded – undo
44 44
 use Symfony\Component\Console\Output\OutputInterface;
45 45
 
46 46
 class CheckCode extends Command implements CompletionAwareInterface {
47
-	protected $checkers = [
48
-		'private' => PrivateCheck::class,
49
-		'deprecation' => DeprecationCheck::class,
50
-		'strong-comparison' => StrongComparisonCheck::class,
51
-	];
52
-
53
-	protected function configure() {
54
-		$this
55
-			->setName('app:check-code')
56
-			->setDescription('check code to be compliant')
57
-			->addArgument(
58
-				'app-id',
59
-				InputArgument::REQUIRED,
60
-				'check the specified app'
61
-			)
62
-			->addOption(
63
-				'checker',
64
-				'c',
65
-				InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
66
-				'enable the specified checker(s)',
67
-				[ 'private', 'deprecation', 'strong-comparison' ]
68
-			)
69
-			->addOption(
70
-				'--skip-checkers',
71
-				null,
72
-				InputOption::VALUE_NONE,
73
-				'skips the the code checkers to only check info.xml, language and database schema'
74
-			)
75
-			->addOption(
76
-				'--skip-validate-info',
77
-				null,
78
-				InputOption::VALUE_NONE,
79
-				'skips the info.xml/version check'
80
-			);
81
-	}
82
-
83
-	protected function execute(InputInterface $input, OutputInterface $output): int {
84
-		$appId = $input->getArgument('app-id');
85
-
86
-		$checkList = new EmptyCheck();
87
-		foreach ($input->getOption('checker') as $checker) {
88
-			if (!isset($this->checkers[$checker])) {
89
-				throw new \InvalidArgumentException('Invalid checker: '.$checker);
90
-			}
91
-			$checkerClass = $this->checkers[$checker];
92
-			$checkList = new $checkerClass($checkList);
93
-		}
94
-
95
-		$codeChecker = new CodeChecker($checkList, !$input->getOption('skip-validate-info'));
96
-
97
-		$codeChecker->listen('CodeChecker', 'analyseFileBegin', function ($params) use ($output) {
98
-			if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
99
-				$output->writeln("<info>Analysing {$params}</info>");
100
-			}
101
-		});
102
-		$codeChecker->listen('CodeChecker', 'analyseFileFinished', function ($filename, $errors) use ($output) {
103
-			$count = count($errors);
104
-
105
-			// show filename if the verbosity is low, but there are errors in a file
106
-			if ($count > 0 && OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) {
107
-				$output->writeln("<info>Analysing {$filename}</info>");
108
-			}
109
-
110
-			// show error count if there are errors present or the verbosity is high
111
-			if ($count > 0 || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
112
-				$output->writeln(" {$count} errors");
113
-			}
114
-			usort($errors, function ($a, $b) {
115
-				return $a['line'] > $b['line'];
116
-			});
117
-
118
-			foreach ($errors as $p) {
119
-				$line = sprintf("%' 4d", $p['line']);
120
-				$output->writeln("    <error>line $line: {$p['disallowedToken']} - {$p['reason']}</error>");
121
-			}
122
-		});
123
-		$errors = [];
124
-		if (!$input->getOption('skip-checkers')) {
125
-			$errors = $codeChecker->analyse($appId);
126
-		}
127
-
128
-		if (!$input->getOption('skip-validate-info')) {
129
-			$infoChecker = new InfoChecker();
130
-			$infoChecker->listen('InfoChecker', 'parseError', function ($error) use ($output) {
131
-				$output->writeln("<error>Invalid appinfo.xml file found: $error</error>");
132
-			});
133
-
134
-			$infoErrors = $infoChecker->analyse($appId);
135
-
136
-			$errors = array_merge($errors, $infoErrors);
137
-
138
-			$languageParser = new LanguageParseChecker();
139
-			$languageErrors = $languageParser->analyse($appId);
140
-
141
-			foreach ($languageErrors as $languageError) {
142
-				$output->writeln("<error>$languageError</error>");
143
-			}
144
-
145
-			$errors = array_merge($errors, $languageErrors);
146
-
147
-			$databaseSchema = new DatabaseSchemaChecker();
148
-			$schemaErrors = $databaseSchema->analyse($appId);
149
-
150
-			foreach ($schemaErrors['errors'] as $schemaError) {
151
-				$output->writeln("<error>$schemaError</error>");
152
-			}
153
-			foreach ($schemaErrors['warnings'] as $schemaWarning) {
154
-				$output->writeln("<comment>$schemaWarning</comment>");
155
-			}
156
-
157
-			$errors = array_merge($errors, $schemaErrors['errors']);
158
-		}
159
-
160
-		if (empty($errors)) {
161
-			$output->writeln('<info>App is compliant - awesome job!</info>');
162
-			return 0;
163
-		} else {
164
-			$output->writeln('<error>App is not compliant</error>');
165
-			return 101;
166
-		}
167
-	}
168
-
169
-	/**
170
-	 * @param string $optionName
171
-	 * @param CompletionContext $context
172
-	 * @return string[]
173
-	 */
174
-	public function completeOptionValues($optionName, CompletionContext $context) {
175
-		if ($optionName === 'checker') {
176
-			return ['private', 'deprecation', 'strong-comparison'];
177
-		}
178
-		return [];
179
-	}
180
-
181
-	/**
182
-	 * @param string $argumentName
183
-	 * @param CompletionContext $context
184
-	 * @return string[]
185
-	 */
186
-	public function completeArgumentValues($argumentName, CompletionContext $context) {
187
-		if ($argumentName === 'app-id') {
188
-			return \OC_App::getAllApps();
189
-		}
190
-		return [];
191
-	}
47
+    protected $checkers = [
48
+        'private' => PrivateCheck::class,
49
+        'deprecation' => DeprecationCheck::class,
50
+        'strong-comparison' => StrongComparisonCheck::class,
51
+    ];
52
+
53
+    protected function configure() {
54
+        $this
55
+            ->setName('app:check-code')
56
+            ->setDescription('check code to be compliant')
57
+            ->addArgument(
58
+                'app-id',
59
+                InputArgument::REQUIRED,
60
+                'check the specified app'
61
+            )
62
+            ->addOption(
63
+                'checker',
64
+                'c',
65
+                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
66
+                'enable the specified checker(s)',
67
+                [ 'private', 'deprecation', 'strong-comparison' ]
68
+            )
69
+            ->addOption(
70
+                '--skip-checkers',
71
+                null,
72
+                InputOption::VALUE_NONE,
73
+                'skips the the code checkers to only check info.xml, language and database schema'
74
+            )
75
+            ->addOption(
76
+                '--skip-validate-info',
77
+                null,
78
+                InputOption::VALUE_NONE,
79
+                'skips the info.xml/version check'
80
+            );
81
+    }
82
+
83
+    protected function execute(InputInterface $input, OutputInterface $output): int {
84
+        $appId = $input->getArgument('app-id');
85
+
86
+        $checkList = new EmptyCheck();
87
+        foreach ($input->getOption('checker') as $checker) {
88
+            if (!isset($this->checkers[$checker])) {
89
+                throw new \InvalidArgumentException('Invalid checker: '.$checker);
90
+            }
91
+            $checkerClass = $this->checkers[$checker];
92
+            $checkList = new $checkerClass($checkList);
93
+        }
94
+
95
+        $codeChecker = new CodeChecker($checkList, !$input->getOption('skip-validate-info'));
96
+
97
+        $codeChecker->listen('CodeChecker', 'analyseFileBegin', function ($params) use ($output) {
98
+            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
99
+                $output->writeln("<info>Analysing {$params}</info>");
100
+            }
101
+        });
102
+        $codeChecker->listen('CodeChecker', 'analyseFileFinished', function ($filename, $errors) use ($output) {
103
+            $count = count($errors);
104
+
105
+            // show filename if the verbosity is low, but there are errors in a file
106
+            if ($count > 0 && OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) {
107
+                $output->writeln("<info>Analysing {$filename}</info>");
108
+            }
109
+
110
+            // show error count if there are errors present or the verbosity is high
111
+            if ($count > 0 || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
112
+                $output->writeln(" {$count} errors");
113
+            }
114
+            usort($errors, function ($a, $b) {
115
+                return $a['line'] > $b['line'];
116
+            });
117
+
118
+            foreach ($errors as $p) {
119
+                $line = sprintf("%' 4d", $p['line']);
120
+                $output->writeln("    <error>line $line: {$p['disallowedToken']} - {$p['reason']}</error>");
121
+            }
122
+        });
123
+        $errors = [];
124
+        if (!$input->getOption('skip-checkers')) {
125
+            $errors = $codeChecker->analyse($appId);
126
+        }
127
+
128
+        if (!$input->getOption('skip-validate-info')) {
129
+            $infoChecker = new InfoChecker();
130
+            $infoChecker->listen('InfoChecker', 'parseError', function ($error) use ($output) {
131
+                $output->writeln("<error>Invalid appinfo.xml file found: $error</error>");
132
+            });
133
+
134
+            $infoErrors = $infoChecker->analyse($appId);
135
+
136
+            $errors = array_merge($errors, $infoErrors);
137
+
138
+            $languageParser = new LanguageParseChecker();
139
+            $languageErrors = $languageParser->analyse($appId);
140
+
141
+            foreach ($languageErrors as $languageError) {
142
+                $output->writeln("<error>$languageError</error>");
143
+            }
144
+
145
+            $errors = array_merge($errors, $languageErrors);
146
+
147
+            $databaseSchema = new DatabaseSchemaChecker();
148
+            $schemaErrors = $databaseSchema->analyse($appId);
149
+
150
+            foreach ($schemaErrors['errors'] as $schemaError) {
151
+                $output->writeln("<error>$schemaError</error>");
152
+            }
153
+            foreach ($schemaErrors['warnings'] as $schemaWarning) {
154
+                $output->writeln("<comment>$schemaWarning</comment>");
155
+            }
156
+
157
+            $errors = array_merge($errors, $schemaErrors['errors']);
158
+        }
159
+
160
+        if (empty($errors)) {
161
+            $output->writeln('<info>App is compliant - awesome job!</info>');
162
+            return 0;
163
+        } else {
164
+            $output->writeln('<error>App is not compliant</error>');
165
+            return 101;
166
+        }
167
+    }
168
+
169
+    /**
170
+     * @param string $optionName
171
+     * @param CompletionContext $context
172
+     * @return string[]
173
+     */
174
+    public function completeOptionValues($optionName, CompletionContext $context) {
175
+        if ($optionName === 'checker') {
176
+            return ['private', 'deprecation', 'strong-comparison'];
177
+        }
178
+        return [];
179
+    }
180
+
181
+    /**
182
+     * @param string $argumentName
183
+     * @param CompletionContext $context
184
+     * @return string[]
185
+     */
186
+    public function completeArgumentValues($argumentName, CompletionContext $context) {
187
+        if ($argumentName === 'app-id') {
188
+            return \OC_App::getAllApps();
189
+        }
190
+        return [];
191
+    }
192 192
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
 				'c',
65 65
 				InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
66 66
 				'enable the specified checker(s)',
67
-				[ 'private', 'deprecation', 'strong-comparison' ]
67
+				['private', 'deprecation', 'strong-comparison']
68 68
 			)
69 69
 			->addOption(
70 70
 				'--skip-checkers',
@@ -94,12 +94,12 @@  discard block
 block discarded – undo
94 94
 
95 95
 		$codeChecker = new CodeChecker($checkList, !$input->getOption('skip-validate-info'));
96 96
 
97
-		$codeChecker->listen('CodeChecker', 'analyseFileBegin', function ($params) use ($output) {
97
+		$codeChecker->listen('CodeChecker', 'analyseFileBegin', function($params) use ($output) {
98 98
 			if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
99 99
 				$output->writeln("<info>Analysing {$params}</info>");
100 100
 			}
101 101
 		});
102
-		$codeChecker->listen('CodeChecker', 'analyseFileFinished', function ($filename, $errors) use ($output) {
102
+		$codeChecker->listen('CodeChecker', 'analyseFileFinished', function($filename, $errors) use ($output) {
103 103
 			$count = count($errors);
104 104
 
105 105
 			// show filename if the verbosity is low, but there are errors in a file
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 			if ($count > 0 || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
112 112
 				$output->writeln(" {$count} errors");
113 113
 			}
114
-			usort($errors, function ($a, $b) {
114
+			usort($errors, function($a, $b) {
115 115
 				return $a['line'] > $b['line'];
116 116
 			});
117 117
 
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 
128 128
 		if (!$input->getOption('skip-validate-info')) {
129 129
 			$infoChecker = new InfoChecker();
130
-			$infoChecker->listen('InfoChecker', 'parseError', function ($error) use ($output) {
130
+			$infoChecker->listen('InfoChecker', 'parseError', function($error) use ($output) {
131 131
 				$output->writeln("<error>Invalid appinfo.xml file found: $error</error>");
132 132
 			});
133 133
 
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/AmazonS3.php 1 patch
Indentation   +649 added lines, -649 removed lines patch added patch discarded remove patch
@@ -52,653 +52,653 @@
 block discarded – undo
52 52
 use OCP\Constants;
53 53
 
54 54
 class AmazonS3 extends \OC\Files\Storage\Common {
55
-	use S3ConnectionTrait;
56
-	use S3ObjectTrait;
57
-
58
-	public function needsPartFile() {
59
-		return false;
60
-	}
61
-
62
-	/** @var CappedMemoryCache|Result[] */
63
-	private $objectCache;
64
-
65
-	/** @var CappedMemoryCache|bool[] */
66
-	private $directoryCache;
67
-
68
-	/** @var CappedMemoryCache|array */
69
-	private $filesCache;
70
-
71
-	public function __construct($parameters) {
72
-		parent::__construct($parameters);
73
-		$this->parseParams($parameters);
74
-		$this->objectCache = new CappedMemoryCache();
75
-		$this->directoryCache = new CappedMemoryCache();
76
-		$this->filesCache = new CappedMemoryCache();
77
-	}
78
-
79
-	/**
80
-	 * @param string $path
81
-	 * @return string correctly encoded path
82
-	 */
83
-	private function normalizePath($path) {
84
-		$path = trim($path, '/');
85
-
86
-		if (!$path) {
87
-			$path = '.';
88
-		}
89
-
90
-		return $path;
91
-	}
92
-
93
-	private function isRoot($path) {
94
-		return $path === '.';
95
-	}
96
-
97
-	private function cleanKey($path) {
98
-		if ($this->isRoot($path)) {
99
-			return '/';
100
-		}
101
-		return $path;
102
-	}
103
-
104
-	private function clearCache() {
105
-		$this->objectCache = new CappedMemoryCache();
106
-		$this->directoryCache = new CappedMemoryCache();
107
-		$this->filesCache = new CappedMemoryCache();
108
-	}
109
-
110
-	private function invalidateCache($key) {
111
-		unset($this->objectCache[$key]);
112
-		$keys = array_keys($this->objectCache->getData());
113
-		$keyLength = strlen($key);
114
-		foreach ($keys as $existingKey) {
115
-			if (substr($existingKey, 0, $keyLength) === $key) {
116
-				unset($this->objectCache[$existingKey]);
117
-			}
118
-		}
119
-		unset($this->directoryCache[$key], $this->filesCache[$key]);
120
-	}
121
-
122
-	/**
123
-	 * @param $key
124
-	 * @return Result|boolean
125
-	 */
126
-	private function headObject($key) {
127
-		if (!isset($this->objectCache[$key])) {
128
-			try {
129
-				$this->objectCache[$key] = $this->getConnection()->headObject([
130
-					'Bucket' => $this->bucket,
131
-					'Key' => $key
132
-				]);
133
-			} catch (S3Exception $e) {
134
-				if ($e->getStatusCode() >= 500) {
135
-					throw $e;
136
-				}
137
-				$this->objectCache[$key] = false;
138
-			}
139
-		}
140
-
141
-		return $this->objectCache[$key];
142
-	}
143
-
144
-	/**
145
-	 * Return true if directory exists
146
-	 *
147
-	 * There are no folders in s3. A folder like structure could be archived
148
-	 * by prefixing files with the folder name.
149
-	 *
150
-	 * Implementation from flysystem-aws-s3-v3:
151
-	 * https://github.com/thephpleague/flysystem-aws-s3-v3/blob/8241e9cc5b28f981e0d24cdaf9867f14c7498ae4/src/AwsS3Adapter.php#L670-L694
152
-	 *
153
-	 * @param $path
154
-	 * @return bool
155
-	 * @throws \Exception
156
-	 */
157
-	private function doesDirectoryExist($path) {
158
-		if (!isset($this->directoryCache[$path])) {
159
-			// Maybe this isn't an actual key, but a prefix.
160
-			// Do a prefix listing of objects to determine.
161
-			try {
162
-				$result = $this->getConnection()->listObjects([
163
-					'Bucket' => $this->bucket,
164
-					'Prefix' => rtrim($path, '/'),
165
-					'MaxKeys' => 1,
166
-					'Delimiter' => '/',
167
-				]);
168
-
169
-				if ((isset($result['Contents'][0]['Key']) && $result['Contents'][0]['Key'] === rtrim($path, '/') . '/')
170
-					 || isset($result['CommonPrefixes'])) {
171
-					$this->directoryCache[$path] = true;
172
-				} else {
173
-					$this->directoryCache[$path] = false;
174
-				}
175
-			} catch (S3Exception $e) {
176
-				if ($e->getStatusCode() === 403) {
177
-					$this->directoryCache[$path] = false;
178
-				}
179
-				throw $e;
180
-			}
181
-		}
182
-
183
-		return $this->directoryCache[$path];
184
-	}
185
-
186
-	/**
187
-	 * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
188
-	 * TODO Do this in a repair step. requires iterating over all users and loading the mount.json from their home
189
-	 *
190
-	 * @param array $params
191
-	 */
192
-	public function updateLegacyId(array $params) {
193
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
194
-
195
-		// find by old id or bucket
196
-		$stmt = \OC::$server->getDatabaseConnection()->prepare(
197
-			'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
198
-		);
199
-		$stmt->execute([$oldId, $this->id]);
200
-		while ($row = $stmt->fetch()) {
201
-			$storages[$row['id']] = $row['numeric_id'];
202
-		}
203
-
204
-		if (isset($storages[$this->id]) && isset($storages[$oldId])) {
205
-			// if both ids exist, delete the old storage and corresponding filecache entries
206
-			\OC\Files\Cache\Storage::remove($oldId);
207
-		} elseif (isset($storages[$oldId])) {
208
-			// if only the old id exists do an update
209
-			$stmt = \OC::$server->getDatabaseConnection()->prepare(
210
-				'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
211
-			);
212
-			$stmt->execute([$this->id, $oldId]);
213
-		}
214
-		// only the bucket based id may exist, do nothing
215
-	}
216
-
217
-	/**
218
-	 * Remove a file or folder
219
-	 *
220
-	 * @param string $path
221
-	 * @return bool
222
-	 */
223
-	protected function remove($path) {
224
-		// remember fileType to reduce http calls
225
-		$fileType = $this->filetype($path);
226
-		if ($fileType === 'dir') {
227
-			return $this->rmdir($path);
228
-		} elseif ($fileType === 'file') {
229
-			return $this->unlink($path);
230
-		} else {
231
-			return false;
232
-		}
233
-	}
234
-
235
-	public function mkdir($path) {
236
-		$path = $this->normalizePath($path);
237
-
238
-		if ($this->is_dir($path)) {
239
-			return false;
240
-		}
241
-
242
-		try {
243
-			$this->getConnection()->putObject([
244
-				'Bucket' => $this->bucket,
245
-				'Key' => $path . '/',
246
-				'Body' => '',
247
-				'ContentType' => 'httpd/unix-directory'
248
-			]);
249
-			$this->testTimeout();
250
-		} catch (S3Exception $e) {
251
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
252
-			return false;
253
-		}
254
-
255
-		$this->invalidateCache($path);
256
-
257
-		return true;
258
-	}
259
-
260
-	public function file_exists($path) {
261
-		return $this->filetype($path) !== false;
262
-	}
263
-
264
-
265
-	public function rmdir($path) {
266
-		$path = $this->normalizePath($path);
267
-
268
-		if ($this->isRoot($path)) {
269
-			return $this->clearBucket();
270
-		}
271
-
272
-		if (!$this->file_exists($path)) {
273
-			return false;
274
-		}
275
-
276
-		$this->invalidateCache($path);
277
-		return $this->batchDelete($path);
278
-	}
279
-
280
-	protected function clearBucket() {
281
-		$this->clearCache();
282
-		try {
283
-			$this->getConnection()->clearBucket($this->bucket);
284
-			return true;
285
-			// clearBucket() is not working with Ceph, so if it fails we try the slower approach
286
-		} catch (\Exception $e) {
287
-			return $this->batchDelete();
288
-		}
289
-	}
290
-
291
-	private function batchDelete($path = null) {
292
-		$params = [
293
-			'Bucket' => $this->bucket
294
-		];
295
-		if ($path !== null) {
296
-			$params['Prefix'] = $path . '/';
297
-		}
298
-		try {
299
-			$connection = $this->getConnection();
300
-			// Since there are no real directories on S3, we need
301
-			// to delete all objects prefixed with the path.
302
-			do {
303
-				// instead of the iterator, manually loop over the list ...
304
-				$objects = $connection->listObjects($params);
305
-				// ... so we can delete the files in batches
306
-				if (isset($objects['Contents'])) {
307
-					$connection->deleteObjects([
308
-						'Bucket' => $this->bucket,
309
-						'Delete' => [
310
-							'Objects' => $objects['Contents']
311
-						]
312
-					]);
313
-					$this->testTimeout();
314
-				}
315
-				// we reached the end when the list is no longer truncated
316
-			} while ($objects['IsTruncated']);
317
-		} catch (S3Exception $e) {
318
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
319
-			return false;
320
-		}
321
-		return true;
322
-	}
323
-
324
-	public function opendir($path) {
325
-		$path = $this->normalizePath($path);
326
-
327
-		if ($this->isRoot($path)) {
328
-			$path = '';
329
-		} else {
330
-			$path .= '/';
331
-		}
332
-
333
-		try {
334
-			$files = [];
335
-			$results = $this->getConnection()->getPaginator('ListObjects', [
336
-				'Bucket' => $this->bucket,
337
-				'Delimiter' => '/',
338
-				'Prefix' => $path,
339
-			]);
340
-
341
-			foreach ($results as $result) {
342
-				// sub folders
343
-				if (is_array($result['CommonPrefixes'])) {
344
-					foreach ($result['CommonPrefixes'] as $prefix) {
345
-						$directoryName = trim($prefix['Prefix'], '/');
346
-						$files[] = substr($directoryName, strlen($path));
347
-						$this->directoryCache[$directoryName] = true;
348
-					}
349
-				}
350
-				if (is_array($result['Contents'])) {
351
-					foreach ($result['Contents'] as $object) {
352
-						if (isset($object['Key']) && $object['Key'] === $path) {
353
-							// it's the directory itself, skip
354
-							continue;
355
-						}
356
-						$file = basename(
357
-							isset($object['Key']) ? $object['Key'] : $object['Prefix']
358
-						);
359
-						$files[] = $file;
360
-
361
-						// store this information for later usage
362
-						$this->filesCache[$path . $file] = [
363
-							'ContentLength' => $object['Size'],
364
-							'LastModified' => (string)$object['LastModified'],
365
-						];
366
-					}
367
-				}
368
-			}
369
-
370
-			return IteratorDirectory::wrap($files);
371
-		} catch (S3Exception $e) {
372
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
373
-			return false;
374
-		}
375
-	}
376
-
377
-	public function stat($path) {
378
-		$path = $this->normalizePath($path);
379
-
380
-		try {
381
-			$stat = [];
382
-			if ($this->is_dir($path)) {
383
-				//folders don't really exist
384
-				$stat['size'] = -1; //unknown
385
-				$stat['mtime'] = time();
386
-				$cacheEntry = $this->getCache()->get($path);
387
-				if ($cacheEntry instanceof CacheEntry && $this->getMountOption('filesystem_check_changes', 1) !== 1) {
388
-					$stat['size'] = $cacheEntry->getSize();
389
-					$stat['mtime'] = $cacheEntry->getMTime();
390
-				}
391
-			} else {
392
-				$stat['size'] = $this->getContentLength($path);
393
-				$stat['mtime'] = strtotime($this->getLastModified($path));
394
-			}
395
-			$stat['atime'] = time();
396
-
397
-			return $stat;
398
-		} catch (S3Exception $e) {
399
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
400
-			return false;
401
-		}
402
-	}
403
-
404
-	/**
405
-	 * Return content length for object
406
-	 *
407
-	 * When the information is already present (e.g. opendir has been called before)
408
-	 * this value is return. Otherwise a headObject is emitted.
409
-	 *
410
-	 * @param $path
411
-	 * @return int|mixed
412
-	 */
413
-	private function getContentLength($path) {
414
-		if (isset($this->filesCache[$path])) {
415
-			return (int)$this->filesCache[$path]['ContentLength'];
416
-		}
417
-
418
-		$result = $this->headObject($path);
419
-		if (isset($result['ContentLength'])) {
420
-			return (int)$result['ContentLength'];
421
-		}
422
-
423
-		return 0;
424
-	}
425
-
426
-	/**
427
-	 * Return last modified for object
428
-	 *
429
-	 * When the information is already present (e.g. opendir has been called before)
430
-	 * this value is return. Otherwise a headObject is emitted.
431
-	 *
432
-	 * @param $path
433
-	 * @return mixed|string
434
-	 */
435
-	private function getLastModified($path) {
436
-		if (isset($this->filesCache[$path])) {
437
-			return $this->filesCache[$path]['LastModified'];
438
-		}
439
-
440
-		$result = $this->headObject($path);
441
-		if (isset($result['LastModified'])) {
442
-			return $result['LastModified'];
443
-		}
444
-
445
-		return 'now';
446
-	}
447
-
448
-	public function is_dir($path) {
449
-		$path = $this->normalizePath($path);
450
-
451
-		if (isset($this->filesCache[$path])) {
452
-			return false;
453
-		}
454
-
455
-		try {
456
-			return $this->isRoot($path) || $this->doesDirectoryExist($path);
457
-		} catch (S3Exception $e) {
458
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
459
-			return false;
460
-		}
461
-	}
462
-
463
-	public function filetype($path) {
464
-		$path = $this->normalizePath($path);
465
-
466
-		if ($this->isRoot($path)) {
467
-			return 'dir';
468
-		}
469
-
470
-		try {
471
-			if (isset($this->filesCache[$path]) || $this->headObject($path)) {
472
-				return 'file';
473
-			}
474
-			if ($this->doesDirectoryExist($path)) {
475
-				return 'dir';
476
-			}
477
-		} catch (S3Exception $e) {
478
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
479
-			return false;
480
-		}
481
-
482
-		return false;
483
-	}
484
-
485
-	public function getPermissions($path) {
486
-		$type = $this->filetype($path);
487
-		if (!$type) {
488
-			return 0;
489
-		}
490
-		return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
491
-	}
492
-
493
-	public function unlink($path) {
494
-		$path = $this->normalizePath($path);
495
-
496
-		if ($this->is_dir($path)) {
497
-			return $this->rmdir($path);
498
-		}
499
-
500
-		try {
501
-			$this->deleteObject($path);
502
-			$this->invalidateCache($path);
503
-		} catch (S3Exception $e) {
504
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
505
-			return false;
506
-		}
507
-
508
-		return true;
509
-	}
510
-
511
-	public function fopen($path, $mode) {
512
-		$path = $this->normalizePath($path);
513
-
514
-		switch ($mode) {
515
-			case 'r':
516
-			case 'rb':
517
-				// Don't try to fetch empty files
518
-				$stat = $this->stat($path);
519
-				if (is_array($stat) && isset($stat['size']) && $stat['size'] === 0) {
520
-					return fopen('php://memory', $mode);
521
-				}
522
-
523
-				try {
524
-					return $this->readObject($path);
525
-				} catch (S3Exception $e) {
526
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
527
-					return false;
528
-				}
529
-			case 'w':
530
-			case 'wb':
531
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
532
-
533
-				$handle = fopen($tmpFile, 'w');
534
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
535
-					$this->writeBack($tmpFile, $path);
536
-				});
537
-			case 'a':
538
-			case 'ab':
539
-			case 'r+':
540
-			case 'w+':
541
-			case 'wb+':
542
-			case 'a+':
543
-			case 'x':
544
-			case 'x+':
545
-			case 'c':
546
-			case 'c+':
547
-				if (strrpos($path, '.') !== false) {
548
-					$ext = substr($path, strrpos($path, '.'));
549
-				} else {
550
-					$ext = '';
551
-				}
552
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
553
-				if ($this->file_exists($path)) {
554
-					$source = $this->readObject($path);
555
-					file_put_contents($tmpFile, $source);
556
-				}
557
-
558
-				$handle = fopen($tmpFile, $mode);
559
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
560
-					$this->writeBack($tmpFile, $path);
561
-				});
562
-		}
563
-		return false;
564
-	}
565
-
566
-	public function touch($path, $mtime = null) {
567
-		if (is_null($mtime)) {
568
-			$mtime = time();
569
-		}
570
-		$metadata = [
571
-			'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
572
-		];
573
-
574
-		try {
575
-			if (!$this->file_exists($path)) {
576
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
577
-				$this->getConnection()->putObject([
578
-					'Bucket' => $this->bucket,
579
-					'Key' => $this->cleanKey($path),
580
-					'Metadata' => $metadata,
581
-					'Body' => '',
582
-					'ContentType' => $mimeType,
583
-					'MetadataDirective' => 'REPLACE',
584
-				]);
585
-				$this->testTimeout();
586
-			}
587
-		} catch (S3Exception $e) {
588
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
589
-			return false;
590
-		}
591
-
592
-		$this->invalidateCache($path);
593
-		return true;
594
-	}
595
-
596
-	public function copy($path1, $path2) {
597
-		$path1 = $this->normalizePath($path1);
598
-		$path2 = $this->normalizePath($path2);
599
-
600
-		if ($this->is_file($path1)) {
601
-			try {
602
-				$this->getConnection()->copyObject([
603
-					'Bucket' => $this->bucket,
604
-					'Key' => $this->cleanKey($path2),
605
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
606
-				]);
607
-				$this->testTimeout();
608
-			} catch (S3Exception $e) {
609
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
610
-				return false;
611
-			}
612
-		} else {
613
-			$this->remove($path2);
614
-
615
-			try {
616
-				$this->getConnection()->copyObject([
617
-					'Bucket' => $this->bucket,
618
-					'Key' => $path2 . '/',
619
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
620
-				]);
621
-				$this->testTimeout();
622
-			} catch (S3Exception $e) {
623
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
624
-				return false;
625
-			}
626
-
627
-			$dh = $this->opendir($path1);
628
-			if (is_resource($dh)) {
629
-				while (($file = readdir($dh)) !== false) {
630
-					if (\OC\Files\Filesystem::isIgnoredDir($file)) {
631
-						continue;
632
-					}
633
-
634
-					$source = $path1 . '/' . $file;
635
-					$target = $path2 . '/' . $file;
636
-					$this->copy($source, $target);
637
-				}
638
-			}
639
-		}
640
-
641
-		$this->invalidateCache($path2);
642
-
643
-		return true;
644
-	}
645
-
646
-	public function rename($path1, $path2) {
647
-		$path1 = $this->normalizePath($path1);
648
-		$path2 = $this->normalizePath($path2);
649
-
650
-		if ($this->is_file($path1)) {
651
-			if ($this->copy($path1, $path2) === false) {
652
-				return false;
653
-			}
654
-
655
-			if ($this->unlink($path1) === false) {
656
-				$this->unlink($path2);
657
-				return false;
658
-			}
659
-		} else {
660
-			if ($this->copy($path1, $path2) === false) {
661
-				return false;
662
-			}
663
-
664
-			if ($this->rmdir($path1) === false) {
665
-				$this->rmdir($path2);
666
-				return false;
667
-			}
668
-		}
669
-
670
-		return true;
671
-	}
672
-
673
-	public function test() {
674
-		$this->getConnection()->headBucket([
675
-			'Bucket' => $this->bucket
676
-		]);
677
-		return true;
678
-	}
679
-
680
-	public function getId() {
681
-		return $this->id;
682
-	}
683
-
684
-	public function writeBack($tmpFile, $path) {
685
-		try {
686
-			$source = fopen($tmpFile, 'r');
687
-			$this->writeObject($path, $source);
688
-			$this->invalidateCache($path);
689
-
690
-			unlink($tmpFile);
691
-			return true;
692
-		} catch (S3Exception $e) {
693
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
694
-			return false;
695
-		}
696
-	}
697
-
698
-	/**
699
-	 * check if curl is installed
700
-	 */
701
-	public static function checkDependencies() {
702
-		return true;
703
-	}
55
+    use S3ConnectionTrait;
56
+    use S3ObjectTrait;
57
+
58
+    public function needsPartFile() {
59
+        return false;
60
+    }
61
+
62
+    /** @var CappedMemoryCache|Result[] */
63
+    private $objectCache;
64
+
65
+    /** @var CappedMemoryCache|bool[] */
66
+    private $directoryCache;
67
+
68
+    /** @var CappedMemoryCache|array */
69
+    private $filesCache;
70
+
71
+    public function __construct($parameters) {
72
+        parent::__construct($parameters);
73
+        $this->parseParams($parameters);
74
+        $this->objectCache = new CappedMemoryCache();
75
+        $this->directoryCache = new CappedMemoryCache();
76
+        $this->filesCache = new CappedMemoryCache();
77
+    }
78
+
79
+    /**
80
+     * @param string $path
81
+     * @return string correctly encoded path
82
+     */
83
+    private function normalizePath($path) {
84
+        $path = trim($path, '/');
85
+
86
+        if (!$path) {
87
+            $path = '.';
88
+        }
89
+
90
+        return $path;
91
+    }
92
+
93
+    private function isRoot($path) {
94
+        return $path === '.';
95
+    }
96
+
97
+    private function cleanKey($path) {
98
+        if ($this->isRoot($path)) {
99
+            return '/';
100
+        }
101
+        return $path;
102
+    }
103
+
104
+    private function clearCache() {
105
+        $this->objectCache = new CappedMemoryCache();
106
+        $this->directoryCache = new CappedMemoryCache();
107
+        $this->filesCache = new CappedMemoryCache();
108
+    }
109
+
110
+    private function invalidateCache($key) {
111
+        unset($this->objectCache[$key]);
112
+        $keys = array_keys($this->objectCache->getData());
113
+        $keyLength = strlen($key);
114
+        foreach ($keys as $existingKey) {
115
+            if (substr($existingKey, 0, $keyLength) === $key) {
116
+                unset($this->objectCache[$existingKey]);
117
+            }
118
+        }
119
+        unset($this->directoryCache[$key], $this->filesCache[$key]);
120
+    }
121
+
122
+    /**
123
+     * @param $key
124
+     * @return Result|boolean
125
+     */
126
+    private function headObject($key) {
127
+        if (!isset($this->objectCache[$key])) {
128
+            try {
129
+                $this->objectCache[$key] = $this->getConnection()->headObject([
130
+                    'Bucket' => $this->bucket,
131
+                    'Key' => $key
132
+                ]);
133
+            } catch (S3Exception $e) {
134
+                if ($e->getStatusCode() >= 500) {
135
+                    throw $e;
136
+                }
137
+                $this->objectCache[$key] = false;
138
+            }
139
+        }
140
+
141
+        return $this->objectCache[$key];
142
+    }
143
+
144
+    /**
145
+     * Return true if directory exists
146
+     *
147
+     * There are no folders in s3. A folder like structure could be archived
148
+     * by prefixing files with the folder name.
149
+     *
150
+     * Implementation from flysystem-aws-s3-v3:
151
+     * https://github.com/thephpleague/flysystem-aws-s3-v3/blob/8241e9cc5b28f981e0d24cdaf9867f14c7498ae4/src/AwsS3Adapter.php#L670-L694
152
+     *
153
+     * @param $path
154
+     * @return bool
155
+     * @throws \Exception
156
+     */
157
+    private function doesDirectoryExist($path) {
158
+        if (!isset($this->directoryCache[$path])) {
159
+            // Maybe this isn't an actual key, but a prefix.
160
+            // Do a prefix listing of objects to determine.
161
+            try {
162
+                $result = $this->getConnection()->listObjects([
163
+                    'Bucket' => $this->bucket,
164
+                    'Prefix' => rtrim($path, '/'),
165
+                    'MaxKeys' => 1,
166
+                    'Delimiter' => '/',
167
+                ]);
168
+
169
+                if ((isset($result['Contents'][0]['Key']) && $result['Contents'][0]['Key'] === rtrim($path, '/') . '/')
170
+                     || isset($result['CommonPrefixes'])) {
171
+                    $this->directoryCache[$path] = true;
172
+                } else {
173
+                    $this->directoryCache[$path] = false;
174
+                }
175
+            } catch (S3Exception $e) {
176
+                if ($e->getStatusCode() === 403) {
177
+                    $this->directoryCache[$path] = false;
178
+                }
179
+                throw $e;
180
+            }
181
+        }
182
+
183
+        return $this->directoryCache[$path];
184
+    }
185
+
186
+    /**
187
+     * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
188
+     * TODO Do this in a repair step. requires iterating over all users and loading the mount.json from their home
189
+     *
190
+     * @param array $params
191
+     */
192
+    public function updateLegacyId(array $params) {
193
+        $oldId = 'amazon::' . $params['key'] . md5($params['secret']);
194
+
195
+        // find by old id or bucket
196
+        $stmt = \OC::$server->getDatabaseConnection()->prepare(
197
+            'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
198
+        );
199
+        $stmt->execute([$oldId, $this->id]);
200
+        while ($row = $stmt->fetch()) {
201
+            $storages[$row['id']] = $row['numeric_id'];
202
+        }
203
+
204
+        if (isset($storages[$this->id]) && isset($storages[$oldId])) {
205
+            // if both ids exist, delete the old storage and corresponding filecache entries
206
+            \OC\Files\Cache\Storage::remove($oldId);
207
+        } elseif (isset($storages[$oldId])) {
208
+            // if only the old id exists do an update
209
+            $stmt = \OC::$server->getDatabaseConnection()->prepare(
210
+                'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
211
+            );
212
+            $stmt->execute([$this->id, $oldId]);
213
+        }
214
+        // only the bucket based id may exist, do nothing
215
+    }
216
+
217
+    /**
218
+     * Remove a file or folder
219
+     *
220
+     * @param string $path
221
+     * @return bool
222
+     */
223
+    protected function remove($path) {
224
+        // remember fileType to reduce http calls
225
+        $fileType = $this->filetype($path);
226
+        if ($fileType === 'dir') {
227
+            return $this->rmdir($path);
228
+        } elseif ($fileType === 'file') {
229
+            return $this->unlink($path);
230
+        } else {
231
+            return false;
232
+        }
233
+    }
234
+
235
+    public function mkdir($path) {
236
+        $path = $this->normalizePath($path);
237
+
238
+        if ($this->is_dir($path)) {
239
+            return false;
240
+        }
241
+
242
+        try {
243
+            $this->getConnection()->putObject([
244
+                'Bucket' => $this->bucket,
245
+                'Key' => $path . '/',
246
+                'Body' => '',
247
+                'ContentType' => 'httpd/unix-directory'
248
+            ]);
249
+            $this->testTimeout();
250
+        } catch (S3Exception $e) {
251
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
252
+            return false;
253
+        }
254
+
255
+        $this->invalidateCache($path);
256
+
257
+        return true;
258
+    }
259
+
260
+    public function file_exists($path) {
261
+        return $this->filetype($path) !== false;
262
+    }
263
+
264
+
265
+    public function rmdir($path) {
266
+        $path = $this->normalizePath($path);
267
+
268
+        if ($this->isRoot($path)) {
269
+            return $this->clearBucket();
270
+        }
271
+
272
+        if (!$this->file_exists($path)) {
273
+            return false;
274
+        }
275
+
276
+        $this->invalidateCache($path);
277
+        return $this->batchDelete($path);
278
+    }
279
+
280
+    protected function clearBucket() {
281
+        $this->clearCache();
282
+        try {
283
+            $this->getConnection()->clearBucket($this->bucket);
284
+            return true;
285
+            // clearBucket() is not working with Ceph, so if it fails we try the slower approach
286
+        } catch (\Exception $e) {
287
+            return $this->batchDelete();
288
+        }
289
+    }
290
+
291
+    private function batchDelete($path = null) {
292
+        $params = [
293
+            'Bucket' => $this->bucket
294
+        ];
295
+        if ($path !== null) {
296
+            $params['Prefix'] = $path . '/';
297
+        }
298
+        try {
299
+            $connection = $this->getConnection();
300
+            // Since there are no real directories on S3, we need
301
+            // to delete all objects prefixed with the path.
302
+            do {
303
+                // instead of the iterator, manually loop over the list ...
304
+                $objects = $connection->listObjects($params);
305
+                // ... so we can delete the files in batches
306
+                if (isset($objects['Contents'])) {
307
+                    $connection->deleteObjects([
308
+                        'Bucket' => $this->bucket,
309
+                        'Delete' => [
310
+                            'Objects' => $objects['Contents']
311
+                        ]
312
+                    ]);
313
+                    $this->testTimeout();
314
+                }
315
+                // we reached the end when the list is no longer truncated
316
+            } while ($objects['IsTruncated']);
317
+        } catch (S3Exception $e) {
318
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
319
+            return false;
320
+        }
321
+        return true;
322
+    }
323
+
324
+    public function opendir($path) {
325
+        $path = $this->normalizePath($path);
326
+
327
+        if ($this->isRoot($path)) {
328
+            $path = '';
329
+        } else {
330
+            $path .= '/';
331
+        }
332
+
333
+        try {
334
+            $files = [];
335
+            $results = $this->getConnection()->getPaginator('ListObjects', [
336
+                'Bucket' => $this->bucket,
337
+                'Delimiter' => '/',
338
+                'Prefix' => $path,
339
+            ]);
340
+
341
+            foreach ($results as $result) {
342
+                // sub folders
343
+                if (is_array($result['CommonPrefixes'])) {
344
+                    foreach ($result['CommonPrefixes'] as $prefix) {
345
+                        $directoryName = trim($prefix['Prefix'], '/');
346
+                        $files[] = substr($directoryName, strlen($path));
347
+                        $this->directoryCache[$directoryName] = true;
348
+                    }
349
+                }
350
+                if (is_array($result['Contents'])) {
351
+                    foreach ($result['Contents'] as $object) {
352
+                        if (isset($object['Key']) && $object['Key'] === $path) {
353
+                            // it's the directory itself, skip
354
+                            continue;
355
+                        }
356
+                        $file = basename(
357
+                            isset($object['Key']) ? $object['Key'] : $object['Prefix']
358
+                        );
359
+                        $files[] = $file;
360
+
361
+                        // store this information for later usage
362
+                        $this->filesCache[$path . $file] = [
363
+                            'ContentLength' => $object['Size'],
364
+                            'LastModified' => (string)$object['LastModified'],
365
+                        ];
366
+                    }
367
+                }
368
+            }
369
+
370
+            return IteratorDirectory::wrap($files);
371
+        } catch (S3Exception $e) {
372
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
373
+            return false;
374
+        }
375
+    }
376
+
377
+    public function stat($path) {
378
+        $path = $this->normalizePath($path);
379
+
380
+        try {
381
+            $stat = [];
382
+            if ($this->is_dir($path)) {
383
+                //folders don't really exist
384
+                $stat['size'] = -1; //unknown
385
+                $stat['mtime'] = time();
386
+                $cacheEntry = $this->getCache()->get($path);
387
+                if ($cacheEntry instanceof CacheEntry && $this->getMountOption('filesystem_check_changes', 1) !== 1) {
388
+                    $stat['size'] = $cacheEntry->getSize();
389
+                    $stat['mtime'] = $cacheEntry->getMTime();
390
+                }
391
+            } else {
392
+                $stat['size'] = $this->getContentLength($path);
393
+                $stat['mtime'] = strtotime($this->getLastModified($path));
394
+            }
395
+            $stat['atime'] = time();
396
+
397
+            return $stat;
398
+        } catch (S3Exception $e) {
399
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
400
+            return false;
401
+        }
402
+    }
403
+
404
+    /**
405
+     * Return content length for object
406
+     *
407
+     * When the information is already present (e.g. opendir has been called before)
408
+     * this value is return. Otherwise a headObject is emitted.
409
+     *
410
+     * @param $path
411
+     * @return int|mixed
412
+     */
413
+    private function getContentLength($path) {
414
+        if (isset($this->filesCache[$path])) {
415
+            return (int)$this->filesCache[$path]['ContentLength'];
416
+        }
417
+
418
+        $result = $this->headObject($path);
419
+        if (isset($result['ContentLength'])) {
420
+            return (int)$result['ContentLength'];
421
+        }
422
+
423
+        return 0;
424
+    }
425
+
426
+    /**
427
+     * Return last modified for object
428
+     *
429
+     * When the information is already present (e.g. opendir has been called before)
430
+     * this value is return. Otherwise a headObject is emitted.
431
+     *
432
+     * @param $path
433
+     * @return mixed|string
434
+     */
435
+    private function getLastModified($path) {
436
+        if (isset($this->filesCache[$path])) {
437
+            return $this->filesCache[$path]['LastModified'];
438
+        }
439
+
440
+        $result = $this->headObject($path);
441
+        if (isset($result['LastModified'])) {
442
+            return $result['LastModified'];
443
+        }
444
+
445
+        return 'now';
446
+    }
447
+
448
+    public function is_dir($path) {
449
+        $path = $this->normalizePath($path);
450
+
451
+        if (isset($this->filesCache[$path])) {
452
+            return false;
453
+        }
454
+
455
+        try {
456
+            return $this->isRoot($path) || $this->doesDirectoryExist($path);
457
+        } catch (S3Exception $e) {
458
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
459
+            return false;
460
+        }
461
+    }
462
+
463
+    public function filetype($path) {
464
+        $path = $this->normalizePath($path);
465
+
466
+        if ($this->isRoot($path)) {
467
+            return 'dir';
468
+        }
469
+
470
+        try {
471
+            if (isset($this->filesCache[$path]) || $this->headObject($path)) {
472
+                return 'file';
473
+            }
474
+            if ($this->doesDirectoryExist($path)) {
475
+                return 'dir';
476
+            }
477
+        } catch (S3Exception $e) {
478
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
479
+            return false;
480
+        }
481
+
482
+        return false;
483
+    }
484
+
485
+    public function getPermissions($path) {
486
+        $type = $this->filetype($path);
487
+        if (!$type) {
488
+            return 0;
489
+        }
490
+        return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
491
+    }
492
+
493
+    public function unlink($path) {
494
+        $path = $this->normalizePath($path);
495
+
496
+        if ($this->is_dir($path)) {
497
+            return $this->rmdir($path);
498
+        }
499
+
500
+        try {
501
+            $this->deleteObject($path);
502
+            $this->invalidateCache($path);
503
+        } catch (S3Exception $e) {
504
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
505
+            return false;
506
+        }
507
+
508
+        return true;
509
+    }
510
+
511
+    public function fopen($path, $mode) {
512
+        $path = $this->normalizePath($path);
513
+
514
+        switch ($mode) {
515
+            case 'r':
516
+            case 'rb':
517
+                // Don't try to fetch empty files
518
+                $stat = $this->stat($path);
519
+                if (is_array($stat) && isset($stat['size']) && $stat['size'] === 0) {
520
+                    return fopen('php://memory', $mode);
521
+                }
522
+
523
+                try {
524
+                    return $this->readObject($path);
525
+                } catch (S3Exception $e) {
526
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
527
+                    return false;
528
+                }
529
+            case 'w':
530
+            case 'wb':
531
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
532
+
533
+                $handle = fopen($tmpFile, 'w');
534
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
535
+                    $this->writeBack($tmpFile, $path);
536
+                });
537
+            case 'a':
538
+            case 'ab':
539
+            case 'r+':
540
+            case 'w+':
541
+            case 'wb+':
542
+            case 'a+':
543
+            case 'x':
544
+            case 'x+':
545
+            case 'c':
546
+            case 'c+':
547
+                if (strrpos($path, '.') !== false) {
548
+                    $ext = substr($path, strrpos($path, '.'));
549
+                } else {
550
+                    $ext = '';
551
+                }
552
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
553
+                if ($this->file_exists($path)) {
554
+                    $source = $this->readObject($path);
555
+                    file_put_contents($tmpFile, $source);
556
+                }
557
+
558
+                $handle = fopen($tmpFile, $mode);
559
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
560
+                    $this->writeBack($tmpFile, $path);
561
+                });
562
+        }
563
+        return false;
564
+    }
565
+
566
+    public function touch($path, $mtime = null) {
567
+        if (is_null($mtime)) {
568
+            $mtime = time();
569
+        }
570
+        $metadata = [
571
+            'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
572
+        ];
573
+
574
+        try {
575
+            if (!$this->file_exists($path)) {
576
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
577
+                $this->getConnection()->putObject([
578
+                    'Bucket' => $this->bucket,
579
+                    'Key' => $this->cleanKey($path),
580
+                    'Metadata' => $metadata,
581
+                    'Body' => '',
582
+                    'ContentType' => $mimeType,
583
+                    'MetadataDirective' => 'REPLACE',
584
+                ]);
585
+                $this->testTimeout();
586
+            }
587
+        } catch (S3Exception $e) {
588
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
589
+            return false;
590
+        }
591
+
592
+        $this->invalidateCache($path);
593
+        return true;
594
+    }
595
+
596
+    public function copy($path1, $path2) {
597
+        $path1 = $this->normalizePath($path1);
598
+        $path2 = $this->normalizePath($path2);
599
+
600
+        if ($this->is_file($path1)) {
601
+            try {
602
+                $this->getConnection()->copyObject([
603
+                    'Bucket' => $this->bucket,
604
+                    'Key' => $this->cleanKey($path2),
605
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
606
+                ]);
607
+                $this->testTimeout();
608
+            } catch (S3Exception $e) {
609
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
610
+                return false;
611
+            }
612
+        } else {
613
+            $this->remove($path2);
614
+
615
+            try {
616
+                $this->getConnection()->copyObject([
617
+                    'Bucket' => $this->bucket,
618
+                    'Key' => $path2 . '/',
619
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
620
+                ]);
621
+                $this->testTimeout();
622
+            } catch (S3Exception $e) {
623
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
624
+                return false;
625
+            }
626
+
627
+            $dh = $this->opendir($path1);
628
+            if (is_resource($dh)) {
629
+                while (($file = readdir($dh)) !== false) {
630
+                    if (\OC\Files\Filesystem::isIgnoredDir($file)) {
631
+                        continue;
632
+                    }
633
+
634
+                    $source = $path1 . '/' . $file;
635
+                    $target = $path2 . '/' . $file;
636
+                    $this->copy($source, $target);
637
+                }
638
+            }
639
+        }
640
+
641
+        $this->invalidateCache($path2);
642
+
643
+        return true;
644
+    }
645
+
646
+    public function rename($path1, $path2) {
647
+        $path1 = $this->normalizePath($path1);
648
+        $path2 = $this->normalizePath($path2);
649
+
650
+        if ($this->is_file($path1)) {
651
+            if ($this->copy($path1, $path2) === false) {
652
+                return false;
653
+            }
654
+
655
+            if ($this->unlink($path1) === false) {
656
+                $this->unlink($path2);
657
+                return false;
658
+            }
659
+        } else {
660
+            if ($this->copy($path1, $path2) === false) {
661
+                return false;
662
+            }
663
+
664
+            if ($this->rmdir($path1) === false) {
665
+                $this->rmdir($path2);
666
+                return false;
667
+            }
668
+        }
669
+
670
+        return true;
671
+    }
672
+
673
+    public function test() {
674
+        $this->getConnection()->headBucket([
675
+            'Bucket' => $this->bucket
676
+        ]);
677
+        return true;
678
+    }
679
+
680
+    public function getId() {
681
+        return $this->id;
682
+    }
683
+
684
+    public function writeBack($tmpFile, $path) {
685
+        try {
686
+            $source = fopen($tmpFile, 'r');
687
+            $this->writeObject($path, $source);
688
+            $this->invalidateCache($path);
689
+
690
+            unlink($tmpFile);
691
+            return true;
692
+        } catch (S3Exception $e) {
693
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
694
+            return false;
695
+        }
696
+    }
697
+
698
+    /**
699
+     * check if curl is installed
700
+     */
701
+    public static function checkDependencies() {
702
+        return true;
703
+    }
704 704
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_App.php 2 patches
Indentation   +1112 added lines, -1112 removed lines patch added patch discarded remove patch
@@ -68,1116 +68,1116 @@
 block discarded – undo
68 68
  * upgrading and removing apps.
69 69
  */
70 70
 class OC_App {
71
-	private static $adminForms = [];
72
-	private static $personalForms = [];
73
-	private static $appTypes = [];
74
-	private static $loadedApps = [];
75
-	private static $altLogin = [];
76
-	private static $alreadyRegistered = [];
77
-	public const supportedApp = 300;
78
-	public const officialApp = 200;
79
-
80
-	/**
81
-	 * clean the appId
82
-	 *
83
-	 * @psalm-taint-escape file
84
-	 * @psalm-taint-escape include
85
-	 *
86
-	 * @param string $app AppId that needs to be cleaned
87
-	 * @return string
88
-	 */
89
-	public static function cleanAppId(string $app): string {
90
-		return str_replace(['\0', '/', '\\', '..'], '', $app);
91
-	}
92
-
93
-	/**
94
-	 * Check if an app is loaded
95
-	 *
96
-	 * @param string $app
97
-	 * @return bool
98
-	 */
99
-	public static function isAppLoaded(string $app): bool {
100
-		return isset(self::$loadedApps[$app]);
101
-	}
102
-
103
-	/**
104
-	 * loads all apps
105
-	 *
106
-	 * @param string[] $types
107
-	 * @return bool
108
-	 *
109
-	 * This function walks through the ownCloud directory and loads all apps
110
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
111
-	 * exists.
112
-	 *
113
-	 * if $types is set to non-empty array, only apps of those types will be loaded
114
-	 */
115
-	public static function loadApps(array $types = []): bool {
116
-		if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
117
-			return false;
118
-		}
119
-		// Load the enabled apps here
120
-		$apps = self::getEnabledApps();
121
-
122
-		// Add each apps' folder as allowed class path
123
-		foreach ($apps as $app) {
124
-			// If the app is already loaded then autoloading it makes no sense
125
-			if (!isset(self::$loadedApps[$app])) {
126
-				$path = self::getAppPath($app);
127
-				if ($path !== false) {
128
-					self::registerAutoloading($app, $path);
129
-				}
130
-			}
131
-		}
132
-
133
-		// prevent app.php from printing output
134
-		ob_start();
135
-		foreach ($apps as $app) {
136
-			if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) {
137
-				self::loadApp($app);
138
-			}
139
-		}
140
-		ob_end_clean();
141
-
142
-		return true;
143
-	}
144
-
145
-	/**
146
-	 * load a single app
147
-	 *
148
-	 * @param string $app
149
-	 * @throws Exception
150
-	 */
151
-	public static function loadApp(string $app) {
152
-		self::$loadedApps[$app] = true;
153
-		$appPath = self::getAppPath($app);
154
-		if ($appPath === false) {
155
-			return;
156
-		}
157
-
158
-		// in case someone calls loadApp() directly
159
-		self::registerAutoloading($app, $appPath);
160
-
161
-		/** @var Coordinator $coordinator */
162
-		$coordinator = \OC::$server->query(Coordinator::class);
163
-		$isBootable = $coordinator->isBootable($app);
164
-
165
-		$hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
166
-
167
-		if ($isBootable && $hasAppPhpFile) {
168
-			\OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
169
-				'app' => $app,
170
-			]);
171
-		} elseif ($hasAppPhpFile) {
172
-			\OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
173
-				'app' => $app,
174
-			]);
175
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
176
-			try {
177
-				self::requireAppFile($app);
178
-			} catch (Throwable $ex) {
179
-				if ($ex instanceof ServerNotAvailableException) {
180
-					throw $ex;
181
-				}
182
-				if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
183
-					\OC::$server->getLogger()->logException($ex, [
184
-						'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
185
-					]);
186
-
187
-					// Only disable apps which are not shipped and that are not authentication apps
188
-					\OC::$server->getAppManager()->disableApp($app, true);
189
-				} else {
190
-					\OC::$server->getLogger()->logException($ex, [
191
-						'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
192
-					]);
193
-				}
194
-			}
195
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
196
-		}
197
-		$coordinator->bootApp($app);
198
-
199
-		$info = self::getAppInfo($app);
200
-		if (!empty($info['activity']['filters'])) {
201
-			foreach ($info['activity']['filters'] as $filter) {
202
-				\OC::$server->getActivityManager()->registerFilter($filter);
203
-			}
204
-		}
205
-		if (!empty($info['activity']['settings'])) {
206
-			foreach ($info['activity']['settings'] as $setting) {
207
-				\OC::$server->getActivityManager()->registerSetting($setting);
208
-			}
209
-		}
210
-		if (!empty($info['activity']['providers'])) {
211
-			foreach ($info['activity']['providers'] as $provider) {
212
-				\OC::$server->getActivityManager()->registerProvider($provider);
213
-			}
214
-		}
215
-
216
-		if (!empty($info['settings']['admin'])) {
217
-			foreach ($info['settings']['admin'] as $setting) {
218
-				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
219
-			}
220
-		}
221
-		if (!empty($info['settings']['admin-section'])) {
222
-			foreach ($info['settings']['admin-section'] as $section) {
223
-				\OC::$server->getSettingsManager()->registerSection('admin', $section);
224
-			}
225
-		}
226
-		if (!empty($info['settings']['personal'])) {
227
-			foreach ($info['settings']['personal'] as $setting) {
228
-				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
229
-			}
230
-		}
231
-		if (!empty($info['settings']['personal-section'])) {
232
-			foreach ($info['settings']['personal-section'] as $section) {
233
-				\OC::$server->getSettingsManager()->registerSection('personal', $section);
234
-			}
235
-		}
236
-
237
-		if (!empty($info['collaboration']['plugins'])) {
238
-			// deal with one or many plugin entries
239
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
240
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
241
-			foreach ($plugins as $plugin) {
242
-				if ($plugin['@attributes']['type'] === 'collaborator-search') {
243
-					$pluginInfo = [
244
-						'shareType' => $plugin['@attributes']['share-type'],
245
-						'class' => $plugin['@value'],
246
-					];
247
-					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
248
-				} elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
249
-					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
250
-				}
251
-			}
252
-		}
253
-	}
254
-
255
-	/**
256
-	 * @internal
257
-	 * @param string $app
258
-	 * @param string $path
259
-	 * @param bool $force
260
-	 */
261
-	public static function registerAutoloading(string $app, string $path, bool $force = false) {
262
-		$key = $app . '-' . $path;
263
-		if (!$force && isset(self::$alreadyRegistered[$key])) {
264
-			return;
265
-		}
266
-
267
-		self::$alreadyRegistered[$key] = true;
268
-
269
-		// Register on PSR-4 composer autoloader
270
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
271
-		\OC::$server->registerNamespace($app, $appNamespace);
272
-
273
-		if (file_exists($path . '/composer/autoload.php')) {
274
-			require_once $path . '/composer/autoload.php';
275
-		} else {
276
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
277
-			// Register on legacy autoloader
278
-			\OC::$loader->addValidRoot($path);
279
-		}
280
-
281
-		// Register Test namespace only when testing
282
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
283
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
284
-		}
285
-	}
286
-
287
-	/**
288
-	 * Load app.php from the given app
289
-	 *
290
-	 * @param string $app app name
291
-	 * @throws Error
292
-	 */
293
-	private static function requireAppFile(string $app) {
294
-		// encapsulated here to avoid variable scope conflicts
295
-		require_once $app . '/appinfo/app.php';
296
-	}
297
-
298
-	/**
299
-	 * check if an app is of a specific type
300
-	 *
301
-	 * @param string $app
302
-	 * @param array $types
303
-	 * @return bool
304
-	 */
305
-	public static function isType(string $app, array $types): bool {
306
-		$appTypes = self::getAppTypes($app);
307
-		foreach ($types as $type) {
308
-			if (array_search($type, $appTypes) !== false) {
309
-				return true;
310
-			}
311
-		}
312
-		return false;
313
-	}
314
-
315
-	/**
316
-	 * get the types of an app
317
-	 *
318
-	 * @param string $app
319
-	 * @return array
320
-	 */
321
-	private static function getAppTypes(string $app): array {
322
-		//load the cache
323
-		if (count(self::$appTypes) == 0) {
324
-			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
325
-		}
326
-
327
-		if (isset(self::$appTypes[$app])) {
328
-			return explode(',', self::$appTypes[$app]);
329
-		}
330
-
331
-		return [];
332
-	}
333
-
334
-	/**
335
-	 * read app types from info.xml and cache them in the database
336
-	 */
337
-	public static function setAppTypes(string $app) {
338
-		$appManager = \OC::$server->getAppManager();
339
-		$appData = $appManager->getAppInfo($app);
340
-		if (!is_array($appData)) {
341
-			return;
342
-		}
343
-
344
-		if (isset($appData['types'])) {
345
-			$appTypes = implode(',', $appData['types']);
346
-		} else {
347
-			$appTypes = '';
348
-			$appData['types'] = [];
349
-		}
350
-
351
-		$config = \OC::$server->getConfig();
352
-		$config->setAppValue($app, 'types', $appTypes);
353
-
354
-		if ($appManager->hasProtectedAppType($appData['types'])) {
355
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
356
-			if ($enabled !== 'yes' && $enabled !== 'no') {
357
-				$config->setAppValue($app, 'enabled', 'yes');
358
-			}
359
-		}
360
-	}
361
-
362
-	/**
363
-	 * Returns apps enabled for the current user.
364
-	 *
365
-	 * @param bool $forceRefresh whether to refresh the cache
366
-	 * @param bool $all whether to return apps for all users, not only the
367
-	 * currently logged in one
368
-	 * @return string[]
369
-	 */
370
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
371
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
372
-			return [];
373
-		}
374
-		// in incognito mode or when logged out, $user will be false,
375
-		// which is also the case during an upgrade
376
-		$appManager = \OC::$server->getAppManager();
377
-		if ($all) {
378
-			$user = null;
379
-		} else {
380
-			$user = \OC::$server->getUserSession()->getUser();
381
-		}
382
-
383
-		if (is_null($user)) {
384
-			$apps = $appManager->getInstalledApps();
385
-		} else {
386
-			$apps = $appManager->getEnabledAppsForUser($user);
387
-		}
388
-		$apps = array_filter($apps, function ($app) {
389
-			return $app !== 'files';//we add this manually
390
-		});
391
-		sort($apps);
392
-		array_unshift($apps, 'files');
393
-		return $apps;
394
-	}
395
-
396
-	/**
397
-	 * checks whether or not an app is enabled
398
-	 *
399
-	 * @param string $app app
400
-	 * @return bool
401
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
402
-	 *
403
-	 * This function checks whether or not an app is enabled.
404
-	 */
405
-	public static function isEnabled(string $app): bool {
406
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
407
-	}
408
-
409
-	/**
410
-	 * enables an app
411
-	 *
412
-	 * @param string $appId
413
-	 * @param array $groups (optional) when set, only these groups will have access to the app
414
-	 * @throws \Exception
415
-	 * @return void
416
-	 *
417
-	 * This function set an app as enabled in appconfig.
418
-	 */
419
-	public function enable(string $appId,
420
-						   array $groups = []) {
421
-
422
-		// Check if app is already downloaded
423
-		/** @var Installer $installer */
424
-		$installer = \OC::$server->query(Installer::class);
425
-		$isDownloaded = $installer->isDownloaded($appId);
426
-
427
-		if (!$isDownloaded) {
428
-			$installer->downloadApp($appId);
429
-		}
430
-
431
-		$installer->installApp($appId);
432
-
433
-		$appManager = \OC::$server->getAppManager();
434
-		if ($groups !== []) {
435
-			$groupManager = \OC::$server->getGroupManager();
436
-			$groupsList = [];
437
-			foreach ($groups as $group) {
438
-				$groupItem = $groupManager->get($group);
439
-				if ($groupItem instanceof \OCP\IGroup) {
440
-					$groupsList[] = $groupManager->get($group);
441
-				}
442
-			}
443
-			$appManager->enableAppForGroups($appId, $groupsList);
444
-		} else {
445
-			$appManager->enableApp($appId);
446
-		}
447
-	}
448
-
449
-	/**
450
-	 * Get the path where to install apps
451
-	 *
452
-	 * @return string|false
453
-	 */
454
-	public static function getInstallPath() {
455
-		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
456
-			return false;
457
-		}
458
-
459
-		foreach (OC::$APPSROOTS as $dir) {
460
-			if (isset($dir['writable']) && $dir['writable'] === true) {
461
-				return $dir['path'];
462
-			}
463
-		}
464
-
465
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
466
-		return null;
467
-	}
468
-
469
-
470
-	/**
471
-	 * search for an app in all app-directories
472
-	 *
473
-	 * @param string $appId
474
-	 * @return false|string
475
-	 */
476
-	public static function findAppInDirectories(string $appId) {
477
-		$sanitizedAppId = self::cleanAppId($appId);
478
-		if ($sanitizedAppId !== $appId) {
479
-			return false;
480
-		}
481
-		static $app_dir = [];
482
-
483
-		if (isset($app_dir[$appId])) {
484
-			return $app_dir[$appId];
485
-		}
486
-
487
-		$possibleApps = [];
488
-		foreach (OC::$APPSROOTS as $dir) {
489
-			if (file_exists($dir['path'] . '/' . $appId)) {
490
-				$possibleApps[] = $dir;
491
-			}
492
-		}
493
-
494
-		if (empty($possibleApps)) {
495
-			return false;
496
-		} elseif (count($possibleApps) === 1) {
497
-			$dir = array_shift($possibleApps);
498
-			$app_dir[$appId] = $dir;
499
-			return $dir;
500
-		} else {
501
-			$versionToLoad = [];
502
-			foreach ($possibleApps as $possibleApp) {
503
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
504
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
505
-					$versionToLoad = [
506
-						'dir' => $possibleApp,
507
-						'version' => $version,
508
-					];
509
-				}
510
-			}
511
-			$app_dir[$appId] = $versionToLoad['dir'];
512
-			return $versionToLoad['dir'];
513
-			//TODO - write test
514
-		}
515
-	}
516
-
517
-	/**
518
-	 * Get the directory for the given app.
519
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
520
-	 *
521
-	 * @psalm-taint-specialize
522
-	 *
523
-	 * @param string $appId
524
-	 * @return string|false
525
-	 * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
526
-	 */
527
-	public static function getAppPath(string $appId) {
528
-		if ($appId === null || trim($appId) === '') {
529
-			return false;
530
-		}
531
-
532
-		if (($dir = self::findAppInDirectories($appId)) != false) {
533
-			return $dir['path'] . '/' . $appId;
534
-		}
535
-		return false;
536
-	}
537
-
538
-	/**
539
-	 * Get the path for the given app on the access
540
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
541
-	 *
542
-	 * @param string $appId
543
-	 * @return string|false
544
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
545
-	 */
546
-	public static function getAppWebPath(string $appId) {
547
-		if (($dir = self::findAppInDirectories($appId)) != false) {
548
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
549
-		}
550
-		return false;
551
-	}
552
-
553
-	/**
554
-	 * get the last version of the app from appinfo/info.xml
555
-	 *
556
-	 * @param string $appId
557
-	 * @param bool $useCache
558
-	 * @return string
559
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
560
-	 */
561
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
562
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
563
-	}
564
-
565
-	/**
566
-	 * get app's version based on it's path
567
-	 *
568
-	 * @param string $path
569
-	 * @return string
570
-	 */
571
-	public static function getAppVersionByPath(string $path): string {
572
-		$infoFile = $path . '/appinfo/info.xml';
573
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
574
-		return isset($appData['version']) ? $appData['version'] : '';
575
-	}
576
-
577
-
578
-	/**
579
-	 * Read all app metadata from the info.xml file
580
-	 *
581
-	 * @param string $appId id of the app or the path of the info.xml file
582
-	 * @param bool $path
583
-	 * @param string $lang
584
-	 * @return array|null
585
-	 * @note all data is read from info.xml, not just pre-defined fields
586
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
587
-	 */
588
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
589
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
590
-	}
591
-
592
-	/**
593
-	 * Returns the navigation
594
-	 *
595
-	 * @return array
596
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
597
-	 *
598
-	 * This function returns an array containing all entries added. The
599
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
600
-	 * given for each app the following keys exist:
601
-	 *   - active: boolean, signals if the user is on this navigation entry
602
-	 */
603
-	public static function getNavigation(): array {
604
-		return OC::$server->getNavigationManager()->getAll();
605
-	}
606
-
607
-	/**
608
-	 * Returns the Settings Navigation
609
-	 *
610
-	 * @return string[]
611
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
612
-	 *
613
-	 * This function returns an array containing all settings pages added. The
614
-	 * entries are sorted by the key 'order' ascending.
615
-	 */
616
-	public static function getSettingsNavigation(): array {
617
-		return OC::$server->getNavigationManager()->getAll('settings');
618
-	}
619
-
620
-	/**
621
-	 * get the id of loaded app
622
-	 *
623
-	 * @return string
624
-	 */
625
-	public static function getCurrentApp(): string {
626
-		$request = \OC::$server->getRequest();
627
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
628
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
629
-		if (empty($topFolder)) {
630
-			$path_info = $request->getPathInfo();
631
-			if ($path_info) {
632
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
633
-			}
634
-		}
635
-		if ($topFolder == 'apps') {
636
-			$length = strlen($topFolder);
637
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
638
-		} else {
639
-			return $topFolder;
640
-		}
641
-	}
642
-
643
-	/**
644
-	 * @param string $type
645
-	 * @return array
646
-	 */
647
-	public static function getForms(string $type): array {
648
-		$forms = [];
649
-		switch ($type) {
650
-			case 'admin':
651
-				$source = self::$adminForms;
652
-				break;
653
-			case 'personal':
654
-				$source = self::$personalForms;
655
-				break;
656
-			default:
657
-				return [];
658
-		}
659
-		foreach ($source as $form) {
660
-			$forms[] = include $form;
661
-		}
662
-		return $forms;
663
-	}
664
-
665
-	/**
666
-	 * register an admin form to be shown
667
-	 *
668
-	 * @param string $app
669
-	 * @param string $page
670
-	 */
671
-	public static function registerAdmin(string $app, string $page) {
672
-		self::$adminForms[] = $app . '/' . $page . '.php';
673
-	}
674
-
675
-	/**
676
-	 * register a personal form to be shown
677
-	 * @param string $app
678
-	 * @param string $page
679
-	 */
680
-	public static function registerPersonal(string $app, string $page) {
681
-		self::$personalForms[] = $app . '/' . $page . '.php';
682
-	}
683
-
684
-	/**
685
-	 * @param array $entry
686
-	 * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
687
-	 */
688
-	public static function registerLogIn(array $entry) {
689
-		\OC::$server->getLogger()->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
690
-		self::$altLogin[] = $entry;
691
-	}
692
-
693
-	/**
694
-	 * @return array
695
-	 */
696
-	public static function getAlternativeLogIns(): array {
697
-		/** @var Coordinator $bootstrapCoordinator */
698
-		$bootstrapCoordinator = \OC::$server->query(Coordinator::class);
699
-
700
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
701
-			if (!in_array(IAlternativeLogin::class, class_implements($registration['class']), true)) {
702
-				\OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
703
-					'option' => $registration['class'],
704
-					'interface' => IAlternativeLogin::class,
705
-					'app' => $registration['app'],
706
-				]);
707
-				continue;
708
-			}
709
-
710
-			try {
711
-				/** @var IAlternativeLogin $provider */
712
-				$provider = \OC::$server->query($registration['class']);
713
-			} catch (QueryException $e) {
714
-				\OC::$server->getLogger()->logException($e, [
715
-					'message' => 'Alternative login option {option} can not be initialised.',
716
-					'option' => $registration['class'],
717
-					'app' => $registration['app'],
718
-				]);
719
-			}
720
-
721
-			try {
722
-				$provider->load();
723
-
724
-				self::$altLogin[] = [
725
-					'name' => $provider->getLabel(),
726
-					'href' => $provider->getLink(),
727
-					'style' => $provider->getClass(),
728
-				];
729
-			} catch (Throwable $e) {
730
-				\OC::$server->getLogger()->logException($e, [
731
-					'message' => 'Alternative login option {option} had an error while loading.',
732
-					'option' => $registration['class'],
733
-					'app' => $registration['app'],
734
-				]);
735
-			}
736
-		}
737
-
738
-		return self::$altLogin;
739
-	}
740
-
741
-	/**
742
-	 * get a list of all apps in the apps folder
743
-	 *
744
-	 * @return string[] an array of app names (string IDs)
745
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
746
-	 */
747
-	public static function getAllApps(): array {
748
-		$apps = [];
749
-
750
-		foreach (OC::$APPSROOTS as $apps_dir) {
751
-			if (!is_readable($apps_dir['path'])) {
752
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
753
-				continue;
754
-			}
755
-			$dh = opendir($apps_dir['path']);
756
-
757
-			if (is_resource($dh)) {
758
-				while (($file = readdir($dh)) !== false) {
759
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
760
-						$apps[] = $file;
761
-					}
762
-				}
763
-			}
764
-		}
765
-
766
-		$apps = array_unique($apps);
767
-
768
-		return $apps;
769
-	}
770
-
771
-	/**
772
-	 * List all apps, this is used in apps.php
773
-	 *
774
-	 * @return array
775
-	 */
776
-	public function listAllApps(): array {
777
-		$installedApps = OC_App::getAllApps();
778
-
779
-		$appManager = \OC::$server->getAppManager();
780
-		//we don't want to show configuration for these
781
-		$blacklist = $appManager->getAlwaysEnabledApps();
782
-		$appList = [];
783
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
784
-		$urlGenerator = \OC::$server->getURLGenerator();
785
-		/** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
786
-		$subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
787
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
788
-
789
-		foreach ($installedApps as $app) {
790
-			if (array_search($app, $blacklist) === false) {
791
-				$info = OC_App::getAppInfo($app, false, $langCode);
792
-				if (!is_array($info)) {
793
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
794
-					continue;
795
-				}
796
-
797
-				if (!isset($info['name'])) {
798
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
799
-					continue;
800
-				}
801
-
802
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
803
-				$info['groups'] = null;
804
-				if ($enabled === 'yes') {
805
-					$active = true;
806
-				} elseif ($enabled === 'no') {
807
-					$active = false;
808
-				} else {
809
-					$active = true;
810
-					$info['groups'] = $enabled;
811
-				}
812
-
813
-				$info['active'] = $active;
814
-
815
-				if ($appManager->isShipped($app)) {
816
-					$info['internal'] = true;
817
-					$info['level'] = self::officialApp;
818
-					$info['removable'] = false;
819
-				} else {
820
-					$info['internal'] = false;
821
-					$info['removable'] = true;
822
-				}
823
-
824
-				if (in_array($app, $supportedApps)) {
825
-					$info['level'] = self::supportedApp;
826
-				}
827
-
828
-				$appPath = self::getAppPath($app);
829
-				if ($appPath !== false) {
830
-					$appIcon = $appPath . '/img/' . $app . '.svg';
831
-					if (file_exists($appIcon)) {
832
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
833
-						$info['previewAsIcon'] = true;
834
-					} else {
835
-						$appIcon = $appPath . '/img/app.svg';
836
-						if (file_exists($appIcon)) {
837
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
838
-							$info['previewAsIcon'] = true;
839
-						}
840
-					}
841
-				}
842
-				// fix documentation
843
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
844
-					foreach ($info['documentation'] as $key => $url) {
845
-						// If it is not an absolute URL we assume it is a key
846
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
847
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
848
-							$url = $urlGenerator->linkToDocs($url);
849
-						}
850
-
851
-						$info['documentation'][$key] = $url;
852
-					}
853
-				}
854
-
855
-				$info['version'] = OC_App::getAppVersion($app);
856
-				$appList[] = $info;
857
-			}
858
-		}
859
-
860
-		return $appList;
861
-	}
862
-
863
-	public static function shouldUpgrade(string $app): bool {
864
-		$versions = self::getAppVersions();
865
-		$currentVersion = OC_App::getAppVersion($app);
866
-		if ($currentVersion && isset($versions[$app])) {
867
-			$installedVersion = $versions[$app];
868
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
869
-				return true;
870
-			}
871
-		}
872
-		return false;
873
-	}
874
-
875
-	/**
876
-	 * Adjust the number of version parts of $version1 to match
877
-	 * the number of version parts of $version2.
878
-	 *
879
-	 * @param string $version1 version to adjust
880
-	 * @param string $version2 version to take the number of parts from
881
-	 * @return string shortened $version1
882
-	 */
883
-	private static function adjustVersionParts(string $version1, string $version2): string {
884
-		$version1 = explode('.', $version1);
885
-		$version2 = explode('.', $version2);
886
-		// reduce $version1 to match the number of parts in $version2
887
-		while (count($version1) > count($version2)) {
888
-			array_pop($version1);
889
-		}
890
-		// if $version1 does not have enough parts, add some
891
-		while (count($version1) < count($version2)) {
892
-			$version1[] = '0';
893
-		}
894
-		return implode('.', $version1);
895
-	}
896
-
897
-	/**
898
-	 * Check whether the current ownCloud version matches the given
899
-	 * application's version requirements.
900
-	 *
901
-	 * The comparison is made based on the number of parts that the
902
-	 * app info version has. For example for ownCloud 6.0.3 if the
903
-	 * app info version is expecting version 6.0, the comparison is
904
-	 * made on the first two parts of the ownCloud version.
905
-	 * This means that it's possible to specify "requiremin" => 6
906
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
907
-	 *
908
-	 * @param string $ocVersion ownCloud version to check against
909
-	 * @param array $appInfo app info (from xml)
910
-	 *
911
-	 * @return boolean true if compatible, otherwise false
912
-	 */
913
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
914
-		$requireMin = '';
915
-		$requireMax = '';
916
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
917
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
918
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
919
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
920
-		} elseif (isset($appInfo['requiremin'])) {
921
-			$requireMin = $appInfo['requiremin'];
922
-		} elseif (isset($appInfo['require'])) {
923
-			$requireMin = $appInfo['require'];
924
-		}
925
-
926
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
927
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
928
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
929
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
930
-		} elseif (isset($appInfo['requiremax'])) {
931
-			$requireMax = $appInfo['requiremax'];
932
-		}
933
-
934
-		if (!empty($requireMin)
935
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
936
-		) {
937
-			return false;
938
-		}
939
-
940
-		if (!$ignoreMax && !empty($requireMax)
941
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
942
-		) {
943
-			return false;
944
-		}
945
-
946
-		return true;
947
-	}
948
-
949
-	/**
950
-	 * get the installed version of all apps
951
-	 */
952
-	public static function getAppVersions() {
953
-		static $versions;
954
-
955
-		if (!$versions) {
956
-			$appConfig = \OC::$server->getAppConfig();
957
-			$versions = $appConfig->getValues(false, 'installed_version');
958
-		}
959
-		return $versions;
960
-	}
961
-
962
-	/**
963
-	 * update the database for the app and call the update script
964
-	 *
965
-	 * @param string $appId
966
-	 * @return bool
967
-	 */
968
-	public static function updateApp(string $appId): bool {
969
-		$appPath = self::getAppPath($appId);
970
-		if ($appPath === false) {
971
-			return false;
972
-		}
973
-
974
-		\OC::$server->getAppManager()->clearAppsCache();
975
-		$appData = self::getAppInfo($appId);
976
-
977
-		self::registerAutoloading($appId, $appPath, true);
978
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
979
-
980
-		if (file_exists($appPath . '/appinfo/database.xml')) {
981
-			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
982
-		} else {
983
-			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
984
-			$ms->migrate();
985
-		}
986
-
987
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
988
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
989
-		// update appversion in app manager
990
-		\OC::$server->getAppManager()->clearAppsCache();
991
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
992
-
993
-		self::setupBackgroundJobs($appData['background-jobs']);
994
-
995
-		//set remote/public handlers
996
-		if (array_key_exists('ocsid', $appData)) {
997
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
998
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
999
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1000
-		}
1001
-		foreach ($appData['remote'] as $name => $path) {
1002
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1003
-		}
1004
-		foreach ($appData['public'] as $name => $path) {
1005
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1006
-		}
1007
-
1008
-		self::setAppTypes($appId);
1009
-
1010
-		$version = \OC_App::getAppVersion($appId);
1011
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
1012
-
1013
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1014
-			ManagerEvent::EVENT_APP_UPDATE, $appId
1015
-		));
1016
-
1017
-		return true;
1018
-	}
1019
-
1020
-	/**
1021
-	 * @param string $appId
1022
-	 * @param string[] $steps
1023
-	 * @throws \OC\NeedsUpdateException
1024
-	 */
1025
-	public static function executeRepairSteps(string $appId, array $steps) {
1026
-		if (empty($steps)) {
1027
-			return;
1028
-		}
1029
-		// load the app
1030
-		self::loadApp($appId);
1031
-
1032
-		$dispatcher = OC::$server->getEventDispatcher();
1033
-
1034
-		// load the steps
1035
-		$r = new Repair([], $dispatcher);
1036
-		foreach ($steps as $step) {
1037
-			try {
1038
-				$r->addStep($step);
1039
-			} catch (Exception $ex) {
1040
-				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1041
-				\OC::$server->getLogger()->logException($ex);
1042
-			}
1043
-		}
1044
-		// run the steps
1045
-		$r->run();
1046
-	}
1047
-
1048
-	public static function setupBackgroundJobs(array $jobs) {
1049
-		$queue = \OC::$server->getJobList();
1050
-		foreach ($jobs as $job) {
1051
-			$queue->add($job);
1052
-		}
1053
-	}
1054
-
1055
-	/**
1056
-	 * @param string $appId
1057
-	 * @param string[] $steps
1058
-	 */
1059
-	private static function setupLiveMigrations(string $appId, array $steps) {
1060
-		$queue = \OC::$server->getJobList();
1061
-		foreach ($steps as $step) {
1062
-			$queue->add('OC\Migration\BackgroundRepair', [
1063
-				'app' => $appId,
1064
-				'step' => $step]);
1065
-		}
1066
-	}
1067
-
1068
-	/**
1069
-	 * @param string $appId
1070
-	 * @return \OC\Files\View|false
1071
-	 */
1072
-	public static function getStorage(string $appId) {
1073
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1074
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
1075
-				$view = new \OC\Files\View('/' . OC_User::getUser());
1076
-				if (!$view->file_exists($appId)) {
1077
-					$view->mkdir($appId);
1078
-				}
1079
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1080
-			} else {
1081
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1082
-				return false;
1083
-			}
1084
-		} else {
1085
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1086
-			return false;
1087
-		}
1088
-	}
1089
-
1090
-	protected static function findBestL10NOption(array $options, string $lang): string {
1091
-		// only a single option
1092
-		if (isset($options['@value'])) {
1093
-			return $options['@value'];
1094
-		}
1095
-
1096
-		$fallback = $similarLangFallback = $englishFallback = false;
1097
-
1098
-		$lang = strtolower($lang);
1099
-		$similarLang = $lang;
1100
-		if (strpos($similarLang, '_')) {
1101
-			// For "de_DE" we want to find "de" and the other way around
1102
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
1103
-		}
1104
-
1105
-		foreach ($options as $option) {
1106
-			if (is_array($option)) {
1107
-				if ($fallback === false) {
1108
-					$fallback = $option['@value'];
1109
-				}
1110
-
1111
-				if (!isset($option['@attributes']['lang'])) {
1112
-					continue;
1113
-				}
1114
-
1115
-				$attributeLang = strtolower($option['@attributes']['lang']);
1116
-				if ($attributeLang === $lang) {
1117
-					return $option['@value'];
1118
-				}
1119
-
1120
-				if ($attributeLang === $similarLang) {
1121
-					$similarLangFallback = $option['@value'];
1122
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1123
-					if ($similarLangFallback === false) {
1124
-						$similarLangFallback = $option['@value'];
1125
-					}
1126
-				}
1127
-			} else {
1128
-				$englishFallback = $option;
1129
-			}
1130
-		}
1131
-
1132
-		if ($similarLangFallback !== false) {
1133
-			return $similarLangFallback;
1134
-		} elseif ($englishFallback !== false) {
1135
-			return $englishFallback;
1136
-		}
1137
-		return (string) $fallback;
1138
-	}
1139
-
1140
-	/**
1141
-	 * parses the app data array and enhanced the 'description' value
1142
-	 *
1143
-	 * @param array $data the app data
1144
-	 * @param string $lang
1145
-	 * @return array improved app data
1146
-	 */
1147
-	public static function parseAppInfo(array $data, $lang = null): array {
1148
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1149
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1150
-		}
1151
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1152
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1153
-		}
1154
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1155
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1156
-		} elseif (isset($data['description']) && is_string($data['description'])) {
1157
-			$data['description'] = trim($data['description']);
1158
-		} else {
1159
-			$data['description'] = '';
1160
-		}
1161
-
1162
-		return $data;
1163
-	}
1164
-
1165
-	/**
1166
-	 * @param \OCP\IConfig $config
1167
-	 * @param \OCP\IL10N $l
1168
-	 * @param array $info
1169
-	 * @throws \Exception
1170
-	 */
1171
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1172
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1173
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1174
-		if (!empty($missing)) {
1175
-			$missingMsg = implode(PHP_EOL, $missing);
1176
-			throw new \Exception(
1177
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1178
-					[$info['name'], $missingMsg]
1179
-				)
1180
-			);
1181
-		}
1182
-	}
71
+    private static $adminForms = [];
72
+    private static $personalForms = [];
73
+    private static $appTypes = [];
74
+    private static $loadedApps = [];
75
+    private static $altLogin = [];
76
+    private static $alreadyRegistered = [];
77
+    public const supportedApp = 300;
78
+    public const officialApp = 200;
79
+
80
+    /**
81
+     * clean the appId
82
+     *
83
+     * @psalm-taint-escape file
84
+     * @psalm-taint-escape include
85
+     *
86
+     * @param string $app AppId that needs to be cleaned
87
+     * @return string
88
+     */
89
+    public static function cleanAppId(string $app): string {
90
+        return str_replace(['\0', '/', '\\', '..'], '', $app);
91
+    }
92
+
93
+    /**
94
+     * Check if an app is loaded
95
+     *
96
+     * @param string $app
97
+     * @return bool
98
+     */
99
+    public static function isAppLoaded(string $app): bool {
100
+        return isset(self::$loadedApps[$app]);
101
+    }
102
+
103
+    /**
104
+     * loads all apps
105
+     *
106
+     * @param string[] $types
107
+     * @return bool
108
+     *
109
+     * This function walks through the ownCloud directory and loads all apps
110
+     * it can find. A directory contains an app if the file /appinfo/info.xml
111
+     * exists.
112
+     *
113
+     * if $types is set to non-empty array, only apps of those types will be loaded
114
+     */
115
+    public static function loadApps(array $types = []): bool {
116
+        if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
117
+            return false;
118
+        }
119
+        // Load the enabled apps here
120
+        $apps = self::getEnabledApps();
121
+
122
+        // Add each apps' folder as allowed class path
123
+        foreach ($apps as $app) {
124
+            // If the app is already loaded then autoloading it makes no sense
125
+            if (!isset(self::$loadedApps[$app])) {
126
+                $path = self::getAppPath($app);
127
+                if ($path !== false) {
128
+                    self::registerAutoloading($app, $path);
129
+                }
130
+            }
131
+        }
132
+
133
+        // prevent app.php from printing output
134
+        ob_start();
135
+        foreach ($apps as $app) {
136
+            if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) {
137
+                self::loadApp($app);
138
+            }
139
+        }
140
+        ob_end_clean();
141
+
142
+        return true;
143
+    }
144
+
145
+    /**
146
+     * load a single app
147
+     *
148
+     * @param string $app
149
+     * @throws Exception
150
+     */
151
+    public static function loadApp(string $app) {
152
+        self::$loadedApps[$app] = true;
153
+        $appPath = self::getAppPath($app);
154
+        if ($appPath === false) {
155
+            return;
156
+        }
157
+
158
+        // in case someone calls loadApp() directly
159
+        self::registerAutoloading($app, $appPath);
160
+
161
+        /** @var Coordinator $coordinator */
162
+        $coordinator = \OC::$server->query(Coordinator::class);
163
+        $isBootable = $coordinator->isBootable($app);
164
+
165
+        $hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
166
+
167
+        if ($isBootable && $hasAppPhpFile) {
168
+            \OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
169
+                'app' => $app,
170
+            ]);
171
+        } elseif ($hasAppPhpFile) {
172
+            \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
173
+                'app' => $app,
174
+            ]);
175
+            \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
176
+            try {
177
+                self::requireAppFile($app);
178
+            } catch (Throwable $ex) {
179
+                if ($ex instanceof ServerNotAvailableException) {
180
+                    throw $ex;
181
+                }
182
+                if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
183
+                    \OC::$server->getLogger()->logException($ex, [
184
+                        'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
185
+                    ]);
186
+
187
+                    // Only disable apps which are not shipped and that are not authentication apps
188
+                    \OC::$server->getAppManager()->disableApp($app, true);
189
+                } else {
190
+                    \OC::$server->getLogger()->logException($ex, [
191
+                        'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
192
+                    ]);
193
+                }
194
+            }
195
+            \OC::$server->getEventLogger()->end('load_app_' . $app);
196
+        }
197
+        $coordinator->bootApp($app);
198
+
199
+        $info = self::getAppInfo($app);
200
+        if (!empty($info['activity']['filters'])) {
201
+            foreach ($info['activity']['filters'] as $filter) {
202
+                \OC::$server->getActivityManager()->registerFilter($filter);
203
+            }
204
+        }
205
+        if (!empty($info['activity']['settings'])) {
206
+            foreach ($info['activity']['settings'] as $setting) {
207
+                \OC::$server->getActivityManager()->registerSetting($setting);
208
+            }
209
+        }
210
+        if (!empty($info['activity']['providers'])) {
211
+            foreach ($info['activity']['providers'] as $provider) {
212
+                \OC::$server->getActivityManager()->registerProvider($provider);
213
+            }
214
+        }
215
+
216
+        if (!empty($info['settings']['admin'])) {
217
+            foreach ($info['settings']['admin'] as $setting) {
218
+                \OC::$server->getSettingsManager()->registerSetting('admin', $setting);
219
+            }
220
+        }
221
+        if (!empty($info['settings']['admin-section'])) {
222
+            foreach ($info['settings']['admin-section'] as $section) {
223
+                \OC::$server->getSettingsManager()->registerSection('admin', $section);
224
+            }
225
+        }
226
+        if (!empty($info['settings']['personal'])) {
227
+            foreach ($info['settings']['personal'] as $setting) {
228
+                \OC::$server->getSettingsManager()->registerSetting('personal', $setting);
229
+            }
230
+        }
231
+        if (!empty($info['settings']['personal-section'])) {
232
+            foreach ($info['settings']['personal-section'] as $section) {
233
+                \OC::$server->getSettingsManager()->registerSection('personal', $section);
234
+            }
235
+        }
236
+
237
+        if (!empty($info['collaboration']['plugins'])) {
238
+            // deal with one or many plugin entries
239
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
240
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
241
+            foreach ($plugins as $plugin) {
242
+                if ($plugin['@attributes']['type'] === 'collaborator-search') {
243
+                    $pluginInfo = [
244
+                        'shareType' => $plugin['@attributes']['share-type'],
245
+                        'class' => $plugin['@value'],
246
+                    ];
247
+                    \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
248
+                } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
249
+                    \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
250
+                }
251
+            }
252
+        }
253
+    }
254
+
255
+    /**
256
+     * @internal
257
+     * @param string $app
258
+     * @param string $path
259
+     * @param bool $force
260
+     */
261
+    public static function registerAutoloading(string $app, string $path, bool $force = false) {
262
+        $key = $app . '-' . $path;
263
+        if (!$force && isset(self::$alreadyRegistered[$key])) {
264
+            return;
265
+        }
266
+
267
+        self::$alreadyRegistered[$key] = true;
268
+
269
+        // Register on PSR-4 composer autoloader
270
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
271
+        \OC::$server->registerNamespace($app, $appNamespace);
272
+
273
+        if (file_exists($path . '/composer/autoload.php')) {
274
+            require_once $path . '/composer/autoload.php';
275
+        } else {
276
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
277
+            // Register on legacy autoloader
278
+            \OC::$loader->addValidRoot($path);
279
+        }
280
+
281
+        // Register Test namespace only when testing
282
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
283
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
284
+        }
285
+    }
286
+
287
+    /**
288
+     * Load app.php from the given app
289
+     *
290
+     * @param string $app app name
291
+     * @throws Error
292
+     */
293
+    private static function requireAppFile(string $app) {
294
+        // encapsulated here to avoid variable scope conflicts
295
+        require_once $app . '/appinfo/app.php';
296
+    }
297
+
298
+    /**
299
+     * check if an app is of a specific type
300
+     *
301
+     * @param string $app
302
+     * @param array $types
303
+     * @return bool
304
+     */
305
+    public static function isType(string $app, array $types): bool {
306
+        $appTypes = self::getAppTypes($app);
307
+        foreach ($types as $type) {
308
+            if (array_search($type, $appTypes) !== false) {
309
+                return true;
310
+            }
311
+        }
312
+        return false;
313
+    }
314
+
315
+    /**
316
+     * get the types of an app
317
+     *
318
+     * @param string $app
319
+     * @return array
320
+     */
321
+    private static function getAppTypes(string $app): array {
322
+        //load the cache
323
+        if (count(self::$appTypes) == 0) {
324
+            self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
325
+        }
326
+
327
+        if (isset(self::$appTypes[$app])) {
328
+            return explode(',', self::$appTypes[$app]);
329
+        }
330
+
331
+        return [];
332
+    }
333
+
334
+    /**
335
+     * read app types from info.xml and cache them in the database
336
+     */
337
+    public static function setAppTypes(string $app) {
338
+        $appManager = \OC::$server->getAppManager();
339
+        $appData = $appManager->getAppInfo($app);
340
+        if (!is_array($appData)) {
341
+            return;
342
+        }
343
+
344
+        if (isset($appData['types'])) {
345
+            $appTypes = implode(',', $appData['types']);
346
+        } else {
347
+            $appTypes = '';
348
+            $appData['types'] = [];
349
+        }
350
+
351
+        $config = \OC::$server->getConfig();
352
+        $config->setAppValue($app, 'types', $appTypes);
353
+
354
+        if ($appManager->hasProtectedAppType($appData['types'])) {
355
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
356
+            if ($enabled !== 'yes' && $enabled !== 'no') {
357
+                $config->setAppValue($app, 'enabled', 'yes');
358
+            }
359
+        }
360
+    }
361
+
362
+    /**
363
+     * Returns apps enabled for the current user.
364
+     *
365
+     * @param bool $forceRefresh whether to refresh the cache
366
+     * @param bool $all whether to return apps for all users, not only the
367
+     * currently logged in one
368
+     * @return string[]
369
+     */
370
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
371
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
372
+            return [];
373
+        }
374
+        // in incognito mode or when logged out, $user will be false,
375
+        // which is also the case during an upgrade
376
+        $appManager = \OC::$server->getAppManager();
377
+        if ($all) {
378
+            $user = null;
379
+        } else {
380
+            $user = \OC::$server->getUserSession()->getUser();
381
+        }
382
+
383
+        if (is_null($user)) {
384
+            $apps = $appManager->getInstalledApps();
385
+        } else {
386
+            $apps = $appManager->getEnabledAppsForUser($user);
387
+        }
388
+        $apps = array_filter($apps, function ($app) {
389
+            return $app !== 'files';//we add this manually
390
+        });
391
+        sort($apps);
392
+        array_unshift($apps, 'files');
393
+        return $apps;
394
+    }
395
+
396
+    /**
397
+     * checks whether or not an app is enabled
398
+     *
399
+     * @param string $app app
400
+     * @return bool
401
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
402
+     *
403
+     * This function checks whether or not an app is enabled.
404
+     */
405
+    public static function isEnabled(string $app): bool {
406
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
407
+    }
408
+
409
+    /**
410
+     * enables an app
411
+     *
412
+     * @param string $appId
413
+     * @param array $groups (optional) when set, only these groups will have access to the app
414
+     * @throws \Exception
415
+     * @return void
416
+     *
417
+     * This function set an app as enabled in appconfig.
418
+     */
419
+    public function enable(string $appId,
420
+                            array $groups = []) {
421
+
422
+        // Check if app is already downloaded
423
+        /** @var Installer $installer */
424
+        $installer = \OC::$server->query(Installer::class);
425
+        $isDownloaded = $installer->isDownloaded($appId);
426
+
427
+        if (!$isDownloaded) {
428
+            $installer->downloadApp($appId);
429
+        }
430
+
431
+        $installer->installApp($appId);
432
+
433
+        $appManager = \OC::$server->getAppManager();
434
+        if ($groups !== []) {
435
+            $groupManager = \OC::$server->getGroupManager();
436
+            $groupsList = [];
437
+            foreach ($groups as $group) {
438
+                $groupItem = $groupManager->get($group);
439
+                if ($groupItem instanceof \OCP\IGroup) {
440
+                    $groupsList[] = $groupManager->get($group);
441
+                }
442
+            }
443
+            $appManager->enableAppForGroups($appId, $groupsList);
444
+        } else {
445
+            $appManager->enableApp($appId);
446
+        }
447
+    }
448
+
449
+    /**
450
+     * Get the path where to install apps
451
+     *
452
+     * @return string|false
453
+     */
454
+    public static function getInstallPath() {
455
+        if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
456
+            return false;
457
+        }
458
+
459
+        foreach (OC::$APPSROOTS as $dir) {
460
+            if (isset($dir['writable']) && $dir['writable'] === true) {
461
+                return $dir['path'];
462
+            }
463
+        }
464
+
465
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
466
+        return null;
467
+    }
468
+
469
+
470
+    /**
471
+     * search for an app in all app-directories
472
+     *
473
+     * @param string $appId
474
+     * @return false|string
475
+     */
476
+    public static function findAppInDirectories(string $appId) {
477
+        $sanitizedAppId = self::cleanAppId($appId);
478
+        if ($sanitizedAppId !== $appId) {
479
+            return false;
480
+        }
481
+        static $app_dir = [];
482
+
483
+        if (isset($app_dir[$appId])) {
484
+            return $app_dir[$appId];
485
+        }
486
+
487
+        $possibleApps = [];
488
+        foreach (OC::$APPSROOTS as $dir) {
489
+            if (file_exists($dir['path'] . '/' . $appId)) {
490
+                $possibleApps[] = $dir;
491
+            }
492
+        }
493
+
494
+        if (empty($possibleApps)) {
495
+            return false;
496
+        } elseif (count($possibleApps) === 1) {
497
+            $dir = array_shift($possibleApps);
498
+            $app_dir[$appId] = $dir;
499
+            return $dir;
500
+        } else {
501
+            $versionToLoad = [];
502
+            foreach ($possibleApps as $possibleApp) {
503
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
504
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
505
+                    $versionToLoad = [
506
+                        'dir' => $possibleApp,
507
+                        'version' => $version,
508
+                    ];
509
+                }
510
+            }
511
+            $app_dir[$appId] = $versionToLoad['dir'];
512
+            return $versionToLoad['dir'];
513
+            //TODO - write test
514
+        }
515
+    }
516
+
517
+    /**
518
+     * Get the directory for the given app.
519
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
520
+     *
521
+     * @psalm-taint-specialize
522
+     *
523
+     * @param string $appId
524
+     * @return string|false
525
+     * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
526
+     */
527
+    public static function getAppPath(string $appId) {
528
+        if ($appId === null || trim($appId) === '') {
529
+            return false;
530
+        }
531
+
532
+        if (($dir = self::findAppInDirectories($appId)) != false) {
533
+            return $dir['path'] . '/' . $appId;
534
+        }
535
+        return false;
536
+    }
537
+
538
+    /**
539
+     * Get the path for the given app on the access
540
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
541
+     *
542
+     * @param string $appId
543
+     * @return string|false
544
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
545
+     */
546
+    public static function getAppWebPath(string $appId) {
547
+        if (($dir = self::findAppInDirectories($appId)) != false) {
548
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
549
+        }
550
+        return false;
551
+    }
552
+
553
+    /**
554
+     * get the last version of the app from appinfo/info.xml
555
+     *
556
+     * @param string $appId
557
+     * @param bool $useCache
558
+     * @return string
559
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
560
+     */
561
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
562
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
563
+    }
564
+
565
+    /**
566
+     * get app's version based on it's path
567
+     *
568
+     * @param string $path
569
+     * @return string
570
+     */
571
+    public static function getAppVersionByPath(string $path): string {
572
+        $infoFile = $path . '/appinfo/info.xml';
573
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
574
+        return isset($appData['version']) ? $appData['version'] : '';
575
+    }
576
+
577
+
578
+    /**
579
+     * Read all app metadata from the info.xml file
580
+     *
581
+     * @param string $appId id of the app or the path of the info.xml file
582
+     * @param bool $path
583
+     * @param string $lang
584
+     * @return array|null
585
+     * @note all data is read from info.xml, not just pre-defined fields
586
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
587
+     */
588
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
589
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
590
+    }
591
+
592
+    /**
593
+     * Returns the navigation
594
+     *
595
+     * @return array
596
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
597
+     *
598
+     * This function returns an array containing all entries added. The
599
+     * entries are sorted by the key 'order' ascending. Additional to the keys
600
+     * given for each app the following keys exist:
601
+     *   - active: boolean, signals if the user is on this navigation entry
602
+     */
603
+    public static function getNavigation(): array {
604
+        return OC::$server->getNavigationManager()->getAll();
605
+    }
606
+
607
+    /**
608
+     * Returns the Settings Navigation
609
+     *
610
+     * @return string[]
611
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
612
+     *
613
+     * This function returns an array containing all settings pages added. The
614
+     * entries are sorted by the key 'order' ascending.
615
+     */
616
+    public static function getSettingsNavigation(): array {
617
+        return OC::$server->getNavigationManager()->getAll('settings');
618
+    }
619
+
620
+    /**
621
+     * get the id of loaded app
622
+     *
623
+     * @return string
624
+     */
625
+    public static function getCurrentApp(): string {
626
+        $request = \OC::$server->getRequest();
627
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
628
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
629
+        if (empty($topFolder)) {
630
+            $path_info = $request->getPathInfo();
631
+            if ($path_info) {
632
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
633
+            }
634
+        }
635
+        if ($topFolder == 'apps') {
636
+            $length = strlen($topFolder);
637
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
638
+        } else {
639
+            return $topFolder;
640
+        }
641
+    }
642
+
643
+    /**
644
+     * @param string $type
645
+     * @return array
646
+     */
647
+    public static function getForms(string $type): array {
648
+        $forms = [];
649
+        switch ($type) {
650
+            case 'admin':
651
+                $source = self::$adminForms;
652
+                break;
653
+            case 'personal':
654
+                $source = self::$personalForms;
655
+                break;
656
+            default:
657
+                return [];
658
+        }
659
+        foreach ($source as $form) {
660
+            $forms[] = include $form;
661
+        }
662
+        return $forms;
663
+    }
664
+
665
+    /**
666
+     * register an admin form to be shown
667
+     *
668
+     * @param string $app
669
+     * @param string $page
670
+     */
671
+    public static function registerAdmin(string $app, string $page) {
672
+        self::$adminForms[] = $app . '/' . $page . '.php';
673
+    }
674
+
675
+    /**
676
+     * register a personal form to be shown
677
+     * @param string $app
678
+     * @param string $page
679
+     */
680
+    public static function registerPersonal(string $app, string $page) {
681
+        self::$personalForms[] = $app . '/' . $page . '.php';
682
+    }
683
+
684
+    /**
685
+     * @param array $entry
686
+     * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
687
+     */
688
+    public static function registerLogIn(array $entry) {
689
+        \OC::$server->getLogger()->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
690
+        self::$altLogin[] = $entry;
691
+    }
692
+
693
+    /**
694
+     * @return array
695
+     */
696
+    public static function getAlternativeLogIns(): array {
697
+        /** @var Coordinator $bootstrapCoordinator */
698
+        $bootstrapCoordinator = \OC::$server->query(Coordinator::class);
699
+
700
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
701
+            if (!in_array(IAlternativeLogin::class, class_implements($registration['class']), true)) {
702
+                \OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
703
+                    'option' => $registration['class'],
704
+                    'interface' => IAlternativeLogin::class,
705
+                    'app' => $registration['app'],
706
+                ]);
707
+                continue;
708
+            }
709
+
710
+            try {
711
+                /** @var IAlternativeLogin $provider */
712
+                $provider = \OC::$server->query($registration['class']);
713
+            } catch (QueryException $e) {
714
+                \OC::$server->getLogger()->logException($e, [
715
+                    'message' => 'Alternative login option {option} can not be initialised.',
716
+                    'option' => $registration['class'],
717
+                    'app' => $registration['app'],
718
+                ]);
719
+            }
720
+
721
+            try {
722
+                $provider->load();
723
+
724
+                self::$altLogin[] = [
725
+                    'name' => $provider->getLabel(),
726
+                    'href' => $provider->getLink(),
727
+                    'style' => $provider->getClass(),
728
+                ];
729
+            } catch (Throwable $e) {
730
+                \OC::$server->getLogger()->logException($e, [
731
+                    'message' => 'Alternative login option {option} had an error while loading.',
732
+                    'option' => $registration['class'],
733
+                    'app' => $registration['app'],
734
+                ]);
735
+            }
736
+        }
737
+
738
+        return self::$altLogin;
739
+    }
740
+
741
+    /**
742
+     * get a list of all apps in the apps folder
743
+     *
744
+     * @return string[] an array of app names (string IDs)
745
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
746
+     */
747
+    public static function getAllApps(): array {
748
+        $apps = [];
749
+
750
+        foreach (OC::$APPSROOTS as $apps_dir) {
751
+            if (!is_readable($apps_dir['path'])) {
752
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
753
+                continue;
754
+            }
755
+            $dh = opendir($apps_dir['path']);
756
+
757
+            if (is_resource($dh)) {
758
+                while (($file = readdir($dh)) !== false) {
759
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
760
+                        $apps[] = $file;
761
+                    }
762
+                }
763
+            }
764
+        }
765
+
766
+        $apps = array_unique($apps);
767
+
768
+        return $apps;
769
+    }
770
+
771
+    /**
772
+     * List all apps, this is used in apps.php
773
+     *
774
+     * @return array
775
+     */
776
+    public function listAllApps(): array {
777
+        $installedApps = OC_App::getAllApps();
778
+
779
+        $appManager = \OC::$server->getAppManager();
780
+        //we don't want to show configuration for these
781
+        $blacklist = $appManager->getAlwaysEnabledApps();
782
+        $appList = [];
783
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
784
+        $urlGenerator = \OC::$server->getURLGenerator();
785
+        /** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
786
+        $subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
787
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
788
+
789
+        foreach ($installedApps as $app) {
790
+            if (array_search($app, $blacklist) === false) {
791
+                $info = OC_App::getAppInfo($app, false, $langCode);
792
+                if (!is_array($info)) {
793
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
794
+                    continue;
795
+                }
796
+
797
+                if (!isset($info['name'])) {
798
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
799
+                    continue;
800
+                }
801
+
802
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
803
+                $info['groups'] = null;
804
+                if ($enabled === 'yes') {
805
+                    $active = true;
806
+                } elseif ($enabled === 'no') {
807
+                    $active = false;
808
+                } else {
809
+                    $active = true;
810
+                    $info['groups'] = $enabled;
811
+                }
812
+
813
+                $info['active'] = $active;
814
+
815
+                if ($appManager->isShipped($app)) {
816
+                    $info['internal'] = true;
817
+                    $info['level'] = self::officialApp;
818
+                    $info['removable'] = false;
819
+                } else {
820
+                    $info['internal'] = false;
821
+                    $info['removable'] = true;
822
+                }
823
+
824
+                if (in_array($app, $supportedApps)) {
825
+                    $info['level'] = self::supportedApp;
826
+                }
827
+
828
+                $appPath = self::getAppPath($app);
829
+                if ($appPath !== false) {
830
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
831
+                    if (file_exists($appIcon)) {
832
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
833
+                        $info['previewAsIcon'] = true;
834
+                    } else {
835
+                        $appIcon = $appPath . '/img/app.svg';
836
+                        if (file_exists($appIcon)) {
837
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
838
+                            $info['previewAsIcon'] = true;
839
+                        }
840
+                    }
841
+                }
842
+                // fix documentation
843
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
844
+                    foreach ($info['documentation'] as $key => $url) {
845
+                        // If it is not an absolute URL we assume it is a key
846
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
847
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
848
+                            $url = $urlGenerator->linkToDocs($url);
849
+                        }
850
+
851
+                        $info['documentation'][$key] = $url;
852
+                    }
853
+                }
854
+
855
+                $info['version'] = OC_App::getAppVersion($app);
856
+                $appList[] = $info;
857
+            }
858
+        }
859
+
860
+        return $appList;
861
+    }
862
+
863
+    public static function shouldUpgrade(string $app): bool {
864
+        $versions = self::getAppVersions();
865
+        $currentVersion = OC_App::getAppVersion($app);
866
+        if ($currentVersion && isset($versions[$app])) {
867
+            $installedVersion = $versions[$app];
868
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
869
+                return true;
870
+            }
871
+        }
872
+        return false;
873
+    }
874
+
875
+    /**
876
+     * Adjust the number of version parts of $version1 to match
877
+     * the number of version parts of $version2.
878
+     *
879
+     * @param string $version1 version to adjust
880
+     * @param string $version2 version to take the number of parts from
881
+     * @return string shortened $version1
882
+     */
883
+    private static function adjustVersionParts(string $version1, string $version2): string {
884
+        $version1 = explode('.', $version1);
885
+        $version2 = explode('.', $version2);
886
+        // reduce $version1 to match the number of parts in $version2
887
+        while (count($version1) > count($version2)) {
888
+            array_pop($version1);
889
+        }
890
+        // if $version1 does not have enough parts, add some
891
+        while (count($version1) < count($version2)) {
892
+            $version1[] = '0';
893
+        }
894
+        return implode('.', $version1);
895
+    }
896
+
897
+    /**
898
+     * Check whether the current ownCloud version matches the given
899
+     * application's version requirements.
900
+     *
901
+     * The comparison is made based on the number of parts that the
902
+     * app info version has. For example for ownCloud 6.0.3 if the
903
+     * app info version is expecting version 6.0, the comparison is
904
+     * made on the first two parts of the ownCloud version.
905
+     * This means that it's possible to specify "requiremin" => 6
906
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
907
+     *
908
+     * @param string $ocVersion ownCloud version to check against
909
+     * @param array $appInfo app info (from xml)
910
+     *
911
+     * @return boolean true if compatible, otherwise false
912
+     */
913
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
914
+        $requireMin = '';
915
+        $requireMax = '';
916
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
917
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
918
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
919
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
920
+        } elseif (isset($appInfo['requiremin'])) {
921
+            $requireMin = $appInfo['requiremin'];
922
+        } elseif (isset($appInfo['require'])) {
923
+            $requireMin = $appInfo['require'];
924
+        }
925
+
926
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
927
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
928
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
929
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
930
+        } elseif (isset($appInfo['requiremax'])) {
931
+            $requireMax = $appInfo['requiremax'];
932
+        }
933
+
934
+        if (!empty($requireMin)
935
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
936
+        ) {
937
+            return false;
938
+        }
939
+
940
+        if (!$ignoreMax && !empty($requireMax)
941
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
942
+        ) {
943
+            return false;
944
+        }
945
+
946
+        return true;
947
+    }
948
+
949
+    /**
950
+     * get the installed version of all apps
951
+     */
952
+    public static function getAppVersions() {
953
+        static $versions;
954
+
955
+        if (!$versions) {
956
+            $appConfig = \OC::$server->getAppConfig();
957
+            $versions = $appConfig->getValues(false, 'installed_version');
958
+        }
959
+        return $versions;
960
+    }
961
+
962
+    /**
963
+     * update the database for the app and call the update script
964
+     *
965
+     * @param string $appId
966
+     * @return bool
967
+     */
968
+    public static function updateApp(string $appId): bool {
969
+        $appPath = self::getAppPath($appId);
970
+        if ($appPath === false) {
971
+            return false;
972
+        }
973
+
974
+        \OC::$server->getAppManager()->clearAppsCache();
975
+        $appData = self::getAppInfo($appId);
976
+
977
+        self::registerAutoloading($appId, $appPath, true);
978
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
979
+
980
+        if (file_exists($appPath . '/appinfo/database.xml')) {
981
+            OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
982
+        } else {
983
+            $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
984
+            $ms->migrate();
985
+        }
986
+
987
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
988
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
989
+        // update appversion in app manager
990
+        \OC::$server->getAppManager()->clearAppsCache();
991
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
992
+
993
+        self::setupBackgroundJobs($appData['background-jobs']);
994
+
995
+        //set remote/public handlers
996
+        if (array_key_exists('ocsid', $appData)) {
997
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
998
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
999
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1000
+        }
1001
+        foreach ($appData['remote'] as $name => $path) {
1002
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1003
+        }
1004
+        foreach ($appData['public'] as $name => $path) {
1005
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1006
+        }
1007
+
1008
+        self::setAppTypes($appId);
1009
+
1010
+        $version = \OC_App::getAppVersion($appId);
1011
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
1012
+
1013
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1014
+            ManagerEvent::EVENT_APP_UPDATE, $appId
1015
+        ));
1016
+
1017
+        return true;
1018
+    }
1019
+
1020
+    /**
1021
+     * @param string $appId
1022
+     * @param string[] $steps
1023
+     * @throws \OC\NeedsUpdateException
1024
+     */
1025
+    public static function executeRepairSteps(string $appId, array $steps) {
1026
+        if (empty($steps)) {
1027
+            return;
1028
+        }
1029
+        // load the app
1030
+        self::loadApp($appId);
1031
+
1032
+        $dispatcher = OC::$server->getEventDispatcher();
1033
+
1034
+        // load the steps
1035
+        $r = new Repair([], $dispatcher);
1036
+        foreach ($steps as $step) {
1037
+            try {
1038
+                $r->addStep($step);
1039
+            } catch (Exception $ex) {
1040
+                $r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1041
+                \OC::$server->getLogger()->logException($ex);
1042
+            }
1043
+        }
1044
+        // run the steps
1045
+        $r->run();
1046
+    }
1047
+
1048
+    public static function setupBackgroundJobs(array $jobs) {
1049
+        $queue = \OC::$server->getJobList();
1050
+        foreach ($jobs as $job) {
1051
+            $queue->add($job);
1052
+        }
1053
+    }
1054
+
1055
+    /**
1056
+     * @param string $appId
1057
+     * @param string[] $steps
1058
+     */
1059
+    private static function setupLiveMigrations(string $appId, array $steps) {
1060
+        $queue = \OC::$server->getJobList();
1061
+        foreach ($steps as $step) {
1062
+            $queue->add('OC\Migration\BackgroundRepair', [
1063
+                'app' => $appId,
1064
+                'step' => $step]);
1065
+        }
1066
+    }
1067
+
1068
+    /**
1069
+     * @param string $appId
1070
+     * @return \OC\Files\View|false
1071
+     */
1072
+    public static function getStorage(string $appId) {
1073
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1074
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
1075
+                $view = new \OC\Files\View('/' . OC_User::getUser());
1076
+                if (!$view->file_exists($appId)) {
1077
+                    $view->mkdir($appId);
1078
+                }
1079
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1080
+            } else {
1081
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1082
+                return false;
1083
+            }
1084
+        } else {
1085
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1086
+            return false;
1087
+        }
1088
+    }
1089
+
1090
+    protected static function findBestL10NOption(array $options, string $lang): string {
1091
+        // only a single option
1092
+        if (isset($options['@value'])) {
1093
+            return $options['@value'];
1094
+        }
1095
+
1096
+        $fallback = $similarLangFallback = $englishFallback = false;
1097
+
1098
+        $lang = strtolower($lang);
1099
+        $similarLang = $lang;
1100
+        if (strpos($similarLang, '_')) {
1101
+            // For "de_DE" we want to find "de" and the other way around
1102
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
1103
+        }
1104
+
1105
+        foreach ($options as $option) {
1106
+            if (is_array($option)) {
1107
+                if ($fallback === false) {
1108
+                    $fallback = $option['@value'];
1109
+                }
1110
+
1111
+                if (!isset($option['@attributes']['lang'])) {
1112
+                    continue;
1113
+                }
1114
+
1115
+                $attributeLang = strtolower($option['@attributes']['lang']);
1116
+                if ($attributeLang === $lang) {
1117
+                    return $option['@value'];
1118
+                }
1119
+
1120
+                if ($attributeLang === $similarLang) {
1121
+                    $similarLangFallback = $option['@value'];
1122
+                } elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1123
+                    if ($similarLangFallback === false) {
1124
+                        $similarLangFallback = $option['@value'];
1125
+                    }
1126
+                }
1127
+            } else {
1128
+                $englishFallback = $option;
1129
+            }
1130
+        }
1131
+
1132
+        if ($similarLangFallback !== false) {
1133
+            return $similarLangFallback;
1134
+        } elseif ($englishFallback !== false) {
1135
+            return $englishFallback;
1136
+        }
1137
+        return (string) $fallback;
1138
+    }
1139
+
1140
+    /**
1141
+     * parses the app data array and enhanced the 'description' value
1142
+     *
1143
+     * @param array $data the app data
1144
+     * @param string $lang
1145
+     * @return array improved app data
1146
+     */
1147
+    public static function parseAppInfo(array $data, $lang = null): array {
1148
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1149
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1150
+        }
1151
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1152
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1153
+        }
1154
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1155
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1156
+        } elseif (isset($data['description']) && is_string($data['description'])) {
1157
+            $data['description'] = trim($data['description']);
1158
+        } else {
1159
+            $data['description'] = '';
1160
+        }
1161
+
1162
+        return $data;
1163
+    }
1164
+
1165
+    /**
1166
+     * @param \OCP\IConfig $config
1167
+     * @param \OCP\IL10N $l
1168
+     * @param array $info
1169
+     * @throws \Exception
1170
+     */
1171
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1172
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1173
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1174
+        if (!empty($missing)) {
1175
+            $missingMsg = implode(PHP_EOL, $missing);
1176
+            throw new \Exception(
1177
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1178
+                    [$info['name'], $missingMsg]
1179
+                )
1180
+            );
1181
+        }
1182
+    }
1183 1183
 }
Please login to merge, or discard this patch.
Spacing   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 		$coordinator = \OC::$server->query(Coordinator::class);
163 163
 		$isBootable = $coordinator->isBootable($app);
164 164
 
165
-		$hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
165
+		$hasAppPhpFile = is_file($appPath.'/appinfo/app.php');
166 166
 
167 167
 		if ($isBootable && $hasAppPhpFile) {
168 168
 			\OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
 			\OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
173 173
 				'app' => $app,
174 174
 			]);
175
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
175
+			\OC::$server->getEventLogger()->start('load_app_'.$app, 'Load app: '.$app);
176 176
 			try {
177 177
 				self::requireAppFile($app);
178 178
 			} catch (Throwable $ex) {
@@ -181,18 +181,18 @@  discard block
 block discarded – undo
181 181
 				}
182 182
 				if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
183 183
 					\OC::$server->getLogger()->logException($ex, [
184
-						'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
184
+						'message' => "App $app threw an error during app.php load and will be disabled: ".$ex->getMessage(),
185 185
 					]);
186 186
 
187 187
 					// Only disable apps which are not shipped and that are not authentication apps
188 188
 					\OC::$server->getAppManager()->disableApp($app, true);
189 189
 				} else {
190 190
 					\OC::$server->getLogger()->logException($ex, [
191
-						'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
191
+						'message' => "App $app threw an error during app.php load: ".$ex->getMessage(),
192 192
 					]);
193 193
 				}
194 194
 			}
195
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
195
+			\OC::$server->getEventLogger()->end('load_app_'.$app);
196 196
 		}
197 197
 		$coordinator->bootApp($app);
198 198
 
@@ -259,7 +259,7 @@  discard block
 block discarded – undo
259 259
 	 * @param bool $force
260 260
 	 */
261 261
 	public static function registerAutoloading(string $app, string $path, bool $force = false) {
262
-		$key = $app . '-' . $path;
262
+		$key = $app.'-'.$path;
263 263
 		if (!$force && isset(self::$alreadyRegistered[$key])) {
264 264
 			return;
265 265
 		}
@@ -270,17 +270,17 @@  discard block
 block discarded – undo
270 270
 		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
271 271
 		\OC::$server->registerNamespace($app, $appNamespace);
272 272
 
273
-		if (file_exists($path . '/composer/autoload.php')) {
274
-			require_once $path . '/composer/autoload.php';
273
+		if (file_exists($path.'/composer/autoload.php')) {
274
+			require_once $path.'/composer/autoload.php';
275 275
 		} else {
276
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
276
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\', $path.'/lib/', true);
277 277
 			// Register on legacy autoloader
278 278
 			\OC::$loader->addValidRoot($path);
279 279
 		}
280 280
 
281 281
 		// Register Test namespace only when testing
282 282
 		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
283
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
283
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\Tests\\', $path.'/tests/', true);
284 284
 		}
285 285
 	}
286 286
 
@@ -292,7 +292,7 @@  discard block
 block discarded – undo
292 292
 	 */
293 293
 	private static function requireAppFile(string $app) {
294 294
 		// encapsulated here to avoid variable scope conflicts
295
-		require_once $app . '/appinfo/app.php';
295
+		require_once $app.'/appinfo/app.php';
296 296
 	}
297 297
 
298 298
 	/**
@@ -385,8 +385,8 @@  discard block
 block discarded – undo
385 385
 		} else {
386 386
 			$apps = $appManager->getEnabledAppsForUser($user);
387 387
 		}
388
-		$apps = array_filter($apps, function ($app) {
389
-			return $app !== 'files';//we add this manually
388
+		$apps = array_filter($apps, function($app) {
389
+			return $app !== 'files'; //we add this manually
390 390
 		});
391 391
 		sort($apps);
392 392
 		array_unshift($apps, 'files');
@@ -486,7 +486,7 @@  discard block
 block discarded – undo
486 486
 
487 487
 		$possibleApps = [];
488 488
 		foreach (OC::$APPSROOTS as $dir) {
489
-			if (file_exists($dir['path'] . '/' . $appId)) {
489
+			if (file_exists($dir['path'].'/'.$appId)) {
490 490
 				$possibleApps[] = $dir;
491 491
 			}
492 492
 		}
@@ -500,7 +500,7 @@  discard block
 block discarded – undo
500 500
 		} else {
501 501
 			$versionToLoad = [];
502 502
 			foreach ($possibleApps as $possibleApp) {
503
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
503
+				$version = self::getAppVersionByPath($possibleApp['path'].'/'.$appId);
504 504
 				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
505 505
 					$versionToLoad = [
506 506
 						'dir' => $possibleApp,
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
 		}
531 531
 
532 532
 		if (($dir = self::findAppInDirectories($appId)) != false) {
533
-			return $dir['path'] . '/' . $appId;
533
+			return $dir['path'].'/'.$appId;
534 534
 		}
535 535
 		return false;
536 536
 	}
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
 	 */
546 546
 	public static function getAppWebPath(string $appId) {
547 547
 		if (($dir = self::findAppInDirectories($appId)) != false) {
548
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
548
+			return OC::$WEBROOT.$dir['url'].'/'.$appId;
549 549
 		}
550 550
 		return false;
551 551
 	}
@@ -569,7 +569,7 @@  discard block
 block discarded – undo
569 569
 	 * @return string
570 570
 	 */
571 571
 	public static function getAppVersionByPath(string $path): string {
572
-		$infoFile = $path . '/appinfo/info.xml';
572
+		$infoFile = $path.'/appinfo/info.xml';
573 573
 		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
574 574
 		return isset($appData['version']) ? $appData['version'] : '';
575 575
 	}
@@ -669,7 +669,7 @@  discard block
 block discarded – undo
669 669
 	 * @param string $page
670 670
 	 */
671 671
 	public static function registerAdmin(string $app, string $page) {
672
-		self::$adminForms[] = $app . '/' . $page . '.php';
672
+		self::$adminForms[] = $app.'/'.$page.'.php';
673 673
 	}
674 674
 
675 675
 	/**
@@ -678,7 +678,7 @@  discard block
 block discarded – undo
678 678
 	 * @param string $page
679 679
 	 */
680 680
 	public static function registerPersonal(string $app, string $page) {
681
-		self::$personalForms[] = $app . '/' . $page . '.php';
681
+		self::$personalForms[] = $app.'/'.$page.'.php';
682 682
 	}
683 683
 
684 684
 	/**
@@ -749,14 +749,14 @@  discard block
 block discarded – undo
749 749
 
750 750
 		foreach (OC::$APPSROOTS as $apps_dir) {
751 751
 			if (!is_readable($apps_dir['path'])) {
752
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
752
+				\OCP\Util::writeLog('core', 'unable to read app folder : '.$apps_dir['path'], ILogger::WARN);
753 753
 				continue;
754 754
 			}
755 755
 			$dh = opendir($apps_dir['path']);
756 756
 
757 757
 			if (is_resource($dh)) {
758 758
 				while (($file = readdir($dh)) !== false) {
759
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
759
+					if ($file[0] != '.' and is_dir($apps_dir['path'].'/'.$file) and is_file($apps_dir['path'].'/'.$file.'/appinfo/info.xml')) {
760 760
 						$apps[] = $file;
761 761
 					}
762 762
 				}
@@ -790,12 +790,12 @@  discard block
 block discarded – undo
790 790
 			if (array_search($app, $blacklist) === false) {
791 791
 				$info = OC_App::getAppInfo($app, false, $langCode);
792 792
 				if (!is_array($info)) {
793
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
793
+					\OCP\Util::writeLog('core', 'Could not read app info file for app "'.$app.'"', ILogger::ERROR);
794 794
 					continue;
795 795
 				}
796 796
 
797 797
 				if (!isset($info['name'])) {
798
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
798
+					\OCP\Util::writeLog('core', 'App id "'.$app.'" has no name in appinfo', ILogger::ERROR);
799 799
 					continue;
800 800
 				}
801 801
 
@@ -827,12 +827,12 @@  discard block
 block discarded – undo
827 827
 
828 828
 				$appPath = self::getAppPath($app);
829 829
 				if ($appPath !== false) {
830
-					$appIcon = $appPath . '/img/' . $app . '.svg';
830
+					$appIcon = $appPath.'/img/'.$app.'.svg';
831 831
 					if (file_exists($appIcon)) {
832
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
832
+						$info['preview'] = $urlGenerator->imagePath($app, $app.'.svg');
833 833
 						$info['previewAsIcon'] = true;
834 834
 					} else {
835
-						$appIcon = $appPath . '/img/app.svg';
835
+						$appIcon = $appPath.'/img/app.svg';
836 836
 						if (file_exists($appIcon)) {
837 837
 							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
838 838
 							$info['previewAsIcon'] = true;
@@ -977,8 +977,8 @@  discard block
 block discarded – undo
977 977
 		self::registerAutoloading($appId, $appPath, true);
978 978
 		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
979 979
 
980
-		if (file_exists($appPath . '/appinfo/database.xml')) {
981
-			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
980
+		if (file_exists($appPath.'/appinfo/database.xml')) {
981
+			OC_DB::updateDbFromStructure($appPath.'/appinfo/database.xml');
982 982
 		} else {
983 983
 			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
984 984
 			$ms->migrate();
@@ -999,10 +999,10 @@  discard block
 block discarded – undo
999 999
 			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1000 1000
 		}
1001 1001
 		foreach ($appData['remote'] as $name => $path) {
1002
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1002
+			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $appId.'/'.$path);
1003 1003
 		}
1004 1004
 		foreach ($appData['public'] as $name => $path) {
1005
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1005
+			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $appId.'/'.$path);
1006 1006
 		}
1007 1007
 
1008 1008
 		self::setAppTypes($appId);
@@ -1072,17 +1072,17 @@  discard block
 block discarded – undo
1072 1072
 	public static function getStorage(string $appId) {
1073 1073
 		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1074 1074
 			if (\OC::$server->getUserSession()->isLoggedIn()) {
1075
-				$view = new \OC\Files\View('/' . OC_User::getUser());
1075
+				$view = new \OC\Files\View('/'.OC_User::getUser());
1076 1076
 				if (!$view->file_exists($appId)) {
1077 1077
 					$view->mkdir($appId);
1078 1078
 				}
1079
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1079
+				return new \OC\Files\View('/'.OC_User::getUser().'/'.$appId);
1080 1080
 			} else {
1081
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1081
+				\OCP\Util::writeLog('core', 'Can\'t get app storage, app '.$appId.', user not logged in', ILogger::ERROR);
1082 1082
 				return false;
1083 1083
 			}
1084 1084
 		} else {
1085
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1085
+			\OCP\Util::writeLog('core', 'Can\'t get app storage, app '.$appId.' not enabled', ILogger::ERROR);
1086 1086
 			return false;
1087 1087
 		}
1088 1088
 	}
@@ -1119,7 +1119,7 @@  discard block
 block discarded – undo
1119 1119
 
1120 1120
 				if ($attributeLang === $similarLang) {
1121 1121
 					$similarLangFallback = $option['@value'];
1122
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1122
+				} elseif (strpos($attributeLang, $similarLang.'_') === 0) {
1123 1123
 					if ($similarLangFallback === false) {
1124 1124
 						$similarLangFallback = $option['@value'];
1125 1125
 					}
Please login to merge, or discard this patch.