Passed
Push — master ( 9af8c0...c15172 )
by Roeland
15:34 queued 11s
created
core/Command/Db/ConvertType.php 1 patch
Indentation   +414 added lines, -414 removed lines patch added patch discarded remove patch
@@ -57,418 +57,418 @@
 block discarded – undo
57 57
 use function preg_quote;
58 58
 
59 59
 class ConvertType extends Command implements CompletionAwareInterface {
60
-	/**
61
-	 * @var \OCP\IConfig
62
-	 */
63
-	protected $config;
64
-
65
-	/**
66
-	 * @var \OC\DB\ConnectionFactory
67
-	 */
68
-	protected $connectionFactory;
69
-
70
-	/** @var array */
71
-	protected $columnTypes;
72
-
73
-	/**
74
-	 * @param \OCP\IConfig $config
75
-	 * @param \OC\DB\ConnectionFactory $connectionFactory
76
-	 */
77
-	public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
78
-		$this->config = $config;
79
-		$this->connectionFactory = $connectionFactory;
80
-		parent::__construct();
81
-	}
82
-
83
-	protected function configure() {
84
-		$this
85
-			->setName('db:convert-type')
86
-			->setDescription('Convert the Nextcloud database to the newly configured one')
87
-			->addArgument(
88
-				'type',
89
-				InputArgument::REQUIRED,
90
-				'the type of the database to convert to'
91
-			)
92
-			->addArgument(
93
-				'username',
94
-				InputArgument::REQUIRED,
95
-				'the username of the database to convert to'
96
-			)
97
-			->addArgument(
98
-				'hostname',
99
-				InputArgument::REQUIRED,
100
-				'the hostname of the database to convert to'
101
-			)
102
-			->addArgument(
103
-				'database',
104
-				InputArgument::REQUIRED,
105
-				'the name of the database to convert to'
106
-			)
107
-			->addOption(
108
-				'port',
109
-				null,
110
-				InputOption::VALUE_REQUIRED,
111
-				'the port of the database to convert to'
112
-			)
113
-			->addOption(
114
-				'password',
115
-				null,
116
-				InputOption::VALUE_REQUIRED,
117
-				'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.'
118
-			)
119
-			->addOption(
120
-				'clear-schema',
121
-				null,
122
-				InputOption::VALUE_NONE,
123
-				'remove all tables from the destination database'
124
-			)
125
-			->addOption(
126
-				'all-apps',
127
-				null,
128
-				InputOption::VALUE_NONE,
129
-				'whether to create schema for all apps instead of only installed apps'
130
-			)
131
-			->addOption(
132
-				'chunk-size',
133
-				null,
134
-				InputOption::VALUE_REQUIRED,
135
-				'the maximum number of database rows to handle in a single query, bigger tables will be handled in chunks of this size. Lower this if the process runs out of memory during conversion.',
136
-				1000
137
-			)
138
-		;
139
-	}
140
-
141
-	protected function validateInput(InputInterface $input, OutputInterface $output) {
142
-		$type = $this->connectionFactory->normalizeType($input->getArgument('type'));
143
-		if ($type === 'sqlite3') {
144
-			throw new \InvalidArgumentException(
145
-				'Converting to SQLite (sqlite3) is currently not supported.'
146
-			);
147
-		}
148
-		if ($type === $this->config->getSystemValue('dbtype', '')) {
149
-			throw new \InvalidArgumentException(sprintf(
150
-				'Can not convert from %1$s to %1$s.',
151
-				$type
152
-			));
153
-		}
154
-		if ($type === 'oci' && $input->getOption('clear-schema')) {
155
-			// Doctrine unconditionally tries (at least in version 2.3)
156
-			// to drop sequence triggers when dropping a table, even though
157
-			// such triggers may not exist. This results in errors like
158
-			// "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist".
159
-			throw new \InvalidArgumentException(
160
-				'The --clear-schema option is not supported when converting to Oracle (oci).'
161
-			);
162
-		}
163
-	}
164
-
165
-	protected function readPassword(InputInterface $input, OutputInterface $output) {
166
-		// Explicitly specified password
167
-		if ($input->getOption('password')) {
168
-			return;
169
-		}
170
-
171
-		// Read from stdin. stream_set_blocking is used to prevent blocking
172
-		// when nothing is passed via stdin.
173
-		stream_set_blocking(STDIN, 0);
174
-		$password = file_get_contents('php://stdin');
175
-		stream_set_blocking(STDIN, 1);
176
-		if (trim($password) !== '') {
177
-			$input->setOption('password', $password);
178
-			return;
179
-		}
180
-
181
-		// Read password by interacting
182
-		if ($input->isInteractive()) {
183
-			/** @var QuestionHelper $helper */
184
-			$helper = $this->getHelper('question');
185
-			$question = new Question('What is the database password?');
186
-			$question->setHidden(true);
187
-			$question->setHiddenFallback(false);
188
-			$password = $helper->ask($input, $output, $question);
189
-			$input->setOption('password', $password);
190
-			return;
191
-		}
192
-	}
193
-
194
-	protected function execute(InputInterface $input, OutputInterface $output): int {
195
-		$this->validateInput($input, $output);
196
-		$this->readPassword($input, $output);
197
-
198
-		/** @var Connection $fromDB */
199
-		$fromDB = \OC::$server->get(Connection::class);
200
-		$toDB = $this->getToDBConnection($input, $output);
201
-
202
-		if ($input->getOption('clear-schema')) {
203
-			$this->clearSchema($toDB, $input, $output);
204
-		}
205
-
206
-		$this->createSchema($fromDB, $toDB, $input, $output);
207
-
208
-		$toTables = $this->getTables($toDB);
209
-		$fromTables = $this->getTables($fromDB);
210
-
211
-		// warn/fail if there are more tables in 'from' database
212
-		$extraFromTables = array_diff($fromTables, $toTables);
213
-		if (!empty($extraFromTables)) {
214
-			$output->writeln('<comment>The following tables will not be converted:</comment>');
215
-			$output->writeln($extraFromTables);
216
-			if (!$input->getOption('all-apps')) {
217
-				$output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
218
-				$output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
219
-			}
220
-
221
-			$continueConversion = !$input->isInteractive(); // assume yes for --no-interaction and no otherwise.
222
-			$question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', $continueConversion);
223
-
224
-			/** @var QuestionHelper $helper */
225
-			$helper = $this->getHelper('question');
226
-
227
-			if (!$helper->ask($input, $output, $question)) {
228
-				return 1;
229
-			}
230
-		}
231
-		$intersectingTables = array_intersect($toTables, $fromTables);
232
-		$this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output);
233
-		return 0;
234
-	}
235
-
236
-	protected function createSchema(Connection $fromDB, Connection $toDB, InputInterface $input, OutputInterface $output) {
237
-		$output->writeln('<info>Creating schema in new database</info>');
238
-
239
-		$fromMS = new MigrationService('core', $fromDB);
240
-		$currentMigration = $fromMS->getMigration('current');
241
-		if ($currentMigration !== '0') {
242
-			$toMS = new MigrationService('core', $toDB);
243
-			$toMS->migrate($currentMigration);
244
-		}
245
-
246
-		$apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
247
-		foreach ($apps as $app) {
248
-			$output->writeln('<info> - '.$app.'</info>');
249
-			// Make sure autoloading works...
250
-			\OC_App::loadApp($app);
251
-			$fromMS = new MigrationService($app, $fromDB);
252
-			$currentMigration = $fromMS->getMigration('current');
253
-			if ($currentMigration !== '0') {
254
-				$toMS = new MigrationService($app, $toDB);
255
-				$toMS->migrate($currentMigration, true);
256
-			}
257
-		}
258
-	}
259
-
260
-	protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
261
-		$type = $input->getArgument('type');
262
-		$connectionParams = $this->connectionFactory->createConnectionParams();
263
-		$connectionParams = array_merge($connectionParams, [
264
-			'host' => $input->getArgument('hostname'),
265
-			'user' => $input->getArgument('username'),
266
-			'password' => $input->getOption('password'),
267
-			'dbname' => $input->getArgument('database'),
268
-		]);
269
-		if ($input->getOption('port')) {
270
-			$connectionParams['port'] = $input->getOption('port');
271
-		}
272
-		return $this->connectionFactory->getConnection($type, $connectionParams);
273
-	}
274
-
275
-	protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) {
276
-		$toTables = $this->getTables($db);
277
-		if (!empty($toTables)) {
278
-			$output->writeln('<info>Clearing schema in new database</info>');
279
-		}
280
-		foreach ($toTables as $table) {
281
-			$db->getSchemaManager()->dropTable($table);
282
-		}
283
-	}
284
-
285
-	protected function getTables(Connection $db) {
286
-		$db->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
287
-			/** @var string|AbstractAsset $asset */
288
-			$filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
289
-			if ($asset instanceof AbstractAsset) {
290
-				return preg_match($filterExpression, $asset->getName()) !== false;
291
-			}
292
-			return preg_match($filterExpression, $asset) !== false;
293
-		});
294
-		return $db->getSchemaManager()->listTableNames();
295
-	}
296
-
297
-	/**
298
-	 * @param Connection $fromDB
299
-	 * @param Connection $toDB
300
-	 * @param Table $table
301
-	 * @param InputInterface $input
302
-	 * @param OutputInterface $output
303
-	 */
304
-	protected function copyTable(Connection $fromDB, Connection $toDB, Table $table, InputInterface $input, OutputInterface $output) {
305
-		if ($table->getName() === $toDB->getPrefix() . 'migrations') {
306
-			$output->writeln('<comment>Skipping migrations table because it was already filled by running the migrations</comment>');
307
-			return;
308
-		}
309
-
310
-		$chunkSize = $input->getOption('chunk-size');
311
-
312
-		$query = $fromDB->getQueryBuilder();
313
-		$query->automaticTablePrefix(false);
314
-		$query->select($query->func()->count('*', 'num_entries'))
315
-			->from($table->getName());
316
-		$result = $query->execute();
317
-		$count = $result->fetchOne();
318
-		$result->closeCursor();
319
-
320
-		$numChunks = ceil($count / $chunkSize);
321
-		if ($numChunks > 1) {
322
-			$output->writeln('chunked query, ' . $numChunks . ' chunks');
323
-		}
324
-
325
-		$progress = new ProgressBar($output, $count);
326
-		$progress->setFormat('very_verbose');
327
-		$progress->start();
328
-		$redraw = $count > $chunkSize ? 100 : ($count > 100 ? 5 : 1);
329
-		$progress->setRedrawFrequency($redraw);
330
-
331
-		$query = $fromDB->getQueryBuilder();
332
-		$query->automaticTablePrefix(false);
333
-		$query->select('*')
334
-			->from($table->getName())
335
-			->setMaxResults($chunkSize);
336
-
337
-		try {
338
-			$orderColumns = $table->getPrimaryKeyColumns();
339
-		} catch (Exception $e) {
340
-			$orderColumns = [];
341
-			foreach ($table->getColumns() as $column) {
342
-				$orderColumns[] = $column->getName();
343
-			}
344
-		}
345
-
346
-		foreach ($orderColumns as $column) {
347
-			$query->addOrderBy($column);
348
-		}
349
-
350
-		$insertQuery = $toDB->getQueryBuilder();
351
-		$insertQuery->automaticTablePrefix(false);
352
-		$insertQuery->insert($table->getName());
353
-		$parametersCreated = false;
354
-
355
-		for ($chunk = 0; $chunk < $numChunks; $chunk++) {
356
-			$query->setFirstResult($chunk * $chunkSize);
357
-
358
-			$result = $query->execute();
359
-
360
-			while ($row = $result->fetch()) {
361
-				$progress->advance();
362
-				if (!$parametersCreated) {
363
-					foreach ($row as $key => $value) {
364
-						$insertQuery->setValue($key, $insertQuery->createParameter($key));
365
-					}
366
-					$parametersCreated = true;
367
-				}
368
-
369
-				foreach ($row as $key => $value) {
370
-					$type = $this->getColumnType($table, $key);
371
-					if ($type !== false) {
372
-						$insertQuery->setParameter($key, $value, $type);
373
-					} else {
374
-						$insertQuery->setParameter($key, $value);
375
-					}
376
-				}
377
-				$insertQuery->execute();
378
-			}
379
-			$result->closeCursor();
380
-		}
381
-		$progress->finish();
382
-		$output->writeln('');
383
-	}
384
-
385
-	protected function getColumnType(Table $table, $columnName) {
386
-		$tableName = $table->getName();
387
-		if (isset($this->columnTypes[$tableName][$columnName])) {
388
-			return $this->columnTypes[$tableName][$columnName];
389
-		}
390
-
391
-		$type = $table->getColumn($columnName)->getType()->getName();
392
-
393
-		switch ($type) {
394
-			case Types::BLOB:
395
-			case Types::TEXT:
396
-				$this->columnTypes[$tableName][$columnName] = IQueryBuilder::PARAM_LOB;
397
-				break;
398
-			case Types::BOOLEAN:
399
-				$this->columnTypes[$tableName][$columnName] = IQueryBuilder::PARAM_BOOL;
400
-				break;
401
-			default:
402
-				$this->columnTypes[$tableName][$columnName] = false;
403
-		}
404
-
405
-		return $this->columnTypes[$tableName][$columnName];
406
-	}
407
-
408
-	protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) {
409
-		$this->config->setSystemValue('maintenance', true);
410
-		$schema = $fromDB->createSchema();
411
-
412
-		try {
413
-			// copy table rows
414
-			foreach ($tables as $table) {
415
-				$output->writeln('<info> - '.$table.'</info>');
416
-				$this->copyTable($fromDB, $toDB, $schema->getTable($table), $input, $output);
417
-			}
418
-			if ($input->getArgument('type') === 'pgsql') {
419
-				$tools = new \OC\DB\PgSqlTools($this->config);
420
-				$tools->resynchronizeDatabaseSequences($toDB);
421
-			}
422
-			// save new database config
423
-			$this->saveDBInfo($input);
424
-		} catch (\Exception $e) {
425
-			$this->config->setSystemValue('maintenance', false);
426
-			throw $e;
427
-		}
428
-		$this->config->setSystemValue('maintenance', false);
429
-	}
430
-
431
-	protected function saveDBInfo(InputInterface $input) {
432
-		$type = $input->getArgument('type');
433
-		$username = $input->getArgument('username');
434
-		$dbHost = $input->getArgument('hostname');
435
-		$dbName = $input->getArgument('database');
436
-		$password = $input->getOption('password');
437
-		if ($input->getOption('port')) {
438
-			$dbHost .= ':'.$input->getOption('port');
439
-		}
440
-
441
-		$this->config->setSystemValues([
442
-			'dbtype' => $type,
443
-			'dbname' => $dbName,
444
-			'dbhost' => $dbHost,
445
-			'dbuser' => $username,
446
-			'dbpassword' => $password,
447
-		]);
448
-	}
449
-
450
-	/**
451
-	 * Return possible values for the named option
452
-	 *
453
-	 * @param string $optionName
454
-	 * @param CompletionContext $context
455
-	 * @return string[]
456
-	 */
457
-	public function completeOptionValues($optionName, CompletionContext $context) {
458
-		return [];
459
-	}
460
-
461
-	/**
462
-	 * Return possible values for the named argument
463
-	 *
464
-	 * @param string $argumentName
465
-	 * @param CompletionContext $context
466
-	 * @return string[]
467
-	 */
468
-	public function completeArgumentValues($argumentName, CompletionContext $context) {
469
-		if ($argumentName === 'type') {
470
-			return ['mysql', 'oci', 'pgsql'];
471
-		}
472
-		return [];
473
-	}
60
+    /**
61
+     * @var \OCP\IConfig
62
+     */
63
+    protected $config;
64
+
65
+    /**
66
+     * @var \OC\DB\ConnectionFactory
67
+     */
68
+    protected $connectionFactory;
69
+
70
+    /** @var array */
71
+    protected $columnTypes;
72
+
73
+    /**
74
+     * @param \OCP\IConfig $config
75
+     * @param \OC\DB\ConnectionFactory $connectionFactory
76
+     */
77
+    public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
78
+        $this->config = $config;
79
+        $this->connectionFactory = $connectionFactory;
80
+        parent::__construct();
81
+    }
82
+
83
+    protected function configure() {
84
+        $this
85
+            ->setName('db:convert-type')
86
+            ->setDescription('Convert the Nextcloud database to the newly configured one')
87
+            ->addArgument(
88
+                'type',
89
+                InputArgument::REQUIRED,
90
+                'the type of the database to convert to'
91
+            )
92
+            ->addArgument(
93
+                'username',
94
+                InputArgument::REQUIRED,
95
+                'the username of the database to convert to'
96
+            )
97
+            ->addArgument(
98
+                'hostname',
99
+                InputArgument::REQUIRED,
100
+                'the hostname of the database to convert to'
101
+            )
102
+            ->addArgument(
103
+                'database',
104
+                InputArgument::REQUIRED,
105
+                'the name of the database to convert to'
106
+            )
107
+            ->addOption(
108
+                'port',
109
+                null,
110
+                InputOption::VALUE_REQUIRED,
111
+                'the port of the database to convert to'
112
+            )
113
+            ->addOption(
114
+                'password',
115
+                null,
116
+                InputOption::VALUE_REQUIRED,
117
+                'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.'
118
+            )
119
+            ->addOption(
120
+                'clear-schema',
121
+                null,
122
+                InputOption::VALUE_NONE,
123
+                'remove all tables from the destination database'
124
+            )
125
+            ->addOption(
126
+                'all-apps',
127
+                null,
128
+                InputOption::VALUE_NONE,
129
+                'whether to create schema for all apps instead of only installed apps'
130
+            )
131
+            ->addOption(
132
+                'chunk-size',
133
+                null,
134
+                InputOption::VALUE_REQUIRED,
135
+                'the maximum number of database rows to handle in a single query, bigger tables will be handled in chunks of this size. Lower this if the process runs out of memory during conversion.',
136
+                1000
137
+            )
138
+        ;
139
+    }
140
+
141
+    protected function validateInput(InputInterface $input, OutputInterface $output) {
142
+        $type = $this->connectionFactory->normalizeType($input->getArgument('type'));
143
+        if ($type === 'sqlite3') {
144
+            throw new \InvalidArgumentException(
145
+                'Converting to SQLite (sqlite3) is currently not supported.'
146
+            );
147
+        }
148
+        if ($type === $this->config->getSystemValue('dbtype', '')) {
149
+            throw new \InvalidArgumentException(sprintf(
150
+                'Can not convert from %1$s to %1$s.',
151
+                $type
152
+            ));
153
+        }
154
+        if ($type === 'oci' && $input->getOption('clear-schema')) {
155
+            // Doctrine unconditionally tries (at least in version 2.3)
156
+            // to drop sequence triggers when dropping a table, even though
157
+            // such triggers may not exist. This results in errors like
158
+            // "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist".
159
+            throw new \InvalidArgumentException(
160
+                'The --clear-schema option is not supported when converting to Oracle (oci).'
161
+            );
162
+        }
163
+    }
164
+
165
+    protected function readPassword(InputInterface $input, OutputInterface $output) {
166
+        // Explicitly specified password
167
+        if ($input->getOption('password')) {
168
+            return;
169
+        }
170
+
171
+        // Read from stdin. stream_set_blocking is used to prevent blocking
172
+        // when nothing is passed via stdin.
173
+        stream_set_blocking(STDIN, 0);
174
+        $password = file_get_contents('php://stdin');
175
+        stream_set_blocking(STDIN, 1);
176
+        if (trim($password) !== '') {
177
+            $input->setOption('password', $password);
178
+            return;
179
+        }
180
+
181
+        // Read password by interacting
182
+        if ($input->isInteractive()) {
183
+            /** @var QuestionHelper $helper */
184
+            $helper = $this->getHelper('question');
185
+            $question = new Question('What is the database password?');
186
+            $question->setHidden(true);
187
+            $question->setHiddenFallback(false);
188
+            $password = $helper->ask($input, $output, $question);
189
+            $input->setOption('password', $password);
190
+            return;
191
+        }
192
+    }
193
+
194
+    protected function execute(InputInterface $input, OutputInterface $output): int {
195
+        $this->validateInput($input, $output);
196
+        $this->readPassword($input, $output);
197
+
198
+        /** @var Connection $fromDB */
199
+        $fromDB = \OC::$server->get(Connection::class);
200
+        $toDB = $this->getToDBConnection($input, $output);
201
+
202
+        if ($input->getOption('clear-schema')) {
203
+            $this->clearSchema($toDB, $input, $output);
204
+        }
205
+
206
+        $this->createSchema($fromDB, $toDB, $input, $output);
207
+
208
+        $toTables = $this->getTables($toDB);
209
+        $fromTables = $this->getTables($fromDB);
210
+
211
+        // warn/fail if there are more tables in 'from' database
212
+        $extraFromTables = array_diff($fromTables, $toTables);
213
+        if (!empty($extraFromTables)) {
214
+            $output->writeln('<comment>The following tables will not be converted:</comment>');
215
+            $output->writeln($extraFromTables);
216
+            if (!$input->getOption('all-apps')) {
217
+                $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
218
+                $output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
219
+            }
220
+
221
+            $continueConversion = !$input->isInteractive(); // assume yes for --no-interaction and no otherwise.
222
+            $question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', $continueConversion);
223
+
224
+            /** @var QuestionHelper $helper */
225
+            $helper = $this->getHelper('question');
226
+
227
+            if (!$helper->ask($input, $output, $question)) {
228
+                return 1;
229
+            }
230
+        }
231
+        $intersectingTables = array_intersect($toTables, $fromTables);
232
+        $this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output);
233
+        return 0;
234
+    }
235
+
236
+    protected function createSchema(Connection $fromDB, Connection $toDB, InputInterface $input, OutputInterface $output) {
237
+        $output->writeln('<info>Creating schema in new database</info>');
238
+
239
+        $fromMS = new MigrationService('core', $fromDB);
240
+        $currentMigration = $fromMS->getMigration('current');
241
+        if ($currentMigration !== '0') {
242
+            $toMS = new MigrationService('core', $toDB);
243
+            $toMS->migrate($currentMigration);
244
+        }
245
+
246
+        $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
247
+        foreach ($apps as $app) {
248
+            $output->writeln('<info> - '.$app.'</info>');
249
+            // Make sure autoloading works...
250
+            \OC_App::loadApp($app);
251
+            $fromMS = new MigrationService($app, $fromDB);
252
+            $currentMigration = $fromMS->getMigration('current');
253
+            if ($currentMigration !== '0') {
254
+                $toMS = new MigrationService($app, $toDB);
255
+                $toMS->migrate($currentMigration, true);
256
+            }
257
+        }
258
+    }
259
+
260
+    protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
261
+        $type = $input->getArgument('type');
262
+        $connectionParams = $this->connectionFactory->createConnectionParams();
263
+        $connectionParams = array_merge($connectionParams, [
264
+            'host' => $input->getArgument('hostname'),
265
+            'user' => $input->getArgument('username'),
266
+            'password' => $input->getOption('password'),
267
+            'dbname' => $input->getArgument('database'),
268
+        ]);
269
+        if ($input->getOption('port')) {
270
+            $connectionParams['port'] = $input->getOption('port');
271
+        }
272
+        return $this->connectionFactory->getConnection($type, $connectionParams);
273
+    }
274
+
275
+    protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) {
276
+        $toTables = $this->getTables($db);
277
+        if (!empty($toTables)) {
278
+            $output->writeln('<info>Clearing schema in new database</info>');
279
+        }
280
+        foreach ($toTables as $table) {
281
+            $db->getSchemaManager()->dropTable($table);
282
+        }
283
+    }
284
+
285
+    protected function getTables(Connection $db) {
286
+        $db->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
287
+            /** @var string|AbstractAsset $asset */
288
+            $filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
289
+            if ($asset instanceof AbstractAsset) {
290
+                return preg_match($filterExpression, $asset->getName()) !== false;
291
+            }
292
+            return preg_match($filterExpression, $asset) !== false;
293
+        });
294
+        return $db->getSchemaManager()->listTableNames();
295
+    }
296
+
297
+    /**
298
+     * @param Connection $fromDB
299
+     * @param Connection $toDB
300
+     * @param Table $table
301
+     * @param InputInterface $input
302
+     * @param OutputInterface $output
303
+     */
304
+    protected function copyTable(Connection $fromDB, Connection $toDB, Table $table, InputInterface $input, OutputInterface $output) {
305
+        if ($table->getName() === $toDB->getPrefix() . 'migrations') {
306
+            $output->writeln('<comment>Skipping migrations table because it was already filled by running the migrations</comment>');
307
+            return;
308
+        }
309
+
310
+        $chunkSize = $input->getOption('chunk-size');
311
+
312
+        $query = $fromDB->getQueryBuilder();
313
+        $query->automaticTablePrefix(false);
314
+        $query->select($query->func()->count('*', 'num_entries'))
315
+            ->from($table->getName());
316
+        $result = $query->execute();
317
+        $count = $result->fetchOne();
318
+        $result->closeCursor();
319
+
320
+        $numChunks = ceil($count / $chunkSize);
321
+        if ($numChunks > 1) {
322
+            $output->writeln('chunked query, ' . $numChunks . ' chunks');
323
+        }
324
+
325
+        $progress = new ProgressBar($output, $count);
326
+        $progress->setFormat('very_verbose');
327
+        $progress->start();
328
+        $redraw = $count > $chunkSize ? 100 : ($count > 100 ? 5 : 1);
329
+        $progress->setRedrawFrequency($redraw);
330
+
331
+        $query = $fromDB->getQueryBuilder();
332
+        $query->automaticTablePrefix(false);
333
+        $query->select('*')
334
+            ->from($table->getName())
335
+            ->setMaxResults($chunkSize);
336
+
337
+        try {
338
+            $orderColumns = $table->getPrimaryKeyColumns();
339
+        } catch (Exception $e) {
340
+            $orderColumns = [];
341
+            foreach ($table->getColumns() as $column) {
342
+                $orderColumns[] = $column->getName();
343
+            }
344
+        }
345
+
346
+        foreach ($orderColumns as $column) {
347
+            $query->addOrderBy($column);
348
+        }
349
+
350
+        $insertQuery = $toDB->getQueryBuilder();
351
+        $insertQuery->automaticTablePrefix(false);
352
+        $insertQuery->insert($table->getName());
353
+        $parametersCreated = false;
354
+
355
+        for ($chunk = 0; $chunk < $numChunks; $chunk++) {
356
+            $query->setFirstResult($chunk * $chunkSize);
357
+
358
+            $result = $query->execute();
359
+
360
+            while ($row = $result->fetch()) {
361
+                $progress->advance();
362
+                if (!$parametersCreated) {
363
+                    foreach ($row as $key => $value) {
364
+                        $insertQuery->setValue($key, $insertQuery->createParameter($key));
365
+                    }
366
+                    $parametersCreated = true;
367
+                }
368
+
369
+                foreach ($row as $key => $value) {
370
+                    $type = $this->getColumnType($table, $key);
371
+                    if ($type !== false) {
372
+                        $insertQuery->setParameter($key, $value, $type);
373
+                    } else {
374
+                        $insertQuery->setParameter($key, $value);
375
+                    }
376
+                }
377
+                $insertQuery->execute();
378
+            }
379
+            $result->closeCursor();
380
+        }
381
+        $progress->finish();
382
+        $output->writeln('');
383
+    }
384
+
385
+    protected function getColumnType(Table $table, $columnName) {
386
+        $tableName = $table->getName();
387
+        if (isset($this->columnTypes[$tableName][$columnName])) {
388
+            return $this->columnTypes[$tableName][$columnName];
389
+        }
390
+
391
+        $type = $table->getColumn($columnName)->getType()->getName();
392
+
393
+        switch ($type) {
394
+            case Types::BLOB:
395
+            case Types::TEXT:
396
+                $this->columnTypes[$tableName][$columnName] = IQueryBuilder::PARAM_LOB;
397
+                break;
398
+            case Types::BOOLEAN:
399
+                $this->columnTypes[$tableName][$columnName] = IQueryBuilder::PARAM_BOOL;
400
+                break;
401
+            default:
402
+                $this->columnTypes[$tableName][$columnName] = false;
403
+        }
404
+
405
+        return $this->columnTypes[$tableName][$columnName];
406
+    }
407
+
408
+    protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) {
409
+        $this->config->setSystemValue('maintenance', true);
410
+        $schema = $fromDB->createSchema();
411
+
412
+        try {
413
+            // copy table rows
414
+            foreach ($tables as $table) {
415
+                $output->writeln('<info> - '.$table.'</info>');
416
+                $this->copyTable($fromDB, $toDB, $schema->getTable($table), $input, $output);
417
+            }
418
+            if ($input->getArgument('type') === 'pgsql') {
419
+                $tools = new \OC\DB\PgSqlTools($this->config);
420
+                $tools->resynchronizeDatabaseSequences($toDB);
421
+            }
422
+            // save new database config
423
+            $this->saveDBInfo($input);
424
+        } catch (\Exception $e) {
425
+            $this->config->setSystemValue('maintenance', false);
426
+            throw $e;
427
+        }
428
+        $this->config->setSystemValue('maintenance', false);
429
+    }
430
+
431
+    protected function saveDBInfo(InputInterface $input) {
432
+        $type = $input->getArgument('type');
433
+        $username = $input->getArgument('username');
434
+        $dbHost = $input->getArgument('hostname');
435
+        $dbName = $input->getArgument('database');
436
+        $password = $input->getOption('password');
437
+        if ($input->getOption('port')) {
438
+            $dbHost .= ':'.$input->getOption('port');
439
+        }
440
+
441
+        $this->config->setSystemValues([
442
+            'dbtype' => $type,
443
+            'dbname' => $dbName,
444
+            'dbhost' => $dbHost,
445
+            'dbuser' => $username,
446
+            'dbpassword' => $password,
447
+        ]);
448
+    }
449
+
450
+    /**
451
+     * Return possible values for the named option
452
+     *
453
+     * @param string $optionName
454
+     * @param CompletionContext $context
455
+     * @return string[]
456
+     */
457
+    public function completeOptionValues($optionName, CompletionContext $context) {
458
+        return [];
459
+    }
460
+
461
+    /**
462
+     * Return possible values for the named argument
463
+     *
464
+     * @param string $argumentName
465
+     * @param CompletionContext $context
466
+     * @return string[]
467
+     */
468
+    public function completeArgumentValues($argumentName, CompletionContext $context) {
469
+        if ($argumentName === 'type') {
470
+            return ['mysql', 'oci', 'pgsql'];
471
+        }
472
+        return [];
473
+    }
474 474
 }
Please login to merge, or discard this patch.
core/register_command.php 1 patch
Indentation   +131 added lines, -131 removed lines patch added patch discarded remove patch
@@ -54,146 +54,146 @@
 block discarded – undo
54 54
 $application->add(new OC\Core\Command\App\CheckCode());
55 55
 $application->add(new OC\Core\Command\L10n\CreateJs());
56 56
 $application->add(new \OC\Core\Command\Integrity\SignApp(
57
-		\OC::$server->getIntegrityCodeChecker(),
58
-		new \OC\IntegrityCheck\Helpers\FileAccessHelper(),
59
-		\OC::$server->getURLGenerator()
57
+        \OC::$server->getIntegrityCodeChecker(),
58
+        new \OC\IntegrityCheck\Helpers\FileAccessHelper(),
59
+        \OC::$server->getURLGenerator()
60 60
 ));
61 61
 $application->add(new \OC\Core\Command\Integrity\SignCore(
62
-		\OC::$server->getIntegrityCodeChecker(),
63
-		new \OC\IntegrityCheck\Helpers\FileAccessHelper()
62
+        \OC::$server->getIntegrityCodeChecker(),
63
+        new \OC\IntegrityCheck\Helpers\FileAccessHelper()
64 64
 ));
65 65
 $application->add(new \OC\Core\Command\Integrity\CheckApp(
66
-		\OC::$server->getIntegrityCodeChecker()
66
+        \OC::$server->getIntegrityCodeChecker()
67 67
 ));
68 68
 $application->add(new \OC\Core\Command\Integrity\CheckCore(
69
-		\OC::$server->getIntegrityCodeChecker()
69
+        \OC::$server->getIntegrityCodeChecker()
70 70
 ));
71 71
 
72 72
 
73 73
 if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
74
-	$application->add(new OC\Core\Command\App\Disable(\OC::$server->getAppManager()));
75
-	$application->add(new OC\Core\Command\App\Enable(\OC::$server->getAppManager(), \OC::$server->getGroupManager()));
76
-	$application->add(new OC\Core\Command\App\Install());
77
-	$application->add(new OC\Core\Command\App\GetPath());
78
-	$application->add(new OC\Core\Command\App\ListApps(\OC::$server->getAppManager()));
79
-	$application->add(new OC\Core\Command\App\Remove(\OC::$server->getAppManager(), \OC::$server->query(\OC\Installer::class), \OC::$server->getLogger()));
80
-	$application->add(\OC::$server->query(\OC\Core\Command\App\Update::class));
81
-
82
-	$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Cleanup::class));
83
-	$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enforce::class));
84
-	$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enable::class));
85
-	$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Disable::class));
86
-	$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\State::class));
87
-
88
-	$application->add(new OC\Core\Command\Background\Cron(\OC::$server->getConfig()));
89
-	$application->add(new OC\Core\Command\Background\WebCron(\OC::$server->getConfig()));
90
-	$application->add(new OC\Core\Command\Background\Ajax(\OC::$server->getConfig()));
91
-
92
-	$application->add(\OC::$server->query(\OC\Core\Command\Broadcast\Test::class));
93
-
94
-	$application->add(new OC\Core\Command\Config\App\DeleteConfig(\OC::$server->getConfig()));
95
-	$application->add(new OC\Core\Command\Config\App\GetConfig(\OC::$server->getConfig()));
96
-	$application->add(new OC\Core\Command\Config\App\SetConfig(\OC::$server->getConfig()));
97
-	$application->add(new OC\Core\Command\Config\Import(\OC::$server->getConfig()));
98
-	$application->add(new OC\Core\Command\Config\ListConfigs(\OC::$server->getSystemConfig(), \OC::$server->getAppConfig()));
99
-	$application->add(new OC\Core\Command\Config\System\DeleteConfig(\OC::$server->getSystemConfig()));
100
-	$application->add(new OC\Core\Command\Config\System\GetConfig(\OC::$server->getSystemConfig()));
101
-	$application->add(new OC\Core\Command\Config\System\SetConfig(\OC::$server->getSystemConfig()));
102
-
103
-	$application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig())));
104
-	$application->add(new OC\Core\Command\Db\ConvertMysqlToMB4(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection(), \OC::$server->getURLGenerator(), \OC::$server->getLogger()));
105
-	$application->add(new OC\Core\Command\Db\ConvertFilecacheBigInt(\OC::$server->get(\OC\DB\Connection::class)));
106
-	$application->add(new OC\Core\Command\Db\AddMissingIndices(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
107
-	$application->add(new OC\Core\Command\Db\AddMissingColumns(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
108
-	$application->add(new OC\Core\Command\Db\AddMissingPrimaryKeys(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
109
-	$application->add(new OC\Core\Command\Db\Migrations\StatusCommand(\OC::$server->get(\OC\DB\Connection::class)));
110
-	$application->add(new OC\Core\Command\Db\Migrations\MigrateCommand(\OC::$server->get(\OC\DB\Connection::class)));
111
-	$application->add(new OC\Core\Command\Db\Migrations\GenerateCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getAppManager()));
112
-	$application->add(new OC\Core\Command\Db\Migrations\ExecuteCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getConfig()));
113
-
114
-	$application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig()));
115
-	$application->add(new OC\Core\Command\Encryption\Enable(\OC::$server->getConfig(), \OC::$server->getEncryptionManager()));
116
-	$application->add(new OC\Core\Command\Encryption\ListModules(\OC::$server->getEncryptionManager(), \OC::$server->getConfig()));
117
-	$application->add(new OC\Core\Command\Encryption\SetDefaultModule(\OC::$server->getEncryptionManager(), \OC::$server->getConfig()));
118
-	$application->add(new OC\Core\Command\Encryption\Status(\OC::$server->getEncryptionManager()));
119
-	$application->add(new OC\Core\Command\Encryption\EncryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getAppManager(), \OC::$server->getConfig(), new \Symfony\Component\Console\Helper\QuestionHelper()));
120
-	$application->add(new OC\Core\Command\Encryption\DecryptAll(
121
-		\OC::$server->getEncryptionManager(),
122
-		\OC::$server->getAppManager(),
123
-		\OC::$server->getConfig(),
124
-		new \OC\Encryption\DecryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getUserManager(), new \OC\Files\View()),
125
-		new \Symfony\Component\Console\Helper\QuestionHelper())
126
-	);
127
-
128
-	$application->add(new OC\Core\Command\Log\Manage(\OC::$server->getConfig()));
129
-	$application->add(new OC\Core\Command\Log\File(\OC::$server->getConfig()));
130
-
131
-	$view = new \OC\Files\View();
132
-	$util = new \OC\Encryption\Util(
133
-		$view,
134
-		\OC::$server->getUserManager(),
135
-		\OC::$server->getGroupManager(),
136
-		\OC::$server->getConfig()
137
-	);
138
-	$application->add(new OC\Core\Command\Encryption\ChangeKeyStorageRoot(
139
-			$view,
140
-			\OC::$server->getUserManager(),
141
-			\OC::$server->getConfig(),
142
-			$util,
143
-			new \Symfony\Component\Console\Helper\QuestionHelper()
144
-		)
145
-	);
146
-	$application->add(new OC\Core\Command\Encryption\ShowKeyStorageRoot($util));
147
-	$application->add(new OC\Core\Command\Encryption\MigrateKeyStorage(
148
-			$view,
149
-			\OC::$server->getUserManager(),
150
-			\OC::$server->getConfig(),
151
-			$util,
152
-			\OC::$server->getCrypto()
153
-		)
154
-	);
155
-
156
-	$application->add(new OC\Core\Command\Maintenance\DataFingerprint(\OC::$server->getConfig(), new \OC\AppFramework\Utility\TimeFactory()));
157
-	$application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateDB(\OC::$server->getMimeTypeDetector(), \OC::$server->getMimeTypeLoader()));
158
-	$application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateJS(\OC::$server->getMimeTypeDetector()));
159
-	$application->add(new OC\Core\Command\Maintenance\Mode(\OC::$server->getConfig()));
160
-	$application->add(new OC\Core\Command\Maintenance\UpdateHtaccess());
161
-	$application->add(new OC\Core\Command\Maintenance\UpdateTheme(\OC::$server->getMimeTypeDetector(), \OC::$server->getMemCacheFactory()));
162
-
163
-	$application->add(new OC\Core\Command\Upgrade(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->query(\OC\Installer::class)));
164
-	$application->add(new OC\Core\Command\Maintenance\Repair(
165
-		new \OC\Repair([], \OC::$server->getEventDispatcher(), \OC::$server->get(LoggerInterface::class)),
166
-		\OC::$server->getConfig(),
167
-		\OC::$server->getEventDispatcher(),
168
-		\OC::$server->getAppManager()
169
-	));
170
-
171
-	$application->add(\OC::$server->query(\OC\Core\Command\Preview\Repair::class));
172
-	$application->add(\OC::$server->query(\OC\Core\Command\Preview\ResetRenderedTexts::class));
173
-
174
-	$application->add(new OC\Core\Command\User\Add(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
175
-	$application->add(new OC\Core\Command\User\Delete(\OC::$server->getUserManager()));
176
-	$application->add(new OC\Core\Command\User\Disable(\OC::$server->getUserManager()));
177
-	$application->add(new OC\Core\Command\User\Enable(\OC::$server->getUserManager()));
178
-	$application->add(new OC\Core\Command\User\LastSeen(\OC::$server->getUserManager()));
179
-	$application->add(\OC::$server->get(\OC\Core\Command\User\Report::class));
180
-	$application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager()));
181
-	$application->add(new OC\Core\Command\User\Setting(\OC::$server->getUserManager(), \OC::$server->getConfig(), \OC::$server->getDatabaseConnection()));
182
-	$application->add(new OC\Core\Command\User\ListCommand(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
183
-	$application->add(new OC\Core\Command\User\Info(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
184
-	$application->add(new OC\Core\Command\User\AddAppPassword(\OC::$server->get(\OCP\IUserManager::class), \OC::$server->get(\OC\Authentication\Token\IProvider::class), \OC::$server->get(\OCP\Security\ISecureRandom::class), \OC::$server->get(\OCP\Security\ICrypto::class)));
185
-
186
-	$application->add(new OC\Core\Command\Group\Add(\OC::$server->getGroupManager()));
187
-	$application->add(new OC\Core\Command\Group\Delete(\OC::$server->getGroupManager()));
188
-	$application->add(new OC\Core\Command\Group\ListCommand(\OC::$server->getGroupManager()));
189
-	$application->add(new OC\Core\Command\Group\AddUser(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
190
-	$application->add(new OC\Core\Command\Group\RemoveUser(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
191
-	$application->add(new OC\Core\Command\Group\Info(\OC::$server->get(\OCP\IGroupManager::class)));
192
-
193
-	$application->add(new OC\Core\Command\Security\ListCertificates(\OC::$server->getCertificateManager(), \OC::$server->getL10N('core')));
194
-	$application->add(new OC\Core\Command\Security\ImportCertificate(\OC::$server->getCertificateManager()));
195
-	$application->add(new OC\Core\Command\Security\RemoveCertificate(\OC::$server->getCertificateManager()));
196
-	$application->add(new OC\Core\Command\Security\ResetBruteforceAttempts(\OC::$server->getBruteForceThrottler()));
74
+    $application->add(new OC\Core\Command\App\Disable(\OC::$server->getAppManager()));
75
+    $application->add(new OC\Core\Command\App\Enable(\OC::$server->getAppManager(), \OC::$server->getGroupManager()));
76
+    $application->add(new OC\Core\Command\App\Install());
77
+    $application->add(new OC\Core\Command\App\GetPath());
78
+    $application->add(new OC\Core\Command\App\ListApps(\OC::$server->getAppManager()));
79
+    $application->add(new OC\Core\Command\App\Remove(\OC::$server->getAppManager(), \OC::$server->query(\OC\Installer::class), \OC::$server->getLogger()));
80
+    $application->add(\OC::$server->query(\OC\Core\Command\App\Update::class));
81
+
82
+    $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Cleanup::class));
83
+    $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enforce::class));
84
+    $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enable::class));
85
+    $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Disable::class));
86
+    $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\State::class));
87
+
88
+    $application->add(new OC\Core\Command\Background\Cron(\OC::$server->getConfig()));
89
+    $application->add(new OC\Core\Command\Background\WebCron(\OC::$server->getConfig()));
90
+    $application->add(new OC\Core\Command\Background\Ajax(\OC::$server->getConfig()));
91
+
92
+    $application->add(\OC::$server->query(\OC\Core\Command\Broadcast\Test::class));
93
+
94
+    $application->add(new OC\Core\Command\Config\App\DeleteConfig(\OC::$server->getConfig()));
95
+    $application->add(new OC\Core\Command\Config\App\GetConfig(\OC::$server->getConfig()));
96
+    $application->add(new OC\Core\Command\Config\App\SetConfig(\OC::$server->getConfig()));
97
+    $application->add(new OC\Core\Command\Config\Import(\OC::$server->getConfig()));
98
+    $application->add(new OC\Core\Command\Config\ListConfigs(\OC::$server->getSystemConfig(), \OC::$server->getAppConfig()));
99
+    $application->add(new OC\Core\Command\Config\System\DeleteConfig(\OC::$server->getSystemConfig()));
100
+    $application->add(new OC\Core\Command\Config\System\GetConfig(\OC::$server->getSystemConfig()));
101
+    $application->add(new OC\Core\Command\Config\System\SetConfig(\OC::$server->getSystemConfig()));
102
+
103
+    $application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig())));
104
+    $application->add(new OC\Core\Command\Db\ConvertMysqlToMB4(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection(), \OC::$server->getURLGenerator(), \OC::$server->getLogger()));
105
+    $application->add(new OC\Core\Command\Db\ConvertFilecacheBigInt(\OC::$server->get(\OC\DB\Connection::class)));
106
+    $application->add(new OC\Core\Command\Db\AddMissingIndices(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
107
+    $application->add(new OC\Core\Command\Db\AddMissingColumns(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
108
+    $application->add(new OC\Core\Command\Db\AddMissingPrimaryKeys(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
109
+    $application->add(new OC\Core\Command\Db\Migrations\StatusCommand(\OC::$server->get(\OC\DB\Connection::class)));
110
+    $application->add(new OC\Core\Command\Db\Migrations\MigrateCommand(\OC::$server->get(\OC\DB\Connection::class)));
111
+    $application->add(new OC\Core\Command\Db\Migrations\GenerateCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getAppManager()));
112
+    $application->add(new OC\Core\Command\Db\Migrations\ExecuteCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getConfig()));
113
+
114
+    $application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig()));
115
+    $application->add(new OC\Core\Command\Encryption\Enable(\OC::$server->getConfig(), \OC::$server->getEncryptionManager()));
116
+    $application->add(new OC\Core\Command\Encryption\ListModules(\OC::$server->getEncryptionManager(), \OC::$server->getConfig()));
117
+    $application->add(new OC\Core\Command\Encryption\SetDefaultModule(\OC::$server->getEncryptionManager(), \OC::$server->getConfig()));
118
+    $application->add(new OC\Core\Command\Encryption\Status(\OC::$server->getEncryptionManager()));
119
+    $application->add(new OC\Core\Command\Encryption\EncryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getAppManager(), \OC::$server->getConfig(), new \Symfony\Component\Console\Helper\QuestionHelper()));
120
+    $application->add(new OC\Core\Command\Encryption\DecryptAll(
121
+        \OC::$server->getEncryptionManager(),
122
+        \OC::$server->getAppManager(),
123
+        \OC::$server->getConfig(),
124
+        new \OC\Encryption\DecryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getUserManager(), new \OC\Files\View()),
125
+        new \Symfony\Component\Console\Helper\QuestionHelper())
126
+    );
127
+
128
+    $application->add(new OC\Core\Command\Log\Manage(\OC::$server->getConfig()));
129
+    $application->add(new OC\Core\Command\Log\File(\OC::$server->getConfig()));
130
+
131
+    $view = new \OC\Files\View();
132
+    $util = new \OC\Encryption\Util(
133
+        $view,
134
+        \OC::$server->getUserManager(),
135
+        \OC::$server->getGroupManager(),
136
+        \OC::$server->getConfig()
137
+    );
138
+    $application->add(new OC\Core\Command\Encryption\ChangeKeyStorageRoot(
139
+            $view,
140
+            \OC::$server->getUserManager(),
141
+            \OC::$server->getConfig(),
142
+            $util,
143
+            new \Symfony\Component\Console\Helper\QuestionHelper()
144
+        )
145
+    );
146
+    $application->add(new OC\Core\Command\Encryption\ShowKeyStorageRoot($util));
147
+    $application->add(new OC\Core\Command\Encryption\MigrateKeyStorage(
148
+            $view,
149
+            \OC::$server->getUserManager(),
150
+            \OC::$server->getConfig(),
151
+            $util,
152
+            \OC::$server->getCrypto()
153
+        )
154
+    );
155
+
156
+    $application->add(new OC\Core\Command\Maintenance\DataFingerprint(\OC::$server->getConfig(), new \OC\AppFramework\Utility\TimeFactory()));
157
+    $application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateDB(\OC::$server->getMimeTypeDetector(), \OC::$server->getMimeTypeLoader()));
158
+    $application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateJS(\OC::$server->getMimeTypeDetector()));
159
+    $application->add(new OC\Core\Command\Maintenance\Mode(\OC::$server->getConfig()));
160
+    $application->add(new OC\Core\Command\Maintenance\UpdateHtaccess());
161
+    $application->add(new OC\Core\Command\Maintenance\UpdateTheme(\OC::$server->getMimeTypeDetector(), \OC::$server->getMemCacheFactory()));
162
+
163
+    $application->add(new OC\Core\Command\Upgrade(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->query(\OC\Installer::class)));
164
+    $application->add(new OC\Core\Command\Maintenance\Repair(
165
+        new \OC\Repair([], \OC::$server->getEventDispatcher(), \OC::$server->get(LoggerInterface::class)),
166
+        \OC::$server->getConfig(),
167
+        \OC::$server->getEventDispatcher(),
168
+        \OC::$server->getAppManager()
169
+    ));
170
+
171
+    $application->add(\OC::$server->query(\OC\Core\Command\Preview\Repair::class));
172
+    $application->add(\OC::$server->query(\OC\Core\Command\Preview\ResetRenderedTexts::class));
173
+
174
+    $application->add(new OC\Core\Command\User\Add(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
175
+    $application->add(new OC\Core\Command\User\Delete(\OC::$server->getUserManager()));
176
+    $application->add(new OC\Core\Command\User\Disable(\OC::$server->getUserManager()));
177
+    $application->add(new OC\Core\Command\User\Enable(\OC::$server->getUserManager()));
178
+    $application->add(new OC\Core\Command\User\LastSeen(\OC::$server->getUserManager()));
179
+    $application->add(\OC::$server->get(\OC\Core\Command\User\Report::class));
180
+    $application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager()));
181
+    $application->add(new OC\Core\Command\User\Setting(\OC::$server->getUserManager(), \OC::$server->getConfig(), \OC::$server->getDatabaseConnection()));
182
+    $application->add(new OC\Core\Command\User\ListCommand(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
183
+    $application->add(new OC\Core\Command\User\Info(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
184
+    $application->add(new OC\Core\Command\User\AddAppPassword(\OC::$server->get(\OCP\IUserManager::class), \OC::$server->get(\OC\Authentication\Token\IProvider::class), \OC::$server->get(\OCP\Security\ISecureRandom::class), \OC::$server->get(\OCP\Security\ICrypto::class)));
185
+
186
+    $application->add(new OC\Core\Command\Group\Add(\OC::$server->getGroupManager()));
187
+    $application->add(new OC\Core\Command\Group\Delete(\OC::$server->getGroupManager()));
188
+    $application->add(new OC\Core\Command\Group\ListCommand(\OC::$server->getGroupManager()));
189
+    $application->add(new OC\Core\Command\Group\AddUser(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
190
+    $application->add(new OC\Core\Command\Group\RemoveUser(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
191
+    $application->add(new OC\Core\Command\Group\Info(\OC::$server->get(\OCP\IGroupManager::class)));
192
+
193
+    $application->add(new OC\Core\Command\Security\ListCertificates(\OC::$server->getCertificateManager(), \OC::$server->getL10N('core')));
194
+    $application->add(new OC\Core\Command\Security\ImportCertificate(\OC::$server->getCertificateManager()));
195
+    $application->add(new OC\Core\Command\Security\RemoveCertificate(\OC::$server->getCertificateManager()));
196
+    $application->add(new OC\Core\Command\Security\ResetBruteforceAttempts(\OC::$server->getBruteForceThrottler()));
197 197
 } else {
198
-	$application->add(\OC::$server->get(\OC\Core\Command\Maintenance\Install::class));
198
+    $application->add(\OC::$server->get(\OC\Core\Command\Maintenance\Install::class));
199 199
 }
Please login to merge, or discard this patch.
lib/private/Installer.php 2 patches
Indentation   +582 added lines, -582 removed lines patch added patch discarded remove patch
@@ -59,586 +59,586 @@
 block discarded – undo
59 59
  * This class provides the functionality needed to install, update and remove apps
60 60
  */
61 61
 class Installer {
62
-	/** @var AppFetcher */
63
-	private $appFetcher;
64
-	/** @var IClientService */
65
-	private $clientService;
66
-	/** @var ITempManager */
67
-	private $tempManager;
68
-	/** @var ILogger */
69
-	private $logger;
70
-	/** @var IConfig */
71
-	private $config;
72
-	/** @var array - for caching the result of app fetcher */
73
-	private $apps = null;
74
-	/** @var bool|null - for caching the result of the ready status */
75
-	private $isInstanceReadyForUpdates = null;
76
-	/** @var bool */
77
-	private $isCLI;
78
-
79
-	/**
80
-	 * @param AppFetcher $appFetcher
81
-	 * @param IClientService $clientService
82
-	 * @param ITempManager $tempManager
83
-	 * @param ILogger $logger
84
-	 * @param IConfig $config
85
-	 */
86
-	public function __construct(
87
-		AppFetcher $appFetcher,
88
-		IClientService $clientService,
89
-		ITempManager $tempManager,
90
-		ILogger $logger,
91
-		IConfig $config,
92
-		bool $isCLI
93
-	) {
94
-		$this->appFetcher = $appFetcher;
95
-		$this->clientService = $clientService;
96
-		$this->tempManager = $tempManager;
97
-		$this->logger = $logger;
98
-		$this->config = $config;
99
-		$this->isCLI = $isCLI;
100
-	}
101
-
102
-	/**
103
-	 * Installs an app that is located in one of the app folders already
104
-	 *
105
-	 * @param string $appId App to install
106
-	 * @param bool $forceEnable
107
-	 * @throws \Exception
108
-	 * @return string app ID
109
-	 */
110
-	public function installApp(string $appId, bool $forceEnable = false): string {
111
-		$app = \OC_App::findAppInDirectories($appId);
112
-		if ($app === false) {
113
-			throw new \Exception('App not found in any app directory');
114
-		}
115
-
116
-		$basedir = $app['path'].'/'.$appId;
117
-
118
-		if (is_file($basedir . '/appinfo/database.xml')) {
119
-			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
120
-		}
121
-
122
-		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
123
-
124
-		$l = \OC::$server->getL10N('core');
125
-
126
-		if (!is_array($info)) {
127
-			throw new \Exception(
128
-				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
129
-					[$appId]
130
-				)
131
-			);
132
-		}
133
-
134
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
135
-		$ignoreMax = $forceEnable || in_array($appId, $ignoreMaxApps, true);
136
-
137
-		$version = implode('.', \OCP\Util::getVersion());
138
-		if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
139
-			throw new \Exception(
140
-				// TODO $l
141
-				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
142
-					[$info['name']]
143
-				)
144
-			);
145
-		}
146
-
147
-		// check for required dependencies
148
-		\OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
149
-		/** @var Coordinator $coordinator */
150
-		$coordinator = \OC::$server->get(Coordinator::class);
151
-		$coordinator->runLazyRegistration($appId);
152
-		\OC_App::registerAutoloading($appId, $basedir);
153
-
154
-		$previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false);
155
-		if ($previousVersion) {
156
-			OC_App::executeRepairSteps($appId, $info['repair-steps']['pre-migration']);
157
-		}
158
-
159
-		//install the database
160
-		$ms = new MigrationService($info['id'], \OC::$server->get(Connection::class));
161
-		$ms->migrate('latest', true);
162
-
163
-		if ($previousVersion) {
164
-			OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']);
165
-		}
166
-
167
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
168
-
169
-		//run appinfo/install.php
170
-		self::includeAppScript($basedir . '/appinfo/install.php');
171
-
172
-		$appData = OC_App::getAppInfo($appId);
173
-		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
174
-
175
-		//set the installed version
176
-		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
177
-		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
178
-
179
-		//set remote/public handlers
180
-		foreach ($info['remote'] as $name => $path) {
181
-			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
182
-		}
183
-		foreach ($info['public'] as $name => $path) {
184
-			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
185
-		}
186
-
187
-		OC_App::setAppTypes($info['id']);
188
-
189
-		return $info['id'];
190
-	}
191
-
192
-	/**
193
-	 * Updates the specified app from the appstore
194
-	 *
195
-	 * @param string $appId
196
-	 * @param bool [$allowUnstable] Allow unstable releases
197
-	 * @return bool
198
-	 */
199
-	public function updateAppstoreApp($appId, $allowUnstable = false) {
200
-		if ($this->isUpdateAvailable($appId, $allowUnstable)) {
201
-			try {
202
-				$this->downloadApp($appId, $allowUnstable);
203
-			} catch (\Exception $e) {
204
-				$this->logger->logException($e, [
205
-					'level' => ILogger::ERROR,
206
-					'app' => 'core',
207
-				]);
208
-				return false;
209
-			}
210
-			return OC_App::updateApp($appId);
211
-		}
212
-
213
-		return false;
214
-	}
215
-
216
-	/**
217
-	 * Split the certificate file in individual certs
218
-	 *
219
-	 * @param string $cert
220
-	 * @return string[]
221
-	 */
222
-	private function splitCerts(string $cert): array {
223
-		preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches);
224
-
225
-		return $matches[0];
226
-	}
227
-
228
-	/**
229
-	 * Downloads an app and puts it into the app directory
230
-	 *
231
-	 * @param string $appId
232
-	 * @param bool [$allowUnstable]
233
-	 *
234
-	 * @throws \Exception If the installation was not successful
235
-	 */
236
-	public function downloadApp($appId, $allowUnstable = false) {
237
-		$appId = strtolower($appId);
238
-
239
-		$apps = $this->appFetcher->get($allowUnstable);
240
-		foreach ($apps as $app) {
241
-			if ($app['id'] === $appId) {
242
-				// Load the certificate
243
-				$certificate = new X509();
244
-				$rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
245
-				$rootCrts = $this->splitCerts($rootCrt);
246
-				foreach ($rootCrts as $rootCrt) {
247
-					$certificate->loadCA($rootCrt);
248
-				}
249
-				$loadedCertificate = $certificate->loadX509($app['certificate']);
250
-
251
-				// Verify if the certificate has been revoked
252
-				$crl = new X509();
253
-				foreach ($rootCrts as $rootCrt) {
254
-					$crl->loadCA($rootCrt);
255
-				}
256
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
257
-				if ($crl->validateSignature() !== true) {
258
-					throw new \Exception('Could not validate CRL signature');
259
-				}
260
-				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
261
-				$revoked = $crl->getRevoked($csn);
262
-				if ($revoked !== false) {
263
-					throw new \Exception(
264
-						sprintf(
265
-							'Certificate "%s" has been revoked',
266
-							$csn
267
-						)
268
-					);
269
-				}
270
-
271
-				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
272
-				if ($certificate->validateSignature() !== true) {
273
-					throw new \Exception(
274
-						sprintf(
275
-							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
276
-							$appId
277
-						)
278
-					);
279
-				}
280
-
281
-				// Verify if the certificate is issued for the requested app id
282
-				$certInfo = openssl_x509_parse($app['certificate']);
283
-				if (!isset($certInfo['subject']['CN'])) {
284
-					throw new \Exception(
285
-						sprintf(
286
-							'App with id %s has a cert with no CN',
287
-							$appId
288
-						)
289
-					);
290
-				}
291
-				if ($certInfo['subject']['CN'] !== $appId) {
292
-					throw new \Exception(
293
-						sprintf(
294
-							'App with id %s has a cert issued to %s',
295
-							$appId,
296
-							$certInfo['subject']['CN']
297
-						)
298
-					);
299
-				}
300
-
301
-				// Download the release
302
-				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
303
-				$timeout = $this->isCLI ? 0 : 120;
304
-				$client = $this->clientService->newClient();
305
-				$client->get($app['releases'][0]['download'], ['sink' => $tempFile, 'timeout' => $timeout]);
306
-
307
-				// Check if the signature actually matches the downloaded content
308
-				$certificate = openssl_get_publickey($app['certificate']);
309
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
310
-				openssl_free_key($certificate);
311
-
312
-				if ($verified === true) {
313
-					// Seems to match, let's proceed
314
-					$extractDir = $this->tempManager->getTemporaryFolder();
315
-					$archive = new TAR($tempFile);
316
-
317
-					if ($archive) {
318
-						if (!$archive->extract($extractDir)) {
319
-							$errorMessage = 'Could not extract app ' . $appId;
320
-
321
-							$archiveError = $archive->getError();
322
-							if ($archiveError instanceof \PEAR_Error) {
323
-								$errorMessage .= ': ' . $archiveError->getMessage();
324
-							}
325
-
326
-							throw new \Exception($errorMessage);
327
-						}
328
-						$allFiles = scandir($extractDir);
329
-						$folders = array_diff($allFiles, ['.', '..']);
330
-						$folders = array_values($folders);
331
-
332
-						if (count($folders) > 1) {
333
-							throw new \Exception(
334
-								sprintf(
335
-									'Extracted app %s has more than 1 folder',
336
-									$appId
337
-								)
338
-							);
339
-						}
340
-
341
-						// Check if appinfo/info.xml has the same app ID as well
342
-						$loadEntities = libxml_disable_entity_loader(false);
343
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
344
-						libxml_disable_entity_loader($loadEntities);
345
-						if ((string)$xml->id !== $appId) {
346
-							throw new \Exception(
347
-								sprintf(
348
-									'App for id %s has a wrong app ID in info.xml: %s',
349
-									$appId,
350
-									(string)$xml->id
351
-								)
352
-							);
353
-						}
354
-
355
-						// Check if the version is lower than before
356
-						$currentVersion = OC_App::getAppVersion($appId);
357
-						$newVersion = (string)$xml->version;
358
-						if (version_compare($currentVersion, $newVersion) === 1) {
359
-							throw new \Exception(
360
-								sprintf(
361
-									'App for id %s has version %s and tried to update to lower version %s',
362
-									$appId,
363
-									$currentVersion,
364
-									$newVersion
365
-								)
366
-							);
367
-						}
368
-
369
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
370
-						// Remove old app with the ID if existent
371
-						OC_Helper::rmdirr($baseDir);
372
-						// Move to app folder
373
-						if (@mkdir($baseDir)) {
374
-							$extractDir .= '/' . $folders[0];
375
-							OC_Helper::copyr($extractDir, $baseDir);
376
-						}
377
-						OC_Helper::copyr($extractDir, $baseDir);
378
-						OC_Helper::rmdirr($extractDir);
379
-						return;
380
-					} else {
381
-						throw new \Exception(
382
-							sprintf(
383
-								'Could not extract app with ID %s to %s',
384
-								$appId,
385
-								$extractDir
386
-							)
387
-						);
388
-					}
389
-				} else {
390
-					// Signature does not match
391
-					throw new \Exception(
392
-						sprintf(
393
-							'App with id %s has invalid signature',
394
-							$appId
395
-						)
396
-					);
397
-				}
398
-			}
399
-		}
400
-
401
-		throw new \Exception(
402
-			sprintf(
403
-				'Could not download app %s',
404
-				$appId
405
-			)
406
-		);
407
-	}
408
-
409
-	/**
410
-	 * Check if an update for the app is available
411
-	 *
412
-	 * @param string $appId
413
-	 * @param bool $allowUnstable
414
-	 * @return string|false false or the version number of the update
415
-	 */
416
-	public function isUpdateAvailable($appId, $allowUnstable = false) {
417
-		if ($this->isInstanceReadyForUpdates === null) {
418
-			$installPath = OC_App::getInstallPath();
419
-			if ($installPath === false || $installPath === null) {
420
-				$this->isInstanceReadyForUpdates = false;
421
-			} else {
422
-				$this->isInstanceReadyForUpdates = true;
423
-			}
424
-		}
425
-
426
-		if ($this->isInstanceReadyForUpdates === false) {
427
-			return false;
428
-		}
429
-
430
-		if ($this->isInstalledFromGit($appId) === true) {
431
-			return false;
432
-		}
433
-
434
-		if ($this->apps === null) {
435
-			$this->apps = $this->appFetcher->get($allowUnstable);
436
-		}
437
-
438
-		foreach ($this->apps as $app) {
439
-			if ($app['id'] === $appId) {
440
-				$currentVersion = OC_App::getAppVersion($appId);
441
-
442
-				if (!isset($app['releases'][0]['version'])) {
443
-					return false;
444
-				}
445
-				$newestVersion = $app['releases'][0]['version'];
446
-				if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
447
-					return $newestVersion;
448
-				} else {
449
-					return false;
450
-				}
451
-			}
452
-		}
453
-
454
-		return false;
455
-	}
456
-
457
-	/**
458
-	 * Check if app has been installed from git
459
-	 * @param string $name name of the application to remove
460
-	 * @return boolean
461
-	 *
462
-	 * The function will check if the path contains a .git folder
463
-	 */
464
-	private function isInstalledFromGit($appId) {
465
-		$app = \OC_App::findAppInDirectories($appId);
466
-		if ($app === false) {
467
-			return false;
468
-		}
469
-		$basedir = $app['path'].'/'.$appId;
470
-		return file_exists($basedir.'/.git/');
471
-	}
472
-
473
-	/**
474
-	 * Check if app is already downloaded
475
-	 * @param string $name name of the application to remove
476
-	 * @return boolean
477
-	 *
478
-	 * The function will check if the app is already downloaded in the apps repository
479
-	 */
480
-	public function isDownloaded($name) {
481
-		foreach (\OC::$APPSROOTS as $dir) {
482
-			$dirToTest = $dir['path'];
483
-			$dirToTest .= '/';
484
-			$dirToTest .= $name;
485
-			$dirToTest .= '/';
486
-
487
-			if (is_dir($dirToTest)) {
488
-				return true;
489
-			}
490
-		}
491
-
492
-		return false;
493
-	}
494
-
495
-	/**
496
-	 * Removes an app
497
-	 * @param string $appId ID of the application to remove
498
-	 * @return boolean
499
-	 *
500
-	 *
501
-	 * This function works as follows
502
-	 *   -# call uninstall repair steps
503
-	 *   -# removing the files
504
-	 *
505
-	 * The function will not delete preferences, tables and the configuration,
506
-	 * this has to be done by the function oc_app_uninstall().
507
-	 */
508
-	public function removeApp($appId) {
509
-		if ($this->isDownloaded($appId)) {
510
-			if (\OC::$server->getAppManager()->isShipped($appId)) {
511
-				return false;
512
-			}
513
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
514
-			OC_Helper::rmdirr($appDir);
515
-			return true;
516
-		} else {
517
-			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
518
-
519
-			return false;
520
-		}
521
-	}
522
-
523
-	/**
524
-	 * Installs the app within the bundle and marks the bundle as installed
525
-	 *
526
-	 * @param Bundle $bundle
527
-	 * @throws \Exception If app could not get installed
528
-	 */
529
-	public function installAppBundle(Bundle $bundle) {
530
-		$appIds = $bundle->getAppIdentifiers();
531
-		foreach ($appIds as $appId) {
532
-			if (!$this->isDownloaded($appId)) {
533
-				$this->downloadApp($appId);
534
-			}
535
-			$this->installApp($appId);
536
-			$app = new OC_App();
537
-			$app->enable($appId);
538
-		}
539
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
540
-		$bundles[] = $bundle->getIdentifier();
541
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
542
-	}
543
-
544
-	/**
545
-	 * Installs shipped apps
546
-	 *
547
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
548
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
549
-	 *                         working ownCloud at the end instead of an aborted update.
550
-	 * @return array Array of error messages (appid => Exception)
551
-	 */
552
-	public static function installShippedApps($softErrors = false) {
553
-		$appManager = \OC::$server->getAppManager();
554
-		$config = \OC::$server->getConfig();
555
-		$errors = [];
556
-		foreach (\OC::$APPSROOTS as $app_dir) {
557
-			if ($dir = opendir($app_dir['path'])) {
558
-				while (false !== ($filename = readdir($dir))) {
559
-					if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) {
560
-						if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
561
-							if ($config->getAppValue($filename, "installed_version", null) === null) {
562
-								$info = OC_App::getAppInfo($filename);
563
-								$enabled = isset($info['default_enable']);
564
-								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
565
-									  && $config->getAppValue($filename, 'enabled') !== 'no') {
566
-									if ($softErrors) {
567
-										try {
568
-											Installer::installShippedApp($filename);
569
-										} catch (HintException $e) {
570
-											if ($e->getPrevious() instanceof TableExistsException) {
571
-												$errors[$filename] = $e;
572
-												continue;
573
-											}
574
-											throw $e;
575
-										}
576
-									} else {
577
-										Installer::installShippedApp($filename);
578
-									}
579
-									$config->setAppValue($filename, 'enabled', 'yes');
580
-								}
581
-							}
582
-						}
583
-					}
584
-				}
585
-				closedir($dir);
586
-			}
587
-		}
588
-
589
-		return $errors;
590
-	}
591
-
592
-	/**
593
-	 * install an app already placed in the app folder
594
-	 * @param string $app id of the app to install
595
-	 * @return integer
596
-	 */
597
-	public static function installShippedApp($app) {
598
-		//install the database
599
-		$appPath = OC_App::getAppPath($app);
600
-		\OC_App::registerAutoloading($app, $appPath);
601
-
602
-		$ms = new MigrationService($app, \OC::$server->get(Connection::class));
603
-		$ms->migrate('latest', true);
604
-
605
-		//run appinfo/install.php
606
-		self::includeAppScript("$appPath/appinfo/install.php");
607
-
608
-		$info = OC_App::getAppInfo($app);
609
-		if (is_null($info)) {
610
-			return false;
611
-		}
612
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
613
-
614
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
615
-
616
-		$config = \OC::$server->getConfig();
617
-
618
-		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
619
-		if (array_key_exists('ocsid', $info)) {
620
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
621
-		}
622
-
623
-		//set remote/public handlers
624
-		foreach ($info['remote'] as $name => $path) {
625
-			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
626
-		}
627
-		foreach ($info['public'] as $name => $path) {
628
-			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
629
-		}
630
-
631
-		OC_App::setAppTypes($info['id']);
632
-
633
-		return $info['id'];
634
-	}
635
-
636
-	/**
637
-	 * @param string $script
638
-	 */
639
-	private static function includeAppScript($script) {
640
-		if (file_exists($script)) {
641
-			include $script;
642
-		}
643
-	}
62
+    /** @var AppFetcher */
63
+    private $appFetcher;
64
+    /** @var IClientService */
65
+    private $clientService;
66
+    /** @var ITempManager */
67
+    private $tempManager;
68
+    /** @var ILogger */
69
+    private $logger;
70
+    /** @var IConfig */
71
+    private $config;
72
+    /** @var array - for caching the result of app fetcher */
73
+    private $apps = null;
74
+    /** @var bool|null - for caching the result of the ready status */
75
+    private $isInstanceReadyForUpdates = null;
76
+    /** @var bool */
77
+    private $isCLI;
78
+
79
+    /**
80
+     * @param AppFetcher $appFetcher
81
+     * @param IClientService $clientService
82
+     * @param ITempManager $tempManager
83
+     * @param ILogger $logger
84
+     * @param IConfig $config
85
+     */
86
+    public function __construct(
87
+        AppFetcher $appFetcher,
88
+        IClientService $clientService,
89
+        ITempManager $tempManager,
90
+        ILogger $logger,
91
+        IConfig $config,
92
+        bool $isCLI
93
+    ) {
94
+        $this->appFetcher = $appFetcher;
95
+        $this->clientService = $clientService;
96
+        $this->tempManager = $tempManager;
97
+        $this->logger = $logger;
98
+        $this->config = $config;
99
+        $this->isCLI = $isCLI;
100
+    }
101
+
102
+    /**
103
+     * Installs an app that is located in one of the app folders already
104
+     *
105
+     * @param string $appId App to install
106
+     * @param bool $forceEnable
107
+     * @throws \Exception
108
+     * @return string app ID
109
+     */
110
+    public function installApp(string $appId, bool $forceEnable = false): string {
111
+        $app = \OC_App::findAppInDirectories($appId);
112
+        if ($app === false) {
113
+            throw new \Exception('App not found in any app directory');
114
+        }
115
+
116
+        $basedir = $app['path'].'/'.$appId;
117
+
118
+        if (is_file($basedir . '/appinfo/database.xml')) {
119
+            throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
120
+        }
121
+
122
+        $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
123
+
124
+        $l = \OC::$server->getL10N('core');
125
+
126
+        if (!is_array($info)) {
127
+            throw new \Exception(
128
+                $l->t('App "%s" cannot be installed because appinfo file cannot be read.',
129
+                    [$appId]
130
+                )
131
+            );
132
+        }
133
+
134
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
135
+        $ignoreMax = $forceEnable || in_array($appId, $ignoreMaxApps, true);
136
+
137
+        $version = implode('.', \OCP\Util::getVersion());
138
+        if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
139
+            throw new \Exception(
140
+                // TODO $l
141
+                $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
142
+                    [$info['name']]
143
+                )
144
+            );
145
+        }
146
+
147
+        // check for required dependencies
148
+        \OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
149
+        /** @var Coordinator $coordinator */
150
+        $coordinator = \OC::$server->get(Coordinator::class);
151
+        $coordinator->runLazyRegistration($appId);
152
+        \OC_App::registerAutoloading($appId, $basedir);
153
+
154
+        $previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false);
155
+        if ($previousVersion) {
156
+            OC_App::executeRepairSteps($appId, $info['repair-steps']['pre-migration']);
157
+        }
158
+
159
+        //install the database
160
+        $ms = new MigrationService($info['id'], \OC::$server->get(Connection::class));
161
+        $ms->migrate('latest', true);
162
+
163
+        if ($previousVersion) {
164
+            OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']);
165
+        }
166
+
167
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
168
+
169
+        //run appinfo/install.php
170
+        self::includeAppScript($basedir . '/appinfo/install.php');
171
+
172
+        $appData = OC_App::getAppInfo($appId);
173
+        OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
174
+
175
+        //set the installed version
176
+        \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
177
+        \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
178
+
179
+        //set remote/public handlers
180
+        foreach ($info['remote'] as $name => $path) {
181
+            \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
182
+        }
183
+        foreach ($info['public'] as $name => $path) {
184
+            \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
185
+        }
186
+
187
+        OC_App::setAppTypes($info['id']);
188
+
189
+        return $info['id'];
190
+    }
191
+
192
+    /**
193
+     * Updates the specified app from the appstore
194
+     *
195
+     * @param string $appId
196
+     * @param bool [$allowUnstable] Allow unstable releases
197
+     * @return bool
198
+     */
199
+    public function updateAppstoreApp($appId, $allowUnstable = false) {
200
+        if ($this->isUpdateAvailable($appId, $allowUnstable)) {
201
+            try {
202
+                $this->downloadApp($appId, $allowUnstable);
203
+            } catch (\Exception $e) {
204
+                $this->logger->logException($e, [
205
+                    'level' => ILogger::ERROR,
206
+                    'app' => 'core',
207
+                ]);
208
+                return false;
209
+            }
210
+            return OC_App::updateApp($appId);
211
+        }
212
+
213
+        return false;
214
+    }
215
+
216
+    /**
217
+     * Split the certificate file in individual certs
218
+     *
219
+     * @param string $cert
220
+     * @return string[]
221
+     */
222
+    private function splitCerts(string $cert): array {
223
+        preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches);
224
+
225
+        return $matches[0];
226
+    }
227
+
228
+    /**
229
+     * Downloads an app and puts it into the app directory
230
+     *
231
+     * @param string $appId
232
+     * @param bool [$allowUnstable]
233
+     *
234
+     * @throws \Exception If the installation was not successful
235
+     */
236
+    public function downloadApp($appId, $allowUnstable = false) {
237
+        $appId = strtolower($appId);
238
+
239
+        $apps = $this->appFetcher->get($allowUnstable);
240
+        foreach ($apps as $app) {
241
+            if ($app['id'] === $appId) {
242
+                // Load the certificate
243
+                $certificate = new X509();
244
+                $rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
245
+                $rootCrts = $this->splitCerts($rootCrt);
246
+                foreach ($rootCrts as $rootCrt) {
247
+                    $certificate->loadCA($rootCrt);
248
+                }
249
+                $loadedCertificate = $certificate->loadX509($app['certificate']);
250
+
251
+                // Verify if the certificate has been revoked
252
+                $crl = new X509();
253
+                foreach ($rootCrts as $rootCrt) {
254
+                    $crl->loadCA($rootCrt);
255
+                }
256
+                $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
257
+                if ($crl->validateSignature() !== true) {
258
+                    throw new \Exception('Could not validate CRL signature');
259
+                }
260
+                $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
261
+                $revoked = $crl->getRevoked($csn);
262
+                if ($revoked !== false) {
263
+                    throw new \Exception(
264
+                        sprintf(
265
+                            'Certificate "%s" has been revoked',
266
+                            $csn
267
+                        )
268
+                    );
269
+                }
270
+
271
+                // Verify if the certificate has been issued by the Nextcloud Code Authority CA
272
+                if ($certificate->validateSignature() !== true) {
273
+                    throw new \Exception(
274
+                        sprintf(
275
+                            'App with id %s has a certificate not issued by a trusted Code Signing Authority',
276
+                            $appId
277
+                        )
278
+                    );
279
+                }
280
+
281
+                // Verify if the certificate is issued for the requested app id
282
+                $certInfo = openssl_x509_parse($app['certificate']);
283
+                if (!isset($certInfo['subject']['CN'])) {
284
+                    throw new \Exception(
285
+                        sprintf(
286
+                            'App with id %s has a cert with no CN',
287
+                            $appId
288
+                        )
289
+                    );
290
+                }
291
+                if ($certInfo['subject']['CN'] !== $appId) {
292
+                    throw new \Exception(
293
+                        sprintf(
294
+                            'App with id %s has a cert issued to %s',
295
+                            $appId,
296
+                            $certInfo['subject']['CN']
297
+                        )
298
+                    );
299
+                }
300
+
301
+                // Download the release
302
+                $tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
303
+                $timeout = $this->isCLI ? 0 : 120;
304
+                $client = $this->clientService->newClient();
305
+                $client->get($app['releases'][0]['download'], ['sink' => $tempFile, 'timeout' => $timeout]);
306
+
307
+                // Check if the signature actually matches the downloaded content
308
+                $certificate = openssl_get_publickey($app['certificate']);
309
+                $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
310
+                openssl_free_key($certificate);
311
+
312
+                if ($verified === true) {
313
+                    // Seems to match, let's proceed
314
+                    $extractDir = $this->tempManager->getTemporaryFolder();
315
+                    $archive = new TAR($tempFile);
316
+
317
+                    if ($archive) {
318
+                        if (!$archive->extract($extractDir)) {
319
+                            $errorMessage = 'Could not extract app ' . $appId;
320
+
321
+                            $archiveError = $archive->getError();
322
+                            if ($archiveError instanceof \PEAR_Error) {
323
+                                $errorMessage .= ': ' . $archiveError->getMessage();
324
+                            }
325
+
326
+                            throw new \Exception($errorMessage);
327
+                        }
328
+                        $allFiles = scandir($extractDir);
329
+                        $folders = array_diff($allFiles, ['.', '..']);
330
+                        $folders = array_values($folders);
331
+
332
+                        if (count($folders) > 1) {
333
+                            throw new \Exception(
334
+                                sprintf(
335
+                                    'Extracted app %s has more than 1 folder',
336
+                                    $appId
337
+                                )
338
+                            );
339
+                        }
340
+
341
+                        // Check if appinfo/info.xml has the same app ID as well
342
+                        $loadEntities = libxml_disable_entity_loader(false);
343
+                        $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
344
+                        libxml_disable_entity_loader($loadEntities);
345
+                        if ((string)$xml->id !== $appId) {
346
+                            throw new \Exception(
347
+                                sprintf(
348
+                                    'App for id %s has a wrong app ID in info.xml: %s',
349
+                                    $appId,
350
+                                    (string)$xml->id
351
+                                )
352
+                            );
353
+                        }
354
+
355
+                        // Check if the version is lower than before
356
+                        $currentVersion = OC_App::getAppVersion($appId);
357
+                        $newVersion = (string)$xml->version;
358
+                        if (version_compare($currentVersion, $newVersion) === 1) {
359
+                            throw new \Exception(
360
+                                sprintf(
361
+                                    'App for id %s has version %s and tried to update to lower version %s',
362
+                                    $appId,
363
+                                    $currentVersion,
364
+                                    $newVersion
365
+                                )
366
+                            );
367
+                        }
368
+
369
+                        $baseDir = OC_App::getInstallPath() . '/' . $appId;
370
+                        // Remove old app with the ID if existent
371
+                        OC_Helper::rmdirr($baseDir);
372
+                        // Move to app folder
373
+                        if (@mkdir($baseDir)) {
374
+                            $extractDir .= '/' . $folders[0];
375
+                            OC_Helper::copyr($extractDir, $baseDir);
376
+                        }
377
+                        OC_Helper::copyr($extractDir, $baseDir);
378
+                        OC_Helper::rmdirr($extractDir);
379
+                        return;
380
+                    } else {
381
+                        throw new \Exception(
382
+                            sprintf(
383
+                                'Could not extract app with ID %s to %s',
384
+                                $appId,
385
+                                $extractDir
386
+                            )
387
+                        );
388
+                    }
389
+                } else {
390
+                    // Signature does not match
391
+                    throw new \Exception(
392
+                        sprintf(
393
+                            'App with id %s has invalid signature',
394
+                            $appId
395
+                        )
396
+                    );
397
+                }
398
+            }
399
+        }
400
+
401
+        throw new \Exception(
402
+            sprintf(
403
+                'Could not download app %s',
404
+                $appId
405
+            )
406
+        );
407
+    }
408
+
409
+    /**
410
+     * Check if an update for the app is available
411
+     *
412
+     * @param string $appId
413
+     * @param bool $allowUnstable
414
+     * @return string|false false or the version number of the update
415
+     */
416
+    public function isUpdateAvailable($appId, $allowUnstable = false) {
417
+        if ($this->isInstanceReadyForUpdates === null) {
418
+            $installPath = OC_App::getInstallPath();
419
+            if ($installPath === false || $installPath === null) {
420
+                $this->isInstanceReadyForUpdates = false;
421
+            } else {
422
+                $this->isInstanceReadyForUpdates = true;
423
+            }
424
+        }
425
+
426
+        if ($this->isInstanceReadyForUpdates === false) {
427
+            return false;
428
+        }
429
+
430
+        if ($this->isInstalledFromGit($appId) === true) {
431
+            return false;
432
+        }
433
+
434
+        if ($this->apps === null) {
435
+            $this->apps = $this->appFetcher->get($allowUnstable);
436
+        }
437
+
438
+        foreach ($this->apps as $app) {
439
+            if ($app['id'] === $appId) {
440
+                $currentVersion = OC_App::getAppVersion($appId);
441
+
442
+                if (!isset($app['releases'][0]['version'])) {
443
+                    return false;
444
+                }
445
+                $newestVersion = $app['releases'][0]['version'];
446
+                if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
447
+                    return $newestVersion;
448
+                } else {
449
+                    return false;
450
+                }
451
+            }
452
+        }
453
+
454
+        return false;
455
+    }
456
+
457
+    /**
458
+     * Check if app has been installed from git
459
+     * @param string $name name of the application to remove
460
+     * @return boolean
461
+     *
462
+     * The function will check if the path contains a .git folder
463
+     */
464
+    private function isInstalledFromGit($appId) {
465
+        $app = \OC_App::findAppInDirectories($appId);
466
+        if ($app === false) {
467
+            return false;
468
+        }
469
+        $basedir = $app['path'].'/'.$appId;
470
+        return file_exists($basedir.'/.git/');
471
+    }
472
+
473
+    /**
474
+     * Check if app is already downloaded
475
+     * @param string $name name of the application to remove
476
+     * @return boolean
477
+     *
478
+     * The function will check if the app is already downloaded in the apps repository
479
+     */
480
+    public function isDownloaded($name) {
481
+        foreach (\OC::$APPSROOTS as $dir) {
482
+            $dirToTest = $dir['path'];
483
+            $dirToTest .= '/';
484
+            $dirToTest .= $name;
485
+            $dirToTest .= '/';
486
+
487
+            if (is_dir($dirToTest)) {
488
+                return true;
489
+            }
490
+        }
491
+
492
+        return false;
493
+    }
494
+
495
+    /**
496
+     * Removes an app
497
+     * @param string $appId ID of the application to remove
498
+     * @return boolean
499
+     *
500
+     *
501
+     * This function works as follows
502
+     *   -# call uninstall repair steps
503
+     *   -# removing the files
504
+     *
505
+     * The function will not delete preferences, tables and the configuration,
506
+     * this has to be done by the function oc_app_uninstall().
507
+     */
508
+    public function removeApp($appId) {
509
+        if ($this->isDownloaded($appId)) {
510
+            if (\OC::$server->getAppManager()->isShipped($appId)) {
511
+                return false;
512
+            }
513
+            $appDir = OC_App::getInstallPath() . '/' . $appId;
514
+            OC_Helper::rmdirr($appDir);
515
+            return true;
516
+        } else {
517
+            \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
518
+
519
+            return false;
520
+        }
521
+    }
522
+
523
+    /**
524
+     * Installs the app within the bundle and marks the bundle as installed
525
+     *
526
+     * @param Bundle $bundle
527
+     * @throws \Exception If app could not get installed
528
+     */
529
+    public function installAppBundle(Bundle $bundle) {
530
+        $appIds = $bundle->getAppIdentifiers();
531
+        foreach ($appIds as $appId) {
532
+            if (!$this->isDownloaded($appId)) {
533
+                $this->downloadApp($appId);
534
+            }
535
+            $this->installApp($appId);
536
+            $app = new OC_App();
537
+            $app->enable($appId);
538
+        }
539
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
540
+        $bundles[] = $bundle->getIdentifier();
541
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
542
+    }
543
+
544
+    /**
545
+     * Installs shipped apps
546
+     *
547
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
548
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
549
+     *                         working ownCloud at the end instead of an aborted update.
550
+     * @return array Array of error messages (appid => Exception)
551
+     */
552
+    public static function installShippedApps($softErrors = false) {
553
+        $appManager = \OC::$server->getAppManager();
554
+        $config = \OC::$server->getConfig();
555
+        $errors = [];
556
+        foreach (\OC::$APPSROOTS as $app_dir) {
557
+            if ($dir = opendir($app_dir['path'])) {
558
+                while (false !== ($filename = readdir($dir))) {
559
+                    if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) {
560
+                        if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
561
+                            if ($config->getAppValue($filename, "installed_version", null) === null) {
562
+                                $info = OC_App::getAppInfo($filename);
563
+                                $enabled = isset($info['default_enable']);
564
+                                if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
565
+                                      && $config->getAppValue($filename, 'enabled') !== 'no') {
566
+                                    if ($softErrors) {
567
+                                        try {
568
+                                            Installer::installShippedApp($filename);
569
+                                        } catch (HintException $e) {
570
+                                            if ($e->getPrevious() instanceof TableExistsException) {
571
+                                                $errors[$filename] = $e;
572
+                                                continue;
573
+                                            }
574
+                                            throw $e;
575
+                                        }
576
+                                    } else {
577
+                                        Installer::installShippedApp($filename);
578
+                                    }
579
+                                    $config->setAppValue($filename, 'enabled', 'yes');
580
+                                }
581
+                            }
582
+                        }
583
+                    }
584
+                }
585
+                closedir($dir);
586
+            }
587
+        }
588
+
589
+        return $errors;
590
+    }
591
+
592
+    /**
593
+     * install an app already placed in the app folder
594
+     * @param string $app id of the app to install
595
+     * @return integer
596
+     */
597
+    public static function installShippedApp($app) {
598
+        //install the database
599
+        $appPath = OC_App::getAppPath($app);
600
+        \OC_App::registerAutoloading($app, $appPath);
601
+
602
+        $ms = new MigrationService($app, \OC::$server->get(Connection::class));
603
+        $ms->migrate('latest', true);
604
+
605
+        //run appinfo/install.php
606
+        self::includeAppScript("$appPath/appinfo/install.php");
607
+
608
+        $info = OC_App::getAppInfo($app);
609
+        if (is_null($info)) {
610
+            return false;
611
+        }
612
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
613
+
614
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
615
+
616
+        $config = \OC::$server->getConfig();
617
+
618
+        $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
619
+        if (array_key_exists('ocsid', $info)) {
620
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
621
+        }
622
+
623
+        //set remote/public handlers
624
+        foreach ($info['remote'] as $name => $path) {
625
+            $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
626
+        }
627
+        foreach ($info['public'] as $name => $path) {
628
+            $config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
629
+        }
630
+
631
+        OC_App::setAppTypes($info['id']);
632
+
633
+        return $info['id'];
634
+    }
635
+
636
+    /**
637
+     * @param string $script
638
+     */
639
+    private static function includeAppScript($script) {
640
+        if (file_exists($script)) {
641
+            include $script;
642
+        }
643
+    }
644 644
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -115,8 +115,8 @@  discard block
 block discarded – undo
115 115
 
116 116
 		$basedir = $app['path'].'/'.$appId;
117 117
 
118
-		if (is_file($basedir . '/appinfo/database.xml')) {
119
-			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
118
+		if (is_file($basedir.'/appinfo/database.xml')) {
119
+			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in '.$appId);
120 120
 		}
121 121
 
122 122
 		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
167 167
 		\OC_App::setupBackgroundJobs($info['background-jobs']);
168 168
 
169 169
 		//run appinfo/install.php
170
-		self::includeAppScript($basedir . '/appinfo/install.php');
170
+		self::includeAppScript($basedir.'/appinfo/install.php');
171 171
 
172 172
 		$appData = OC_App::getAppInfo($appId);
173 173
 		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
@@ -241,7 +241,7 @@  discard block
 block discarded – undo
241 241
 			if ($app['id'] === $appId) {
242 242
 				// Load the certificate
243 243
 				$certificate = new X509();
244
-				$rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
244
+				$rootCrt = file_get_contents(__DIR__.'/../../resources/codesigning/root.crt');
245 245
 				$rootCrts = $this->splitCerts($rootCrt);
246 246
 				foreach ($rootCrts as $rootCrt) {
247 247
 					$certificate->loadCA($rootCrt);
@@ -253,7 +253,7 @@  discard block
 block discarded – undo
253 253
 				foreach ($rootCrts as $rootCrt) {
254 254
 					$crl->loadCA($rootCrt);
255 255
 				}
256
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
256
+				$crl->loadCRL(file_get_contents(__DIR__.'/../../resources/codesigning/root.crl'));
257 257
 				if ($crl->validateSignature() !== true) {
258 258
 					throw new \Exception('Could not validate CRL signature');
259 259
 				}
@@ -306,7 +306,7 @@  discard block
 block discarded – undo
306 306
 
307 307
 				// Check if the signature actually matches the downloaded content
308 308
 				$certificate = openssl_get_publickey($app['certificate']);
309
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
309
+				$verified = (bool) openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
310 310
 				openssl_free_key($certificate);
311 311
 
312 312
 				if ($verified === true) {
@@ -316,11 +316,11 @@  discard block
 block discarded – undo
316 316
 
317 317
 					if ($archive) {
318 318
 						if (!$archive->extract($extractDir)) {
319
-							$errorMessage = 'Could not extract app ' . $appId;
319
+							$errorMessage = 'Could not extract app '.$appId;
320 320
 
321 321
 							$archiveError = $archive->getError();
322 322
 							if ($archiveError instanceof \PEAR_Error) {
323
-								$errorMessage .= ': ' . $archiveError->getMessage();
323
+								$errorMessage .= ': '.$archiveError->getMessage();
324 324
 							}
325 325
 
326 326
 							throw new \Exception($errorMessage);
@@ -340,21 +340,21 @@  discard block
 block discarded – undo
340 340
 
341 341
 						// Check if appinfo/info.xml has the same app ID as well
342 342
 						$loadEntities = libxml_disable_entity_loader(false);
343
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
343
+						$xml = simplexml_load_file($extractDir.'/'.$folders[0].'/appinfo/info.xml');
344 344
 						libxml_disable_entity_loader($loadEntities);
345
-						if ((string)$xml->id !== $appId) {
345
+						if ((string) $xml->id !== $appId) {
346 346
 							throw new \Exception(
347 347
 								sprintf(
348 348
 									'App for id %s has a wrong app ID in info.xml: %s',
349 349
 									$appId,
350
-									(string)$xml->id
350
+									(string) $xml->id
351 351
 								)
352 352
 							);
353 353
 						}
354 354
 
355 355
 						// Check if the version is lower than before
356 356
 						$currentVersion = OC_App::getAppVersion($appId);
357
-						$newVersion = (string)$xml->version;
357
+						$newVersion = (string) $xml->version;
358 358
 						if (version_compare($currentVersion, $newVersion) === 1) {
359 359
 							throw new \Exception(
360 360
 								sprintf(
@@ -366,12 +366,12 @@  discard block
 block discarded – undo
366 366
 							);
367 367
 						}
368 368
 
369
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
369
+						$baseDir = OC_App::getInstallPath().'/'.$appId;
370 370
 						// Remove old app with the ID if existent
371 371
 						OC_Helper::rmdirr($baseDir);
372 372
 						// Move to app folder
373 373
 						if (@mkdir($baseDir)) {
374
-							$extractDir .= '/' . $folders[0];
374
+							$extractDir .= '/'.$folders[0];
375 375
 							OC_Helper::copyr($extractDir, $baseDir);
376 376
 						}
377 377
 						OC_Helper::copyr($extractDir, $baseDir);
@@ -510,7 +510,7 @@  discard block
 block discarded – undo
510 510
 			if (\OC::$server->getAppManager()->isShipped($appId)) {
511 511
 				return false;
512 512
 			}
513
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
513
+			$appDir = OC_App::getInstallPath().'/'.$appId;
514 514
 			OC_Helper::rmdirr($appDir);
515 515
 			return true;
516 516
 		} else {
Please login to merge, or discard this patch.
lib/private/DB/Connection.php 1 patch
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -57,493 +57,493 @@
 block discarded – undo
57 57
 use OCP\PreConditionNotMetException;
58 58
 
59 59
 class Connection extends \Doctrine\DBAL\Connection {
60
-	/** @var string */
61
-	protected $tablePrefix;
62
-
63
-	/** @var \OC\DB\Adapter $adapter */
64
-	protected $adapter;
65
-
66
-	/** @var SystemConfig */
67
-	private $systemConfig;
68
-
69
-	/** @var ILogger */
70
-	private $logger;
71
-
72
-	protected $lockedTable = null;
73
-
74
-	/** @var int */
75
-	protected $queriesBuilt = 0;
76
-
77
-	/** @var int */
78
-	protected $queriesExecuted = 0;
79
-
80
-	/**
81
-	 * @throws Exception
82
-	 */
83
-	public function connect() {
84
-		try {
85
-			return parent::connect();
86
-		} catch (Exception $e) {
87
-			// throw a new exception to prevent leaking info from the stacktrace
88
-			throw new Exception('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
89
-		}
90
-	}
91
-
92
-	public function getStats(): array {
93
-		return [
94
-			'built' => $this->queriesBuilt,
95
-			'executed' => $this->queriesExecuted,
96
-		];
97
-	}
98
-
99
-	/**
100
-	 * Returns a QueryBuilder for the connection.
101
-	 */
102
-	public function getQueryBuilder(): IQueryBuilder {
103
-		$this->queriesBuilt++;
104
-		return new QueryBuilder(
105
-			new ConnectionAdapter($this),
106
-			$this->systemConfig,
107
-			$this->logger
108
-		);
109
-	}
110
-
111
-	/**
112
-	 * Gets the QueryBuilder for the connection.
113
-	 *
114
-	 * @return \Doctrine\DBAL\Query\QueryBuilder
115
-	 * @deprecated please use $this->getQueryBuilder() instead
116
-	 */
117
-	public function createQueryBuilder() {
118
-		$backtrace = $this->getCallerBacktrace();
119
-		\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
120
-		$this->queriesBuilt++;
121
-		return parent::createQueryBuilder();
122
-	}
123
-
124
-	/**
125
-	 * Gets the ExpressionBuilder for the connection.
126
-	 *
127
-	 * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
128
-	 * @deprecated please use $this->getQueryBuilder()->expr() instead
129
-	 */
130
-	public function getExpressionBuilder() {
131
-		$backtrace = $this->getCallerBacktrace();
132
-		\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
133
-		$this->queriesBuilt++;
134
-		return parent::getExpressionBuilder();
135
-	}
136
-
137
-	/**
138
-	 * Get the file and line that called the method where `getCallerBacktrace()` was used
139
-	 *
140
-	 * @return string
141
-	 */
142
-	protected function getCallerBacktrace() {
143
-		$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
144
-
145
-		// 0 is the method where we use `getCallerBacktrace`
146
-		// 1 is the target method which uses the method we want to log
147
-		if (isset($traces[1])) {
148
-			return $traces[1]['file'] . ':' . $traces[1]['line'];
149
-		}
150
-
151
-		return '';
152
-	}
153
-
154
-	/**
155
-	 * @return string
156
-	 */
157
-	public function getPrefix() {
158
-		return $this->tablePrefix;
159
-	}
160
-
161
-	/**
162
-	 * Initializes a new instance of the Connection class.
163
-	 *
164
-	 * @param array $params  The connection parameters.
165
-	 * @param \Doctrine\DBAL\Driver $driver
166
-	 * @param \Doctrine\DBAL\Configuration $config
167
-	 * @param \Doctrine\Common\EventManager $eventManager
168
-	 * @throws \Exception
169
-	 */
170
-	public function __construct(array $params, Driver $driver, Configuration $config = null,
171
-		EventManager $eventManager = null) {
172
-		if (!isset($params['adapter'])) {
173
-			throw new \Exception('adapter not set');
174
-		}
175
-		if (!isset($params['tablePrefix'])) {
176
-			throw new \Exception('tablePrefix not set');
177
-		}
178
-		/**
179
-		 * @psalm-suppress InternalMethod
180
-		 */
181
-		parent::__construct($params, $driver, $config, $eventManager);
182
-		$this->adapter = new $params['adapter']($this);
183
-		$this->tablePrefix = $params['tablePrefix'];
184
-
185
-		$this->systemConfig = \OC::$server->getSystemConfig();
186
-		$this->logger = \OC::$server->getLogger();
187
-	}
188
-
189
-	/**
190
-	 * Prepares an SQL statement.
191
-	 *
192
-	 * @param string $statement The SQL statement to prepare.
193
-	 * @param int $limit
194
-	 * @param int $offset
195
-	 *
196
-	 * @return Statement The prepared statement.
197
-	 * @throws Exception
198
-	 */
199
-	public function prepare($statement, $limit = null, $offset = null): Statement {
200
-		if ($limit === -1) {
201
-			$limit = null;
202
-		}
203
-		if (!is_null($limit)) {
204
-			$platform = $this->getDatabasePlatform();
205
-			$statement = $platform->modifyLimitQuery($statement, $limit, $offset);
206
-		}
207
-		$statement = $this->replaceTablePrefix($statement);
208
-		$statement = $this->adapter->fixupStatement($statement);
209
-
210
-		return parent::prepare($statement);
211
-	}
212
-
213
-	/**
214
-	 * Executes an, optionally parametrized, SQL query.
215
-	 *
216
-	 * If the query is parametrized, a prepared statement is used.
217
-	 * If an SQLLogger is configured, the execution is logged.
218
-	 *
219
-	 * @param string                                      $sql  The SQL query to execute.
220
-	 * @param array                                       $params The parameters to bind to the query, if any.
221
-	 * @param array                                       $types  The types the previous parameters are in.
222
-	 * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
223
-	 *
224
-	 * @return Result The executed statement.
225
-	 *
226
-	 * @throws \Doctrine\DBAL\Exception
227
-	 */
228
-	public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
229
-		$sql = $this->replaceTablePrefix($sql);
230
-		$sql = $this->adapter->fixupStatement($sql);
231
-		$this->queriesExecuted++;
232
-		return parent::executeQuery($sql, $params, $types, $qcp);
233
-	}
234
-
235
-	/**
236
-	 * @throws Exception
237
-	 */
238
-	public function executeUpdate(string $sql, array $params = [], array $types = []): int {
239
-		$sql = $this->replaceTablePrefix($sql);
240
-		$sql = $this->adapter->fixupStatement($sql);
241
-		$this->queriesExecuted++;
242
-		return parent::executeUpdate($sql, $params, $types);
243
-	}
244
-
245
-	/**
246
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
247
-	 * and returns the number of affected rows.
248
-	 *
249
-	 * This method supports PDO binding types as well as DBAL mapping types.
250
-	 *
251
-	 * @param string $sql  The SQL query.
252
-	 * @param array  $params The query parameters.
253
-	 * @param array  $types  The parameter types.
254
-	 *
255
-	 * @return int The number of affected rows.
256
-	 *
257
-	 * @throws \Doctrine\DBAL\Exception
258
-	 */
259
-	public function executeStatement($sql, array $params = [], array $types = []): int {
260
-		$sql = $this->replaceTablePrefix($sql);
261
-		$sql = $this->adapter->fixupStatement($sql);
262
-		$this->queriesExecuted++;
263
-		return parent::executeStatement($sql, $params, $types);
264
-	}
265
-
266
-	/**
267
-	 * Returns the ID of the last inserted row, or the last value from a sequence object,
268
-	 * depending on the underlying driver.
269
-	 *
270
-	 * Note: This method may not return a meaningful or consistent result across different drivers,
271
-	 * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
272
-	 * columns or sequences.
273
-	 *
274
-	 * @param string $seqName Name of the sequence object from which the ID should be returned.
275
-	 *
276
-	 * @return string the last inserted ID.
277
-	 * @throws Exception
278
-	 */
279
-	public function lastInsertId($seqName = null) {
280
-		if ($seqName) {
281
-			$seqName = $this->replaceTablePrefix($seqName);
282
-		}
283
-		return $this->adapter->lastInsertId($seqName);
284
-	}
285
-
286
-	/**
287
-	 * @internal
288
-	 * @throws Exception
289
-	 */
290
-	public function realLastInsertId($seqName = null) {
291
-		return parent::lastInsertId($seqName);
292
-	}
293
-
294
-	/**
295
-	 * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
296
-	 * it is needed that there is also a unique constraint on the values. Then this method will
297
-	 * catch the exception and return 0.
298
-	 *
299
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
300
-	 * @param array $input data that should be inserted into the table  (column name => value)
301
-	 * @param array|null $compare List of values that should be checked for "if not exists"
302
-	 *				If this is null or an empty array, all keys of $input will be compared
303
-	 *				Please note: text fields (clob) must not be used in the compare array
304
-	 * @return int number of inserted rows
305
-	 * @throws \Doctrine\DBAL\Exception
306
-	 * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
307
-	 */
308
-	public function insertIfNotExist($table, $input, array $compare = null) {
309
-		return $this->adapter->insertIfNotExist($table, $input, $compare);
310
-	}
311
-
312
-	public function insertIgnoreConflict(string $table, array $values) : int {
313
-		return $this->adapter->insertIgnoreConflict($table, $values);
314
-	}
315
-
316
-	private function getType($value) {
317
-		if (is_bool($value)) {
318
-			return IQueryBuilder::PARAM_BOOL;
319
-		} elseif (is_int($value)) {
320
-			return IQueryBuilder::PARAM_INT;
321
-		} else {
322
-			return IQueryBuilder::PARAM_STR;
323
-		}
324
-	}
325
-
326
-	/**
327
-	 * Insert or update a row value
328
-	 *
329
-	 * @param string $table
330
-	 * @param array $keys (column name => value)
331
-	 * @param array $values (column name => value)
332
-	 * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
333
-	 * @return int number of new rows
334
-	 * @throws \Doctrine\DBAL\Exception
335
-	 * @throws PreConditionNotMetException
336
-	 */
337
-	public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
338
-		try {
339
-			$insertQb = $this->getQueryBuilder();
340
-			$insertQb->insert($table)
341
-				->values(
342
-					array_map(function ($value) use ($insertQb) {
343
-						return $insertQb->createNamedParameter($value, $this->getType($value));
344
-					}, array_merge($keys, $values))
345
-				);
346
-			return $insertQb->execute();
347
-		} catch (NotNullConstraintViolationException $e) {
348
-			throw $e;
349
-		} catch (ConstraintViolationException $e) {
350
-			// value already exists, try update
351
-			$updateQb = $this->getQueryBuilder();
352
-			$updateQb->update($table);
353
-			foreach ($values as $name => $value) {
354
-				$updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
355
-			}
356
-			$where = $updateQb->expr()->andX();
357
-			$whereValues = array_merge($keys, $updatePreconditionValues);
358
-			foreach ($whereValues as $name => $value) {
359
-				if ($value === '') {
360
-					$where->add($updateQb->expr()->emptyString(
361
-						$name
362
-					));
363
-				} else {
364
-					$where->add($updateQb->expr()->eq(
365
-						$name,
366
-						$updateQb->createNamedParameter($value, $this->getType($value)),
367
-						$this->getType($value)
368
-					));
369
-				}
370
-			}
371
-			$updateQb->where($where);
372
-			$affected = $updateQb->execute();
373
-
374
-			if ($affected === 0 && !empty($updatePreconditionValues)) {
375
-				throw new PreConditionNotMetException();
376
-			}
377
-
378
-			return 0;
379
-		}
380
-	}
381
-
382
-	/**
383
-	 * Create an exclusive read+write lock on a table
384
-	 *
385
-	 * @param string $tableName
386
-	 *
387
-	 * @throws \BadMethodCallException When trying to acquire a second lock
388
-	 * @throws Exception
389
-	 * @since 9.1.0
390
-	 */
391
-	public function lockTable($tableName) {
392
-		if ($this->lockedTable !== null) {
393
-			throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
394
-		}
395
-
396
-		$tableName = $this->tablePrefix . $tableName;
397
-		$this->lockedTable = $tableName;
398
-		$this->adapter->lockTable($tableName);
399
-	}
400
-
401
-	/**
402
-	 * Release a previous acquired lock again
403
-	 *
404
-	 * @throws Exception
405
-	 * @since 9.1.0
406
-	 */
407
-	public function unlockTable() {
408
-		$this->adapter->unlockTable();
409
-		$this->lockedTable = null;
410
-	}
411
-
412
-	/**
413
-	 * returns the error code and message as a string for logging
414
-	 * works with DoctrineException
415
-	 * @return string
416
-	 */
417
-	public function getError() {
418
-		$msg = $this->errorCode() . ': ';
419
-		$errorInfo = $this->errorInfo();
420
-		if (!empty($errorInfo)) {
421
-			$msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
422
-			$msg .= 'Driver Code = '.$errorInfo[1] . ', ';
423
-			$msg .= 'Driver Message = '.$errorInfo[2];
424
-		}
425
-		return $msg;
426
-	}
427
-
428
-	public function errorCode() {
429
-		return -1;
430
-	}
431
-
432
-	public function errorInfo() {
433
-		return [];
434
-	}
435
-
436
-	/**
437
-	 * Drop a table from the database if it exists
438
-	 *
439
-	 * @param string $table table name without the prefix
440
-	 *
441
-	 * @throws Exception
442
-	 */
443
-	public function dropTable($table) {
444
-		$table = $this->tablePrefix . trim($table);
445
-		$schema = $this->getSchemaManager();
446
-		if ($schema->tablesExist([$table])) {
447
-			$schema->dropTable($table);
448
-		}
449
-	}
450
-
451
-	/**
452
-	 * Check if a table exists
453
-	 *
454
-	 * @param string $table table name without the prefix
455
-	 *
456
-	 * @return bool
457
-	 * @throws Exception
458
-	 */
459
-	public function tableExists($table) {
460
-		$table = $this->tablePrefix . trim($table);
461
-		$schema = $this->getSchemaManager();
462
-		return $schema->tablesExist([$table]);
463
-	}
464
-
465
-	// internal use
466
-	/**
467
-	 * @param string $statement
468
-	 * @return string
469
-	 */
470
-	protected function replaceTablePrefix($statement) {
471
-		return str_replace('*PREFIX*', $this->tablePrefix, $statement);
472
-	}
473
-
474
-	/**
475
-	 * Check if a transaction is active
476
-	 *
477
-	 * @return bool
478
-	 * @since 8.2.0
479
-	 */
480
-	public function inTransaction() {
481
-		return $this->getTransactionNestingLevel() > 0;
482
-	}
483
-
484
-	/**
485
-	 * Escape a parameter to be used in a LIKE query
486
-	 *
487
-	 * @param string $param
488
-	 * @return string
489
-	 */
490
-	public function escapeLikeParameter($param) {
491
-		return addcslashes($param, '\\_%');
492
-	}
493
-
494
-	/**
495
-	 * Check whether or not the current database support 4byte wide unicode
496
-	 *
497
-	 * @return bool
498
-	 * @since 11.0.0
499
-	 */
500
-	public function supports4ByteText() {
501
-		if (!$this->getDatabasePlatform() instanceof MySQLPlatform) {
502
-			return true;
503
-		}
504
-		return $this->getParams()['charset'] === 'utf8mb4';
505
-	}
506
-
507
-
508
-	/**
509
-	 * Create the schema of the connected database
510
-	 *
511
-	 * @return Schema
512
-	 * @throws Exception
513
-	 */
514
-	public function createSchema() {
515
-		$migrator = $this->getMigrator();
516
-		return $migrator->createSchema();
517
-	}
518
-
519
-	/**
520
-	 * Migrate the database to the given schema
521
-	 *
522
-	 * @param Schema $toSchema
523
-	 *
524
-	 * @throws Exception
525
-	 */
526
-	public function migrateToSchema(Schema $toSchema) {
527
-		$migrator = $this->getMigrator();
528
-		$migrator->migrate($toSchema);
529
-	}
530
-
531
-	private function getMigrator() {
532
-		// TODO properly inject those dependencies
533
-		$random = \OC::$server->getSecureRandom();
534
-		$platform = $this->getDatabasePlatform();
535
-		$config = \OC::$server->getConfig();
536
-		$dispatcher = \OC::$server->getEventDispatcher();
537
-		if ($platform instanceof SqlitePlatform) {
538
-			return new SQLiteMigrator($this, $config, $dispatcher);
539
-		} elseif ($platform instanceof OraclePlatform) {
540
-			return new OracleMigrator($this, $config, $dispatcher);
541
-		} elseif ($platform instanceof MySQLPlatform) {
542
-			return new MySQLMigrator($this, $config, $dispatcher);
543
-		} elseif ($platform instanceof PostgreSQL94Platform) {
544
-			return new PostgreSqlMigrator($this, $config, $dispatcher);
545
-		} else {
546
-			return new Migrator($this, $config, $dispatcher);
547
-		}
548
-	}
60
+    /** @var string */
61
+    protected $tablePrefix;
62
+
63
+    /** @var \OC\DB\Adapter $adapter */
64
+    protected $adapter;
65
+
66
+    /** @var SystemConfig */
67
+    private $systemConfig;
68
+
69
+    /** @var ILogger */
70
+    private $logger;
71
+
72
+    protected $lockedTable = null;
73
+
74
+    /** @var int */
75
+    protected $queriesBuilt = 0;
76
+
77
+    /** @var int */
78
+    protected $queriesExecuted = 0;
79
+
80
+    /**
81
+     * @throws Exception
82
+     */
83
+    public function connect() {
84
+        try {
85
+            return parent::connect();
86
+        } catch (Exception $e) {
87
+            // throw a new exception to prevent leaking info from the stacktrace
88
+            throw new Exception('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
89
+        }
90
+    }
91
+
92
+    public function getStats(): array {
93
+        return [
94
+            'built' => $this->queriesBuilt,
95
+            'executed' => $this->queriesExecuted,
96
+        ];
97
+    }
98
+
99
+    /**
100
+     * Returns a QueryBuilder for the connection.
101
+     */
102
+    public function getQueryBuilder(): IQueryBuilder {
103
+        $this->queriesBuilt++;
104
+        return new QueryBuilder(
105
+            new ConnectionAdapter($this),
106
+            $this->systemConfig,
107
+            $this->logger
108
+        );
109
+    }
110
+
111
+    /**
112
+     * Gets the QueryBuilder for the connection.
113
+     *
114
+     * @return \Doctrine\DBAL\Query\QueryBuilder
115
+     * @deprecated please use $this->getQueryBuilder() instead
116
+     */
117
+    public function createQueryBuilder() {
118
+        $backtrace = $this->getCallerBacktrace();
119
+        \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
120
+        $this->queriesBuilt++;
121
+        return parent::createQueryBuilder();
122
+    }
123
+
124
+    /**
125
+     * Gets the ExpressionBuilder for the connection.
126
+     *
127
+     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
128
+     * @deprecated please use $this->getQueryBuilder()->expr() instead
129
+     */
130
+    public function getExpressionBuilder() {
131
+        $backtrace = $this->getCallerBacktrace();
132
+        \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
133
+        $this->queriesBuilt++;
134
+        return parent::getExpressionBuilder();
135
+    }
136
+
137
+    /**
138
+     * Get the file and line that called the method where `getCallerBacktrace()` was used
139
+     *
140
+     * @return string
141
+     */
142
+    protected function getCallerBacktrace() {
143
+        $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
144
+
145
+        // 0 is the method where we use `getCallerBacktrace`
146
+        // 1 is the target method which uses the method we want to log
147
+        if (isset($traces[1])) {
148
+            return $traces[1]['file'] . ':' . $traces[1]['line'];
149
+        }
150
+
151
+        return '';
152
+    }
153
+
154
+    /**
155
+     * @return string
156
+     */
157
+    public function getPrefix() {
158
+        return $this->tablePrefix;
159
+    }
160
+
161
+    /**
162
+     * Initializes a new instance of the Connection class.
163
+     *
164
+     * @param array $params  The connection parameters.
165
+     * @param \Doctrine\DBAL\Driver $driver
166
+     * @param \Doctrine\DBAL\Configuration $config
167
+     * @param \Doctrine\Common\EventManager $eventManager
168
+     * @throws \Exception
169
+     */
170
+    public function __construct(array $params, Driver $driver, Configuration $config = null,
171
+        EventManager $eventManager = null) {
172
+        if (!isset($params['adapter'])) {
173
+            throw new \Exception('adapter not set');
174
+        }
175
+        if (!isset($params['tablePrefix'])) {
176
+            throw new \Exception('tablePrefix not set');
177
+        }
178
+        /**
179
+         * @psalm-suppress InternalMethod
180
+         */
181
+        parent::__construct($params, $driver, $config, $eventManager);
182
+        $this->adapter = new $params['adapter']($this);
183
+        $this->tablePrefix = $params['tablePrefix'];
184
+
185
+        $this->systemConfig = \OC::$server->getSystemConfig();
186
+        $this->logger = \OC::$server->getLogger();
187
+    }
188
+
189
+    /**
190
+     * Prepares an SQL statement.
191
+     *
192
+     * @param string $statement The SQL statement to prepare.
193
+     * @param int $limit
194
+     * @param int $offset
195
+     *
196
+     * @return Statement The prepared statement.
197
+     * @throws Exception
198
+     */
199
+    public function prepare($statement, $limit = null, $offset = null): Statement {
200
+        if ($limit === -1) {
201
+            $limit = null;
202
+        }
203
+        if (!is_null($limit)) {
204
+            $platform = $this->getDatabasePlatform();
205
+            $statement = $platform->modifyLimitQuery($statement, $limit, $offset);
206
+        }
207
+        $statement = $this->replaceTablePrefix($statement);
208
+        $statement = $this->adapter->fixupStatement($statement);
209
+
210
+        return parent::prepare($statement);
211
+    }
212
+
213
+    /**
214
+     * Executes an, optionally parametrized, SQL query.
215
+     *
216
+     * If the query is parametrized, a prepared statement is used.
217
+     * If an SQLLogger is configured, the execution is logged.
218
+     *
219
+     * @param string                                      $sql  The SQL query to execute.
220
+     * @param array                                       $params The parameters to bind to the query, if any.
221
+     * @param array                                       $types  The types the previous parameters are in.
222
+     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
223
+     *
224
+     * @return Result The executed statement.
225
+     *
226
+     * @throws \Doctrine\DBAL\Exception
227
+     */
228
+    public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
229
+        $sql = $this->replaceTablePrefix($sql);
230
+        $sql = $this->adapter->fixupStatement($sql);
231
+        $this->queriesExecuted++;
232
+        return parent::executeQuery($sql, $params, $types, $qcp);
233
+    }
234
+
235
+    /**
236
+     * @throws Exception
237
+     */
238
+    public function executeUpdate(string $sql, array $params = [], array $types = []): int {
239
+        $sql = $this->replaceTablePrefix($sql);
240
+        $sql = $this->adapter->fixupStatement($sql);
241
+        $this->queriesExecuted++;
242
+        return parent::executeUpdate($sql, $params, $types);
243
+    }
244
+
245
+    /**
246
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
247
+     * and returns the number of affected rows.
248
+     *
249
+     * This method supports PDO binding types as well as DBAL mapping types.
250
+     *
251
+     * @param string $sql  The SQL query.
252
+     * @param array  $params The query parameters.
253
+     * @param array  $types  The parameter types.
254
+     *
255
+     * @return int The number of affected rows.
256
+     *
257
+     * @throws \Doctrine\DBAL\Exception
258
+     */
259
+    public function executeStatement($sql, array $params = [], array $types = []): int {
260
+        $sql = $this->replaceTablePrefix($sql);
261
+        $sql = $this->adapter->fixupStatement($sql);
262
+        $this->queriesExecuted++;
263
+        return parent::executeStatement($sql, $params, $types);
264
+    }
265
+
266
+    /**
267
+     * Returns the ID of the last inserted row, or the last value from a sequence object,
268
+     * depending on the underlying driver.
269
+     *
270
+     * Note: This method may not return a meaningful or consistent result across different drivers,
271
+     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
272
+     * columns or sequences.
273
+     *
274
+     * @param string $seqName Name of the sequence object from which the ID should be returned.
275
+     *
276
+     * @return string the last inserted ID.
277
+     * @throws Exception
278
+     */
279
+    public function lastInsertId($seqName = null) {
280
+        if ($seqName) {
281
+            $seqName = $this->replaceTablePrefix($seqName);
282
+        }
283
+        return $this->adapter->lastInsertId($seqName);
284
+    }
285
+
286
+    /**
287
+     * @internal
288
+     * @throws Exception
289
+     */
290
+    public function realLastInsertId($seqName = null) {
291
+        return parent::lastInsertId($seqName);
292
+    }
293
+
294
+    /**
295
+     * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
296
+     * it is needed that there is also a unique constraint on the values. Then this method will
297
+     * catch the exception and return 0.
298
+     *
299
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
300
+     * @param array $input data that should be inserted into the table  (column name => value)
301
+     * @param array|null $compare List of values that should be checked for "if not exists"
302
+     *				If this is null or an empty array, all keys of $input will be compared
303
+     *				Please note: text fields (clob) must not be used in the compare array
304
+     * @return int number of inserted rows
305
+     * @throws \Doctrine\DBAL\Exception
306
+     * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
307
+     */
308
+    public function insertIfNotExist($table, $input, array $compare = null) {
309
+        return $this->adapter->insertIfNotExist($table, $input, $compare);
310
+    }
311
+
312
+    public function insertIgnoreConflict(string $table, array $values) : int {
313
+        return $this->adapter->insertIgnoreConflict($table, $values);
314
+    }
315
+
316
+    private function getType($value) {
317
+        if (is_bool($value)) {
318
+            return IQueryBuilder::PARAM_BOOL;
319
+        } elseif (is_int($value)) {
320
+            return IQueryBuilder::PARAM_INT;
321
+        } else {
322
+            return IQueryBuilder::PARAM_STR;
323
+        }
324
+    }
325
+
326
+    /**
327
+     * Insert or update a row value
328
+     *
329
+     * @param string $table
330
+     * @param array $keys (column name => value)
331
+     * @param array $values (column name => value)
332
+     * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
333
+     * @return int number of new rows
334
+     * @throws \Doctrine\DBAL\Exception
335
+     * @throws PreConditionNotMetException
336
+     */
337
+    public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
338
+        try {
339
+            $insertQb = $this->getQueryBuilder();
340
+            $insertQb->insert($table)
341
+                ->values(
342
+                    array_map(function ($value) use ($insertQb) {
343
+                        return $insertQb->createNamedParameter($value, $this->getType($value));
344
+                    }, array_merge($keys, $values))
345
+                );
346
+            return $insertQb->execute();
347
+        } catch (NotNullConstraintViolationException $e) {
348
+            throw $e;
349
+        } catch (ConstraintViolationException $e) {
350
+            // value already exists, try update
351
+            $updateQb = $this->getQueryBuilder();
352
+            $updateQb->update($table);
353
+            foreach ($values as $name => $value) {
354
+                $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
355
+            }
356
+            $where = $updateQb->expr()->andX();
357
+            $whereValues = array_merge($keys, $updatePreconditionValues);
358
+            foreach ($whereValues as $name => $value) {
359
+                if ($value === '') {
360
+                    $where->add($updateQb->expr()->emptyString(
361
+                        $name
362
+                    ));
363
+                } else {
364
+                    $where->add($updateQb->expr()->eq(
365
+                        $name,
366
+                        $updateQb->createNamedParameter($value, $this->getType($value)),
367
+                        $this->getType($value)
368
+                    ));
369
+                }
370
+            }
371
+            $updateQb->where($where);
372
+            $affected = $updateQb->execute();
373
+
374
+            if ($affected === 0 && !empty($updatePreconditionValues)) {
375
+                throw new PreConditionNotMetException();
376
+            }
377
+
378
+            return 0;
379
+        }
380
+    }
381
+
382
+    /**
383
+     * Create an exclusive read+write lock on a table
384
+     *
385
+     * @param string $tableName
386
+     *
387
+     * @throws \BadMethodCallException When trying to acquire a second lock
388
+     * @throws Exception
389
+     * @since 9.1.0
390
+     */
391
+    public function lockTable($tableName) {
392
+        if ($this->lockedTable !== null) {
393
+            throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
394
+        }
395
+
396
+        $tableName = $this->tablePrefix . $tableName;
397
+        $this->lockedTable = $tableName;
398
+        $this->adapter->lockTable($tableName);
399
+    }
400
+
401
+    /**
402
+     * Release a previous acquired lock again
403
+     *
404
+     * @throws Exception
405
+     * @since 9.1.0
406
+     */
407
+    public function unlockTable() {
408
+        $this->adapter->unlockTable();
409
+        $this->lockedTable = null;
410
+    }
411
+
412
+    /**
413
+     * returns the error code and message as a string for logging
414
+     * works with DoctrineException
415
+     * @return string
416
+     */
417
+    public function getError() {
418
+        $msg = $this->errorCode() . ': ';
419
+        $errorInfo = $this->errorInfo();
420
+        if (!empty($errorInfo)) {
421
+            $msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
422
+            $msg .= 'Driver Code = '.$errorInfo[1] . ', ';
423
+            $msg .= 'Driver Message = '.$errorInfo[2];
424
+        }
425
+        return $msg;
426
+    }
427
+
428
+    public function errorCode() {
429
+        return -1;
430
+    }
431
+
432
+    public function errorInfo() {
433
+        return [];
434
+    }
435
+
436
+    /**
437
+     * Drop a table from the database if it exists
438
+     *
439
+     * @param string $table table name without the prefix
440
+     *
441
+     * @throws Exception
442
+     */
443
+    public function dropTable($table) {
444
+        $table = $this->tablePrefix . trim($table);
445
+        $schema = $this->getSchemaManager();
446
+        if ($schema->tablesExist([$table])) {
447
+            $schema->dropTable($table);
448
+        }
449
+    }
450
+
451
+    /**
452
+     * Check if a table exists
453
+     *
454
+     * @param string $table table name without the prefix
455
+     *
456
+     * @return bool
457
+     * @throws Exception
458
+     */
459
+    public function tableExists($table) {
460
+        $table = $this->tablePrefix . trim($table);
461
+        $schema = $this->getSchemaManager();
462
+        return $schema->tablesExist([$table]);
463
+    }
464
+
465
+    // internal use
466
+    /**
467
+     * @param string $statement
468
+     * @return string
469
+     */
470
+    protected function replaceTablePrefix($statement) {
471
+        return str_replace('*PREFIX*', $this->tablePrefix, $statement);
472
+    }
473
+
474
+    /**
475
+     * Check if a transaction is active
476
+     *
477
+     * @return bool
478
+     * @since 8.2.0
479
+     */
480
+    public function inTransaction() {
481
+        return $this->getTransactionNestingLevel() > 0;
482
+    }
483
+
484
+    /**
485
+     * Escape a parameter to be used in a LIKE query
486
+     *
487
+     * @param string $param
488
+     * @return string
489
+     */
490
+    public function escapeLikeParameter($param) {
491
+        return addcslashes($param, '\\_%');
492
+    }
493
+
494
+    /**
495
+     * Check whether or not the current database support 4byte wide unicode
496
+     *
497
+     * @return bool
498
+     * @since 11.0.0
499
+     */
500
+    public function supports4ByteText() {
501
+        if (!$this->getDatabasePlatform() instanceof MySQLPlatform) {
502
+            return true;
503
+        }
504
+        return $this->getParams()['charset'] === 'utf8mb4';
505
+    }
506
+
507
+
508
+    /**
509
+     * Create the schema of the connected database
510
+     *
511
+     * @return Schema
512
+     * @throws Exception
513
+     */
514
+    public function createSchema() {
515
+        $migrator = $this->getMigrator();
516
+        return $migrator->createSchema();
517
+    }
518
+
519
+    /**
520
+     * Migrate the database to the given schema
521
+     *
522
+     * @param Schema $toSchema
523
+     *
524
+     * @throws Exception
525
+     */
526
+    public function migrateToSchema(Schema $toSchema) {
527
+        $migrator = $this->getMigrator();
528
+        $migrator->migrate($toSchema);
529
+    }
530
+
531
+    private function getMigrator() {
532
+        // TODO properly inject those dependencies
533
+        $random = \OC::$server->getSecureRandom();
534
+        $platform = $this->getDatabasePlatform();
535
+        $config = \OC::$server->getConfig();
536
+        $dispatcher = \OC::$server->getEventDispatcher();
537
+        if ($platform instanceof SqlitePlatform) {
538
+            return new SQLiteMigrator($this, $config, $dispatcher);
539
+        } elseif ($platform instanceof OraclePlatform) {
540
+            return new OracleMigrator($this, $config, $dispatcher);
541
+        } elseif ($platform instanceof MySQLPlatform) {
542
+            return new MySQLMigrator($this, $config, $dispatcher);
543
+        } elseif ($platform instanceof PostgreSQL94Platform) {
544
+            return new PostgreSqlMigrator($this, $config, $dispatcher);
545
+        } else {
546
+            return new Migrator($this, $config, $dispatcher);
547
+        }
548
+    }
549 549
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_DB.php 1 patch
Indentation   +142 added lines, -142 removed lines patch added patch discarded remove patch
@@ -36,155 +36,155 @@
 block discarded – undo
36 36
  */
37 37
 class OC_DB {
38 38
 
39
-	/**
40
-	 * Prepare a SQL query
41
-	 * @param string $query Query string
42
-	 * @param int|null $limit
43
-	 * @param int|null $offset
44
-	 * @param bool|null $isManipulation
45
-	 * @throws \OC\DatabaseException
46
-	 * @return OC_DB_StatementWrapper prepared SQL query
47
-	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
48
-	 *
49
-	 * SQL query via Doctrine prepare(), needs to be execute()'d!
50
-	 */
51
-	public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) {
52
-		$connection = \OC::$server->getDatabaseConnection();
39
+    /**
40
+     * Prepare a SQL query
41
+     * @param string $query Query string
42
+     * @param int|null $limit
43
+     * @param int|null $offset
44
+     * @param bool|null $isManipulation
45
+     * @throws \OC\DatabaseException
46
+     * @return OC_DB_StatementWrapper prepared SQL query
47
+     * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
48
+     *
49
+     * SQL query via Doctrine prepare(), needs to be execute()'d!
50
+     */
51
+    public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) {
52
+        $connection = \OC::$server->getDatabaseConnection();
53 53
 
54
-		if ($isManipulation === null) {
55
-			//try to guess, so we return the number of rows on manipulations
56
-			$isManipulation = self::isManipulation($query);
57
-		}
54
+        if ($isManipulation === null) {
55
+            //try to guess, so we return the number of rows on manipulations
56
+            $isManipulation = self::isManipulation($query);
57
+        }
58 58
 
59
-		// return the result
60
-		try {
61
-			$result = $connection->prepare($query, $limit, $offset);
62
-		} catch (\Doctrine\DBAL\Exception $e) {
63
-			throw new \OC\DatabaseException($e->getMessage());
64
-		}
65
-		// differentiate between query and manipulation
66
-		return new OC_DB_StatementWrapper($result, $isManipulation);
67
-	}
59
+        // return the result
60
+        try {
61
+            $result = $connection->prepare($query, $limit, $offset);
62
+        } catch (\Doctrine\DBAL\Exception $e) {
63
+            throw new \OC\DatabaseException($e->getMessage());
64
+        }
65
+        // differentiate between query and manipulation
66
+        return new OC_DB_StatementWrapper($result, $isManipulation);
67
+    }
68 68
 
69
-	/**
70
-	 * tries to guess the type of statement based on the first 10 characters
71
-	 * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
72
-	 *
73
-	 * @param string $sql
74
-	 * @return bool
75
-	 */
76
-	public static function isManipulation($sql) {
77
-		$sql = trim($sql);
78
-		$selectOccurrence = stripos($sql, 'SELECT');
79
-		if ($selectOccurrence === 0) {
80
-			return false;
81
-		}
82
-		$insertOccurrence = stripos($sql, 'INSERT');
83
-		if ($insertOccurrence === 0) {
84
-			return true;
85
-		}
86
-		$updateOccurrence = stripos($sql, 'UPDATE');
87
-		if ($updateOccurrence === 0) {
88
-			return true;
89
-		}
90
-		$deleteOccurrence = stripos($sql, 'DELETE');
91
-		if ($deleteOccurrence === 0) {
92
-			return true;
93
-		}
69
+    /**
70
+     * tries to guess the type of statement based on the first 10 characters
71
+     * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
72
+     *
73
+     * @param string $sql
74
+     * @return bool
75
+     */
76
+    public static function isManipulation($sql) {
77
+        $sql = trim($sql);
78
+        $selectOccurrence = stripos($sql, 'SELECT');
79
+        if ($selectOccurrence === 0) {
80
+            return false;
81
+        }
82
+        $insertOccurrence = stripos($sql, 'INSERT');
83
+        if ($insertOccurrence === 0) {
84
+            return true;
85
+        }
86
+        $updateOccurrence = stripos($sql, 'UPDATE');
87
+        if ($updateOccurrence === 0) {
88
+            return true;
89
+        }
90
+        $deleteOccurrence = stripos($sql, 'DELETE');
91
+        if ($deleteOccurrence === 0) {
92
+            return true;
93
+        }
94 94
 
95
-		// This is triggered with "SHOW VERSION" and some more, so until we made a list, we keep this out.
96
-		// \OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql));
95
+        // This is triggered with "SHOW VERSION" and some more, so until we made a list, we keep this out.
96
+        // \OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql));
97 97
 
98
-		return false;
99
-	}
98
+        return false;
99
+    }
100 100
 
101
-	/**
102
-	 * execute a prepared statement, on error write log and throw exception
103
-	 * @param mixed $stmt OC_DB_StatementWrapper,
104
-	 *					  an array with 'sql' and optionally 'limit' and 'offset' keys
105
-	 *					.. or a simple sql query string
106
-	 * @param array $parameters
107
-	 * @return OC_DB_StatementWrapper
108
-	 * @throws \OC\DatabaseException
109
-	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
110
-	 */
111
-	public static function executeAudited($stmt, array $parameters = []) {
112
-		if (is_string($stmt)) {
113
-			// convert to an array with 'sql'
114
-			if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
115
-				// TODO try to convert LIMIT OFFSET notation to parameters
116
-				$message = 'LIMIT and OFFSET are forbidden for portability reasons,'
117
-						 . ' pass an array with \'limit\' and \'offset\' instead';
118
-				throw new \OC\DatabaseException($message);
119
-			}
120
-			$stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
121
-		}
122
-		if (is_array($stmt)) {
123
-			// convert to prepared statement
124
-			if (! array_key_exists('sql', $stmt)) {
125
-				$message = 'statement array must at least contain key \'sql\'';
126
-				throw new \OC\DatabaseException($message);
127
-			}
128
-			if (! array_key_exists('limit', $stmt)) {
129
-				$stmt['limit'] = null;
130
-			}
131
-			if (! array_key_exists('limit', $stmt)) {
132
-				$stmt['offset'] = null;
133
-			}
134
-			$stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
135
-		}
136
-		self::raiseExceptionOnError($stmt, 'Could not prepare statement');
137
-		if ($stmt instanceof OC_DB_StatementWrapper) {
138
-			$result = $stmt->execute($parameters);
139
-			self::raiseExceptionOnError($result, 'Could not execute statement');
140
-		} else {
141
-			if (is_object($stmt)) {
142
-				$message = 'Expected a prepared statement or array got ' . get_class($stmt);
143
-			} else {
144
-				$message = 'Expected a prepared statement or array got ' . gettype($stmt);
145
-			}
146
-			throw new \OC\DatabaseException($message);
147
-		}
148
-		return $result;
149
-	}
101
+    /**
102
+     * execute a prepared statement, on error write log and throw exception
103
+     * @param mixed $stmt OC_DB_StatementWrapper,
104
+     *					  an array with 'sql' and optionally 'limit' and 'offset' keys
105
+     *					.. or a simple sql query string
106
+     * @param array $parameters
107
+     * @return OC_DB_StatementWrapper
108
+     * @throws \OC\DatabaseException
109
+     * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
110
+     */
111
+    public static function executeAudited($stmt, array $parameters = []) {
112
+        if (is_string($stmt)) {
113
+            // convert to an array with 'sql'
114
+            if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
115
+                // TODO try to convert LIMIT OFFSET notation to parameters
116
+                $message = 'LIMIT and OFFSET are forbidden for portability reasons,'
117
+                            . ' pass an array with \'limit\' and \'offset\' instead';
118
+                throw new \OC\DatabaseException($message);
119
+            }
120
+            $stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
121
+        }
122
+        if (is_array($stmt)) {
123
+            // convert to prepared statement
124
+            if (! array_key_exists('sql', $stmt)) {
125
+                $message = 'statement array must at least contain key \'sql\'';
126
+                throw new \OC\DatabaseException($message);
127
+            }
128
+            if (! array_key_exists('limit', $stmt)) {
129
+                $stmt['limit'] = null;
130
+            }
131
+            if (! array_key_exists('limit', $stmt)) {
132
+                $stmt['offset'] = null;
133
+            }
134
+            $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
135
+        }
136
+        self::raiseExceptionOnError($stmt, 'Could not prepare statement');
137
+        if ($stmt instanceof OC_DB_StatementWrapper) {
138
+            $result = $stmt->execute($parameters);
139
+            self::raiseExceptionOnError($result, 'Could not execute statement');
140
+        } else {
141
+            if (is_object($stmt)) {
142
+                $message = 'Expected a prepared statement or array got ' . get_class($stmt);
143
+            } else {
144
+                $message = 'Expected a prepared statement or array got ' . gettype($stmt);
145
+            }
146
+            throw new \OC\DatabaseException($message);
147
+        }
148
+        return $result;
149
+    }
150 150
 
151
-	/**
152
-	 * check if a result is an error and throws an exception, works with \Doctrine\DBAL\Exception
153
-	 * @param mixed $result
154
-	 * @param string $message
155
-	 * @return void
156
-	 * @throws \OC\DatabaseException
157
-	 */
158
-	public static function raiseExceptionOnError($result, $message = null) {
159
-		if ($result === false) {
160
-			if ($message === null) {
161
-				$message = self::getErrorMessage();
162
-			} else {
163
-				$message .= ', Root cause:' . self::getErrorMessage();
164
-			}
165
-			throw new \OC\DatabaseException($message);
166
-		}
167
-	}
151
+    /**
152
+     * check if a result is an error and throws an exception, works with \Doctrine\DBAL\Exception
153
+     * @param mixed $result
154
+     * @param string $message
155
+     * @return void
156
+     * @throws \OC\DatabaseException
157
+     */
158
+    public static function raiseExceptionOnError($result, $message = null) {
159
+        if ($result === false) {
160
+            if ($message === null) {
161
+                $message = self::getErrorMessage();
162
+            } else {
163
+                $message .= ', Root cause:' . self::getErrorMessage();
164
+            }
165
+            throw new \OC\DatabaseException($message);
166
+        }
167
+    }
168 168
 
169
-	/**
170
-	 * returns the error code and message as a string for logging
171
-	 * works with DoctrineException
172
-	 * @return string
173
-	 */
174
-	public static function getErrorMessage() {
175
-		$connection = \OC::$server->getDatabaseConnection();
176
-		return $connection->getError();
177
-	}
169
+    /**
170
+     * returns the error code and message as a string for logging
171
+     * works with DoctrineException
172
+     * @return string
173
+     */
174
+    public static function getErrorMessage() {
175
+        $connection = \OC::$server->getDatabaseConnection();
176
+        return $connection->getError();
177
+    }
178 178
 
179
-	/**
180
-	 * Checks if a table exists in the database - the database prefix will be prepended
181
-	 *
182
-	 * @param string $table
183
-	 * @return bool
184
-	 * @throws \OC\DatabaseException
185
-	 */
186
-	public static function tableExists($table) {
187
-		$connection = \OC::$server->getDatabaseConnection();
188
-		return $connection->tableExists($table);
189
-	}
179
+    /**
180
+     * Checks if a table exists in the database - the database prefix will be prepended
181
+     *
182
+     * @param string $table
183
+     * @return bool
184
+     * @throws \OC\DatabaseException
185
+     */
186
+    public static function tableExists($table) {
187
+        $connection = \OC::$server->getDatabaseConnection();
188
+        return $connection->tableExists($table);
189
+    }
190 190
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_App.php 2 patches
Indentation   +1122 added lines, -1122 removed lines patch added patch discarded remove patch
@@ -69,1126 +69,1126 @@
 block discarded – undo
69 69
  * upgrading and removing apps.
70 70
  */
71 71
 class OC_App {
72
-	private static $adminForms = [];
73
-	private static $personalForms = [];
74
-	private static $appTypes = [];
75
-	private static $loadedApps = [];
76
-	private static $altLogin = [];
77
-	private static $alreadyRegistered = [];
78
-	public const supportedApp = 300;
79
-	public const officialApp = 200;
80
-
81
-	/**
82
-	 * clean the appId
83
-	 *
84
-	 * @psalm-taint-escape file
85
-	 * @psalm-taint-escape include
86
-	 *
87
-	 * @param string $app AppId that needs to be cleaned
88
-	 * @return string
89
-	 */
90
-	public static function cleanAppId(string $app): string {
91
-		return str_replace(['\0', '/', '\\', '..'], '', $app);
92
-	}
93
-
94
-	/**
95
-	 * Check if an app is loaded
96
-	 *
97
-	 * @param string $app
98
-	 * @return bool
99
-	 */
100
-	public static function isAppLoaded(string $app): bool {
101
-		return isset(self::$loadedApps[$app]);
102
-	}
103
-
104
-	/**
105
-	 * loads all apps
106
-	 *
107
-	 * @param string[] $types
108
-	 * @return bool
109
-	 *
110
-	 * This function walks through the ownCloud directory and loads all apps
111
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
112
-	 * exists.
113
-	 *
114
-	 * if $types is set to non-empty array, only apps of those types will be loaded
115
-	 */
116
-	public static function loadApps(array $types = []): bool {
117
-		if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
118
-			return false;
119
-		}
120
-		// Load the enabled apps here
121
-		$apps = self::getEnabledApps();
122
-
123
-		// Add each apps' folder as allowed class path
124
-		foreach ($apps as $app) {
125
-			// If the app is already loaded then autoloading it makes no sense
126
-			if (!isset(self::$loadedApps[$app])) {
127
-				$path = self::getAppPath($app);
128
-				if ($path !== false) {
129
-					self::registerAutoloading($app, $path);
130
-				}
131
-			}
132
-		}
133
-
134
-		// prevent app.php from printing output
135
-		ob_start();
136
-		foreach ($apps as $app) {
137
-			if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) {
138
-				self::loadApp($app);
139
-			}
140
-		}
141
-		ob_end_clean();
142
-
143
-		return true;
144
-	}
145
-
146
-	/**
147
-	 * load a single app
148
-	 *
149
-	 * @param string $app
150
-	 * @throws Exception
151
-	 */
152
-	public static function loadApp(string $app) {
153
-		self::$loadedApps[$app] = true;
154
-		$appPath = self::getAppPath($app);
155
-		if ($appPath === false) {
156
-			return;
157
-		}
158
-
159
-		// in case someone calls loadApp() directly
160
-		self::registerAutoloading($app, $appPath);
161
-
162
-		/** @var Coordinator $coordinator */
163
-		$coordinator = \OC::$server->query(Coordinator::class);
164
-		$isBootable = $coordinator->isBootable($app);
165
-
166
-		$hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
167
-
168
-		if ($isBootable && $hasAppPhpFile) {
169
-			\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.', [
170
-				'app' => $app,
171
-			]);
172
-		} elseif ($hasAppPhpFile) {
173
-			\OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
174
-				'app' => $app,
175
-			]);
176
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
177
-			try {
178
-				self::requireAppFile($app);
179
-			} catch (Throwable $ex) {
180
-				if ($ex instanceof ServerNotAvailableException) {
181
-					throw $ex;
182
-				}
183
-				if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
184
-					\OC::$server->getLogger()->logException($ex, [
185
-						'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
186
-					]);
187
-
188
-					// Only disable apps which are not shipped and that are not authentication apps
189
-					\OC::$server->getAppManager()->disableApp($app, true);
190
-				} else {
191
-					\OC::$server->getLogger()->logException($ex, [
192
-						'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
193
-					]);
194
-				}
195
-			}
196
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
197
-		}
198
-		$coordinator->bootApp($app);
199
-
200
-		$info = self::getAppInfo($app);
201
-		if (!empty($info['activity']['filters'])) {
202
-			foreach ($info['activity']['filters'] as $filter) {
203
-				\OC::$server->getActivityManager()->registerFilter($filter);
204
-			}
205
-		}
206
-		if (!empty($info['activity']['settings'])) {
207
-			foreach ($info['activity']['settings'] as $setting) {
208
-				\OC::$server->getActivityManager()->registerSetting($setting);
209
-			}
210
-		}
211
-		if (!empty($info['activity']['providers'])) {
212
-			foreach ($info['activity']['providers'] as $provider) {
213
-				\OC::$server->getActivityManager()->registerProvider($provider);
214
-			}
215
-		}
216
-
217
-		if (!empty($info['settings']['admin'])) {
218
-			foreach ($info['settings']['admin'] as $setting) {
219
-				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
220
-			}
221
-		}
222
-		if (!empty($info['settings']['admin-section'])) {
223
-			foreach ($info['settings']['admin-section'] as $section) {
224
-				\OC::$server->getSettingsManager()->registerSection('admin', $section);
225
-			}
226
-		}
227
-		if (!empty($info['settings']['personal'])) {
228
-			foreach ($info['settings']['personal'] as $setting) {
229
-				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
230
-			}
231
-		}
232
-		if (!empty($info['settings']['personal-section'])) {
233
-			foreach ($info['settings']['personal-section'] as $section) {
234
-				\OC::$server->getSettingsManager()->registerSection('personal', $section);
235
-			}
236
-		}
237
-
238
-		if (!empty($info['collaboration']['plugins'])) {
239
-			// deal with one or many plugin entries
240
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
241
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
242
-			foreach ($plugins as $plugin) {
243
-				if ($plugin['@attributes']['type'] === 'collaborator-search') {
244
-					$pluginInfo = [
245
-						'shareType' => $plugin['@attributes']['share-type'],
246
-						'class' => $plugin['@value'],
247
-					];
248
-					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
249
-				} elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
250
-					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
251
-				}
252
-			}
253
-		}
254
-	}
255
-
256
-	/**
257
-	 * @internal
258
-	 * @param string $app
259
-	 * @param string $path
260
-	 * @param bool $force
261
-	 */
262
-	public static function registerAutoloading(string $app, string $path, bool $force = false) {
263
-		$key = $app . '-' . $path;
264
-		if (!$force && isset(self::$alreadyRegistered[$key])) {
265
-			return;
266
-		}
267
-
268
-		self::$alreadyRegistered[$key] = true;
269
-
270
-		// Register on PSR-4 composer autoloader
271
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
272
-		\OC::$server->registerNamespace($app, $appNamespace);
273
-
274
-		if (file_exists($path . '/composer/autoload.php')) {
275
-			require_once $path . '/composer/autoload.php';
276
-		} else {
277
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
278
-			// Register on legacy autoloader
279
-			\OC::$loader->addValidRoot($path);
280
-		}
281
-
282
-		// Register Test namespace only when testing
283
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
284
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
285
-		}
286
-	}
287
-
288
-	/**
289
-	 * Load app.php from the given app
290
-	 *
291
-	 * @param string $app app name
292
-	 * @throws Error
293
-	 */
294
-	private static function requireAppFile(string $app) {
295
-		// encapsulated here to avoid variable scope conflicts
296
-		require_once $app . '/appinfo/app.php';
297
-	}
298
-
299
-	/**
300
-	 * check if an app is of a specific type
301
-	 *
302
-	 * @param string $app
303
-	 * @param array $types
304
-	 * @return bool
305
-	 */
306
-	public static function isType(string $app, array $types): bool {
307
-		$appTypes = self::getAppTypes($app);
308
-		foreach ($types as $type) {
309
-			if (array_search($type, $appTypes) !== false) {
310
-				return true;
311
-			}
312
-		}
313
-		return false;
314
-	}
315
-
316
-	/**
317
-	 * get the types of an app
318
-	 *
319
-	 * @param string $app
320
-	 * @return array
321
-	 */
322
-	private static function getAppTypes(string $app): array {
323
-		//load the cache
324
-		if (count(self::$appTypes) == 0) {
325
-			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
326
-		}
327
-
328
-		if (isset(self::$appTypes[$app])) {
329
-			return explode(',', self::$appTypes[$app]);
330
-		}
331
-
332
-		return [];
333
-	}
334
-
335
-	/**
336
-	 * read app types from info.xml and cache them in the database
337
-	 */
338
-	public static function setAppTypes(string $app) {
339
-		$appManager = \OC::$server->getAppManager();
340
-		$appData = $appManager->getAppInfo($app);
341
-		if (!is_array($appData)) {
342
-			return;
343
-		}
344
-
345
-		if (isset($appData['types'])) {
346
-			$appTypes = implode(',', $appData['types']);
347
-		} else {
348
-			$appTypes = '';
349
-			$appData['types'] = [];
350
-		}
351
-
352
-		$config = \OC::$server->getConfig();
353
-		$config->setAppValue($app, 'types', $appTypes);
354
-
355
-		if ($appManager->hasProtectedAppType($appData['types'])) {
356
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
357
-			if ($enabled !== 'yes' && $enabled !== 'no') {
358
-				$config->setAppValue($app, 'enabled', 'yes');
359
-			}
360
-		}
361
-	}
362
-
363
-	/**
364
-	 * Returns apps enabled for the current user.
365
-	 *
366
-	 * @param bool $forceRefresh whether to refresh the cache
367
-	 * @param bool $all whether to return apps for all users, not only the
368
-	 * currently logged in one
369
-	 * @return string[]
370
-	 */
371
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
372
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
373
-			return [];
374
-		}
375
-		// in incognito mode or when logged out, $user will be false,
376
-		// which is also the case during an upgrade
377
-		$appManager = \OC::$server->getAppManager();
378
-		if ($all) {
379
-			$user = null;
380
-		} else {
381
-			$user = \OC::$server->getUserSession()->getUser();
382
-		}
383
-
384
-		if (is_null($user)) {
385
-			$apps = $appManager->getInstalledApps();
386
-		} else {
387
-			$apps = $appManager->getEnabledAppsForUser($user);
388
-		}
389
-		$apps = array_filter($apps, function ($app) {
390
-			return $app !== 'files';//we add this manually
391
-		});
392
-		sort($apps);
393
-		array_unshift($apps, 'files');
394
-		return $apps;
395
-	}
396
-
397
-	/**
398
-	 * checks whether or not an app is enabled
399
-	 *
400
-	 * @param string $app app
401
-	 * @return bool
402
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
403
-	 *
404
-	 * This function checks whether or not an app is enabled.
405
-	 */
406
-	public static function isEnabled(string $app): bool {
407
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
408
-	}
409
-
410
-	/**
411
-	 * enables an app
412
-	 *
413
-	 * @param string $appId
414
-	 * @param array $groups (optional) when set, only these groups will have access to the app
415
-	 * @throws \Exception
416
-	 * @return void
417
-	 *
418
-	 * This function set an app as enabled in appconfig.
419
-	 */
420
-	public function enable(string $appId,
421
-						   array $groups = []) {
422
-
423
-		// Check if app is already downloaded
424
-		/** @var Installer $installer */
425
-		$installer = \OC::$server->query(Installer::class);
426
-		$isDownloaded = $installer->isDownloaded($appId);
427
-
428
-		if (!$isDownloaded) {
429
-			$installer->downloadApp($appId);
430
-		}
431
-
432
-		$installer->installApp($appId);
433
-
434
-		$appManager = \OC::$server->getAppManager();
435
-		if ($groups !== []) {
436
-			$groupManager = \OC::$server->getGroupManager();
437
-			$groupsList = [];
438
-			foreach ($groups as $group) {
439
-				$groupItem = $groupManager->get($group);
440
-				if ($groupItem instanceof \OCP\IGroup) {
441
-					$groupsList[] = $groupManager->get($group);
442
-				}
443
-			}
444
-			$appManager->enableAppForGroups($appId, $groupsList);
445
-		} else {
446
-			$appManager->enableApp($appId);
447
-		}
448
-	}
449
-
450
-	/**
451
-	 * Get the path where to install apps
452
-	 *
453
-	 * @return string|false
454
-	 */
455
-	public static function getInstallPath() {
456
-		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
457
-			return false;
458
-		}
459
-
460
-		foreach (OC::$APPSROOTS as $dir) {
461
-			if (isset($dir['writable']) && $dir['writable'] === true) {
462
-				return $dir['path'];
463
-			}
464
-		}
465
-
466
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
467
-		return null;
468
-	}
469
-
470
-
471
-	/**
472
-	 * search for an app in all app-directories
473
-	 *
474
-	 * @param string $appId
475
-	 * @return false|string
476
-	 */
477
-	public static function findAppInDirectories(string $appId) {
478
-		$sanitizedAppId = self::cleanAppId($appId);
479
-		if ($sanitizedAppId !== $appId) {
480
-			return false;
481
-		}
482
-		static $app_dir = [];
483
-
484
-		if (isset($app_dir[$appId])) {
485
-			return $app_dir[$appId];
486
-		}
487
-
488
-		$possibleApps = [];
489
-		foreach (OC::$APPSROOTS as $dir) {
490
-			if (file_exists($dir['path'] . '/' . $appId)) {
491
-				$possibleApps[] = $dir;
492
-			}
493
-		}
494
-
495
-		if (empty($possibleApps)) {
496
-			return false;
497
-		} elseif (count($possibleApps) === 1) {
498
-			$dir = array_shift($possibleApps);
499
-			$app_dir[$appId] = $dir;
500
-			return $dir;
501
-		} else {
502
-			$versionToLoad = [];
503
-			foreach ($possibleApps as $possibleApp) {
504
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
505
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
506
-					$versionToLoad = [
507
-						'dir' => $possibleApp,
508
-						'version' => $version,
509
-					];
510
-				}
511
-			}
512
-			$app_dir[$appId] = $versionToLoad['dir'];
513
-			return $versionToLoad['dir'];
514
-			//TODO - write test
515
-		}
516
-	}
517
-
518
-	/**
519
-	 * Get the directory for the given app.
520
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
521
-	 *
522
-	 * @psalm-taint-specialize
523
-	 *
524
-	 * @param string $appId
525
-	 * @return string|false
526
-	 * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
527
-	 */
528
-	public static function getAppPath(string $appId) {
529
-		if ($appId === null || trim($appId) === '') {
530
-			return false;
531
-		}
532
-
533
-		if (($dir = self::findAppInDirectories($appId)) != false) {
534
-			return $dir['path'] . '/' . $appId;
535
-		}
536
-		return false;
537
-	}
538
-
539
-	/**
540
-	 * Get the path for the given app on the access
541
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
542
-	 *
543
-	 * @param string $appId
544
-	 * @return string|false
545
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
546
-	 */
547
-	public static function getAppWebPath(string $appId) {
548
-		if (($dir = self::findAppInDirectories($appId)) != false) {
549
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
550
-		}
551
-		return false;
552
-	}
553
-
554
-	/**
555
-	 * get the last version of the app from appinfo/info.xml
556
-	 *
557
-	 * @param string $appId
558
-	 * @param bool $useCache
559
-	 * @return string
560
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
561
-	 */
562
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
563
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
564
-	}
565
-
566
-	/**
567
-	 * get app's version based on it's path
568
-	 *
569
-	 * @param string $path
570
-	 * @return string
571
-	 */
572
-	public static function getAppVersionByPath(string $path): string {
573
-		$infoFile = $path . '/appinfo/info.xml';
574
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
575
-		return isset($appData['version']) ? $appData['version'] : '';
576
-	}
577
-
578
-
579
-	/**
580
-	 * Read all app metadata from the info.xml file
581
-	 *
582
-	 * @param string $appId id of the app or the path of the info.xml file
583
-	 * @param bool $path
584
-	 * @param string $lang
585
-	 * @return array|null
586
-	 * @note all data is read from info.xml, not just pre-defined fields
587
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
588
-	 */
589
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
590
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
591
-	}
592
-
593
-	/**
594
-	 * Returns the navigation
595
-	 *
596
-	 * @return array
597
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
598
-	 *
599
-	 * This function returns an array containing all entries added. The
600
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
601
-	 * given for each app the following keys exist:
602
-	 *   - active: boolean, signals if the user is on this navigation entry
603
-	 */
604
-	public static function getNavigation(): array {
605
-		return OC::$server->getNavigationManager()->getAll();
606
-	}
607
-
608
-	/**
609
-	 * Returns the Settings Navigation
610
-	 *
611
-	 * @return string[]
612
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
613
-	 *
614
-	 * This function returns an array containing all settings pages added. The
615
-	 * entries are sorted by the key 'order' ascending.
616
-	 */
617
-	public static function getSettingsNavigation(): array {
618
-		return OC::$server->getNavigationManager()->getAll('settings');
619
-	}
620
-
621
-	/**
622
-	 * get the id of loaded app
623
-	 *
624
-	 * @return string
625
-	 */
626
-	public static function getCurrentApp(): string {
627
-		$request = \OC::$server->getRequest();
628
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
629
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
630
-		if (empty($topFolder)) {
631
-			$path_info = $request->getPathInfo();
632
-			if ($path_info) {
633
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
634
-			}
635
-		}
636
-		if ($topFolder == 'apps') {
637
-			$length = strlen($topFolder);
638
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
639
-		} else {
640
-			return $topFolder;
641
-		}
642
-	}
643
-
644
-	/**
645
-	 * @param string $type
646
-	 * @return array
647
-	 */
648
-	public static function getForms(string $type): array {
649
-		$forms = [];
650
-		switch ($type) {
651
-			case 'admin':
652
-				$source = self::$adminForms;
653
-				break;
654
-			case 'personal':
655
-				$source = self::$personalForms;
656
-				break;
657
-			default:
658
-				return [];
659
-		}
660
-		foreach ($source as $form) {
661
-			$forms[] = include $form;
662
-		}
663
-		return $forms;
664
-	}
665
-
666
-	/**
667
-	 * register an admin form to be shown
668
-	 *
669
-	 * @param string $app
670
-	 * @param string $page
671
-	 */
672
-	public static function registerAdmin(string $app, string $page) {
673
-		self::$adminForms[] = $app . '/' . $page . '.php';
674
-	}
675
-
676
-	/**
677
-	 * register a personal form to be shown
678
-	 * @param string $app
679
-	 * @param string $page
680
-	 */
681
-	public static function registerPersonal(string $app, string $page) {
682
-		self::$personalForms[] = $app . '/' . $page . '.php';
683
-	}
684
-
685
-	/**
686
-	 * @param array $entry
687
-	 * @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
688
-	 */
689
-	public static function registerLogIn(array $entry) {
690
-		\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');
691
-		self::$altLogin[] = $entry;
692
-	}
693
-
694
-	/**
695
-	 * @return array
696
-	 */
697
-	public static function getAlternativeLogIns(): array {
698
-		/** @var Coordinator $bootstrapCoordinator */
699
-		$bootstrapCoordinator = \OC::$server->query(Coordinator::class);
700
-
701
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
702
-			if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
703
-				\OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
704
-					'option' => $registration->getService(),
705
-					'interface' => IAlternativeLogin::class,
706
-					'app' => $registration->getAppId(),
707
-				]);
708
-				continue;
709
-			}
710
-
711
-			try {
712
-				/** @var IAlternativeLogin $provider */
713
-				$provider = \OC::$server->query($registration->getService());
714
-			} catch (QueryException $e) {
715
-				\OC::$server->getLogger()->logException($e, [
716
-					'message' => 'Alternative login option {option} can not be initialised.',
717
-					'option' => $registration->getService(),
718
-					'app' => $registration->getAppId(),
719
-				]);
720
-			}
721
-
722
-			try {
723
-				$provider->load();
724
-
725
-				self::$altLogin[] = [
726
-					'name' => $provider->getLabel(),
727
-					'href' => $provider->getLink(),
728
-					'style' => $provider->getClass(),
729
-				];
730
-			} catch (Throwable $e) {
731
-				\OC::$server->getLogger()->logException($e, [
732
-					'message' => 'Alternative login option {option} had an error while loading.',
733
-					'option' => $registration->getService(),
734
-					'app' => $registration->getAppId(),
735
-				]);
736
-			}
737
-		}
738
-
739
-		return self::$altLogin;
740
-	}
741
-
742
-	/**
743
-	 * get a list of all apps in the apps folder
744
-	 *
745
-	 * @return string[] an array of app names (string IDs)
746
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
747
-	 */
748
-	public static function getAllApps(): array {
749
-		$apps = [];
750
-
751
-		foreach (OC::$APPSROOTS as $apps_dir) {
752
-			if (!is_readable($apps_dir['path'])) {
753
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
754
-				continue;
755
-			}
756
-			$dh = opendir($apps_dir['path']);
757
-
758
-			if (is_resource($dh)) {
759
-				while (($file = readdir($dh)) !== false) {
760
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
761
-						$apps[] = $file;
762
-					}
763
-				}
764
-			}
765
-		}
766
-
767
-		$apps = array_unique($apps);
768
-
769
-		return $apps;
770
-	}
771
-
772
-	/**
773
-	 * List all apps, this is used in apps.php
774
-	 *
775
-	 * @return array
776
-	 */
777
-	public function listAllApps(): array {
778
-		$installedApps = OC_App::getAllApps();
779
-
780
-		$appManager = \OC::$server->getAppManager();
781
-		//we don't want to show configuration for these
782
-		$blacklist = $appManager->getAlwaysEnabledApps();
783
-		$appList = [];
784
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
785
-		$urlGenerator = \OC::$server->getURLGenerator();
786
-		/** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
787
-		$subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
788
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
789
-
790
-		foreach ($installedApps as $app) {
791
-			if (array_search($app, $blacklist) === false) {
792
-				$info = OC_App::getAppInfo($app, false, $langCode);
793
-				if (!is_array($info)) {
794
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
795
-					continue;
796
-				}
797
-
798
-				if (!isset($info['name'])) {
799
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
800
-					continue;
801
-				}
802
-
803
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
804
-				$info['groups'] = null;
805
-				if ($enabled === 'yes') {
806
-					$active = true;
807
-				} elseif ($enabled === 'no') {
808
-					$active = false;
809
-				} else {
810
-					$active = true;
811
-					$info['groups'] = $enabled;
812
-				}
813
-
814
-				$info['active'] = $active;
815
-
816
-				if ($appManager->isShipped($app)) {
817
-					$info['internal'] = true;
818
-					$info['level'] = self::officialApp;
819
-					$info['removable'] = false;
820
-				} else {
821
-					$info['internal'] = false;
822
-					$info['removable'] = true;
823
-				}
824
-
825
-				if (in_array($app, $supportedApps)) {
826
-					$info['level'] = self::supportedApp;
827
-				}
828
-
829
-				$appPath = self::getAppPath($app);
830
-				if ($appPath !== false) {
831
-					$appIcon = $appPath . '/img/' . $app . '.svg';
832
-					if (file_exists($appIcon)) {
833
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
834
-						$info['previewAsIcon'] = true;
835
-					} else {
836
-						$appIcon = $appPath . '/img/app.svg';
837
-						if (file_exists($appIcon)) {
838
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
839
-							$info['previewAsIcon'] = true;
840
-						}
841
-					}
842
-				}
843
-				// fix documentation
844
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
845
-					foreach ($info['documentation'] as $key => $url) {
846
-						// If it is not an absolute URL we assume it is a key
847
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
848
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
849
-							$url = $urlGenerator->linkToDocs($url);
850
-						}
851
-
852
-						$info['documentation'][$key] = $url;
853
-					}
854
-				}
855
-
856
-				$info['version'] = OC_App::getAppVersion($app);
857
-				$appList[] = $info;
858
-			}
859
-		}
860
-
861
-		return $appList;
862
-	}
863
-
864
-	public static function shouldUpgrade(string $app): bool {
865
-		$versions = self::getAppVersions();
866
-		$currentVersion = OC_App::getAppVersion($app);
867
-		if ($currentVersion && isset($versions[$app])) {
868
-			$installedVersion = $versions[$app];
869
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
870
-				return true;
871
-			}
872
-		}
873
-		return false;
874
-	}
875
-
876
-	/**
877
-	 * Adjust the number of version parts of $version1 to match
878
-	 * the number of version parts of $version2.
879
-	 *
880
-	 * @param string $version1 version to adjust
881
-	 * @param string $version2 version to take the number of parts from
882
-	 * @return string shortened $version1
883
-	 */
884
-	private static function adjustVersionParts(string $version1, string $version2): string {
885
-		$version1 = explode('.', $version1);
886
-		$version2 = explode('.', $version2);
887
-		// reduce $version1 to match the number of parts in $version2
888
-		while (count($version1) > count($version2)) {
889
-			array_pop($version1);
890
-		}
891
-		// if $version1 does not have enough parts, add some
892
-		while (count($version1) < count($version2)) {
893
-			$version1[] = '0';
894
-		}
895
-		return implode('.', $version1);
896
-	}
897
-
898
-	/**
899
-	 * Check whether the current ownCloud version matches the given
900
-	 * application's version requirements.
901
-	 *
902
-	 * The comparison is made based on the number of parts that the
903
-	 * app info version has. For example for ownCloud 6.0.3 if the
904
-	 * app info version is expecting version 6.0, the comparison is
905
-	 * made on the first two parts of the ownCloud version.
906
-	 * This means that it's possible to specify "requiremin" => 6
907
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
908
-	 *
909
-	 * @param string $ocVersion ownCloud version to check against
910
-	 * @param array $appInfo app info (from xml)
911
-	 *
912
-	 * @return boolean true if compatible, otherwise false
913
-	 */
914
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
915
-		$requireMin = '';
916
-		$requireMax = '';
917
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
918
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
919
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
920
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
921
-		} elseif (isset($appInfo['requiremin'])) {
922
-			$requireMin = $appInfo['requiremin'];
923
-		} elseif (isset($appInfo['require'])) {
924
-			$requireMin = $appInfo['require'];
925
-		}
926
-
927
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
928
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
929
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
930
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
931
-		} elseif (isset($appInfo['requiremax'])) {
932
-			$requireMax = $appInfo['requiremax'];
933
-		}
934
-
935
-		if (!empty($requireMin)
936
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
937
-		) {
938
-			return false;
939
-		}
940
-
941
-		if (!$ignoreMax && !empty($requireMax)
942
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
943
-		) {
944
-			return false;
945
-		}
946
-
947
-		return true;
948
-	}
949
-
950
-	/**
951
-	 * get the installed version of all apps
952
-	 */
953
-	public static function getAppVersions() {
954
-		static $versions;
955
-
956
-		if (!$versions) {
957
-			$appConfig = \OC::$server->getAppConfig();
958
-			$versions = $appConfig->getValues(false, 'installed_version');
959
-		}
960
-		return $versions;
961
-	}
962
-
963
-	/**
964
-	 * update the database for the app and call the update script
965
-	 *
966
-	 * @param string $appId
967
-	 * @return bool
968
-	 */
969
-	public static function updateApp(string $appId): bool {
970
-		$appPath = self::getAppPath($appId);
971
-		if ($appPath === false) {
972
-			return false;
973
-		}
974
-
975
-		if (is_file($appPath . '/appinfo/database.xml')) {
976
-			\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
977
-			return false;
978
-		}
979
-
980
-		\OC::$server->getAppManager()->clearAppsCache();
981
-		$appData = self::getAppInfo($appId);
982
-
983
-		$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
984
-		$ignoreMax = in_array($appId, $ignoreMaxApps, true);
985
-		\OC_App::checkAppDependencies(
986
-			\OC::$server->getConfig(),
987
-			\OC::$server->getL10N('core'),
988
-			$appData,
989
-			$ignoreMax
990
-		);
991
-
992
-		self::registerAutoloading($appId, $appPath, true);
993
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
994
-
995
-		$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
996
-		$ms->migrate();
997
-
998
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
999
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1000
-		// update appversion in app manager
1001
-		\OC::$server->getAppManager()->clearAppsCache();
1002
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
1003
-
1004
-		self::setupBackgroundJobs($appData['background-jobs']);
1005
-
1006
-		//set remote/public handlers
1007
-		if (array_key_exists('ocsid', $appData)) {
1008
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1009
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1010
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1011
-		}
1012
-		foreach ($appData['remote'] as $name => $path) {
1013
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1014
-		}
1015
-		foreach ($appData['public'] as $name => $path) {
1016
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1017
-		}
1018
-
1019
-		self::setAppTypes($appId);
1020
-
1021
-		$version = \OC_App::getAppVersion($appId);
1022
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
1023
-
1024
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1025
-			ManagerEvent::EVENT_APP_UPDATE, $appId
1026
-		));
1027
-
1028
-		return true;
1029
-	}
1030
-
1031
-	/**
1032
-	 * @param string $appId
1033
-	 * @param string[] $steps
1034
-	 * @throws \OC\NeedsUpdateException
1035
-	 */
1036
-	public static function executeRepairSteps(string $appId, array $steps) {
1037
-		if (empty($steps)) {
1038
-			return;
1039
-		}
1040
-		// load the app
1041
-		self::loadApp($appId);
1042
-
1043
-		$dispatcher = OC::$server->getEventDispatcher();
1044
-
1045
-		// load the steps
1046
-		$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
1047
-		foreach ($steps as $step) {
1048
-			try {
1049
-				$r->addStep($step);
1050
-			} catch (Exception $ex) {
1051
-				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1052
-				\OC::$server->getLogger()->logException($ex);
1053
-			}
1054
-		}
1055
-		// run the steps
1056
-		$r->run();
1057
-	}
1058
-
1059
-	public static function setupBackgroundJobs(array $jobs) {
1060
-		$queue = \OC::$server->getJobList();
1061
-		foreach ($jobs as $job) {
1062
-			$queue->add($job);
1063
-		}
1064
-	}
1065
-
1066
-	/**
1067
-	 * @param string $appId
1068
-	 * @param string[] $steps
1069
-	 */
1070
-	private static function setupLiveMigrations(string $appId, array $steps) {
1071
-		$queue = \OC::$server->getJobList();
1072
-		foreach ($steps as $step) {
1073
-			$queue->add('OC\Migration\BackgroundRepair', [
1074
-				'app' => $appId,
1075
-				'step' => $step]);
1076
-		}
1077
-	}
1078
-
1079
-	/**
1080
-	 * @param string $appId
1081
-	 * @return \OC\Files\View|false
1082
-	 */
1083
-	public static function getStorage(string $appId) {
1084
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1085
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
1086
-				$view = new \OC\Files\View('/' . OC_User::getUser());
1087
-				if (!$view->file_exists($appId)) {
1088
-					$view->mkdir($appId);
1089
-				}
1090
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1091
-			} else {
1092
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1093
-				return false;
1094
-			}
1095
-		} else {
1096
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1097
-			return false;
1098
-		}
1099
-	}
1100
-
1101
-	protected static function findBestL10NOption(array $options, string $lang): string {
1102
-		// only a single option
1103
-		if (isset($options['@value'])) {
1104
-			return $options['@value'];
1105
-		}
1106
-
1107
-		$fallback = $similarLangFallback = $englishFallback = false;
1108
-
1109
-		$lang = strtolower($lang);
1110
-		$similarLang = $lang;
1111
-		if (strpos($similarLang, '_')) {
1112
-			// For "de_DE" we want to find "de" and the other way around
1113
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
1114
-		}
1115
-
1116
-		foreach ($options as $option) {
1117
-			if (is_array($option)) {
1118
-				if ($fallback === false) {
1119
-					$fallback = $option['@value'];
1120
-				}
1121
-
1122
-				if (!isset($option['@attributes']['lang'])) {
1123
-					continue;
1124
-				}
1125
-
1126
-				$attributeLang = strtolower($option['@attributes']['lang']);
1127
-				if ($attributeLang === $lang) {
1128
-					return $option['@value'];
1129
-				}
1130
-
1131
-				if ($attributeLang === $similarLang) {
1132
-					$similarLangFallback = $option['@value'];
1133
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1134
-					if ($similarLangFallback === false) {
1135
-						$similarLangFallback = $option['@value'];
1136
-					}
1137
-				}
1138
-			} else {
1139
-				$englishFallback = $option;
1140
-			}
1141
-		}
1142
-
1143
-		if ($similarLangFallback !== false) {
1144
-			return $similarLangFallback;
1145
-		} elseif ($englishFallback !== false) {
1146
-			return $englishFallback;
1147
-		}
1148
-		return (string) $fallback;
1149
-	}
1150
-
1151
-	/**
1152
-	 * parses the app data array and enhanced the 'description' value
1153
-	 *
1154
-	 * @param array $data the app data
1155
-	 * @param string $lang
1156
-	 * @return array improved app data
1157
-	 */
1158
-	public static function parseAppInfo(array $data, $lang = null): array {
1159
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1160
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1161
-		}
1162
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1163
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1164
-		}
1165
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1166
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1167
-		} elseif (isset($data['description']) && is_string($data['description'])) {
1168
-			$data['description'] = trim($data['description']);
1169
-		} else {
1170
-			$data['description'] = '';
1171
-		}
1172
-
1173
-		return $data;
1174
-	}
1175
-
1176
-	/**
1177
-	 * @param \OCP\IConfig $config
1178
-	 * @param \OCP\IL10N $l
1179
-	 * @param array $info
1180
-	 * @throws \Exception
1181
-	 */
1182
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1183
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1184
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1185
-		if (!empty($missing)) {
1186
-			$missingMsg = implode(PHP_EOL, $missing);
1187
-			throw new \Exception(
1188
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1189
-					[$info['name'], $missingMsg]
1190
-				)
1191
-			);
1192
-		}
1193
-	}
72
+    private static $adminForms = [];
73
+    private static $personalForms = [];
74
+    private static $appTypes = [];
75
+    private static $loadedApps = [];
76
+    private static $altLogin = [];
77
+    private static $alreadyRegistered = [];
78
+    public const supportedApp = 300;
79
+    public const officialApp = 200;
80
+
81
+    /**
82
+     * clean the appId
83
+     *
84
+     * @psalm-taint-escape file
85
+     * @psalm-taint-escape include
86
+     *
87
+     * @param string $app AppId that needs to be cleaned
88
+     * @return string
89
+     */
90
+    public static function cleanAppId(string $app): string {
91
+        return str_replace(['\0', '/', '\\', '..'], '', $app);
92
+    }
93
+
94
+    /**
95
+     * Check if an app is loaded
96
+     *
97
+     * @param string $app
98
+     * @return bool
99
+     */
100
+    public static function isAppLoaded(string $app): bool {
101
+        return isset(self::$loadedApps[$app]);
102
+    }
103
+
104
+    /**
105
+     * loads all apps
106
+     *
107
+     * @param string[] $types
108
+     * @return bool
109
+     *
110
+     * This function walks through the ownCloud directory and loads all apps
111
+     * it can find. A directory contains an app if the file /appinfo/info.xml
112
+     * exists.
113
+     *
114
+     * if $types is set to non-empty array, only apps of those types will be loaded
115
+     */
116
+    public static function loadApps(array $types = []): bool {
117
+        if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
118
+            return false;
119
+        }
120
+        // Load the enabled apps here
121
+        $apps = self::getEnabledApps();
122
+
123
+        // Add each apps' folder as allowed class path
124
+        foreach ($apps as $app) {
125
+            // If the app is already loaded then autoloading it makes no sense
126
+            if (!isset(self::$loadedApps[$app])) {
127
+                $path = self::getAppPath($app);
128
+                if ($path !== false) {
129
+                    self::registerAutoloading($app, $path);
130
+                }
131
+            }
132
+        }
133
+
134
+        // prevent app.php from printing output
135
+        ob_start();
136
+        foreach ($apps as $app) {
137
+            if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) {
138
+                self::loadApp($app);
139
+            }
140
+        }
141
+        ob_end_clean();
142
+
143
+        return true;
144
+    }
145
+
146
+    /**
147
+     * load a single app
148
+     *
149
+     * @param string $app
150
+     * @throws Exception
151
+     */
152
+    public static function loadApp(string $app) {
153
+        self::$loadedApps[$app] = true;
154
+        $appPath = self::getAppPath($app);
155
+        if ($appPath === false) {
156
+            return;
157
+        }
158
+
159
+        // in case someone calls loadApp() directly
160
+        self::registerAutoloading($app, $appPath);
161
+
162
+        /** @var Coordinator $coordinator */
163
+        $coordinator = \OC::$server->query(Coordinator::class);
164
+        $isBootable = $coordinator->isBootable($app);
165
+
166
+        $hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
167
+
168
+        if ($isBootable && $hasAppPhpFile) {
169
+            \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.', [
170
+                'app' => $app,
171
+            ]);
172
+        } elseif ($hasAppPhpFile) {
173
+            \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
174
+                'app' => $app,
175
+            ]);
176
+            \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
177
+            try {
178
+                self::requireAppFile($app);
179
+            } catch (Throwable $ex) {
180
+                if ($ex instanceof ServerNotAvailableException) {
181
+                    throw $ex;
182
+                }
183
+                if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
184
+                    \OC::$server->getLogger()->logException($ex, [
185
+                        'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
186
+                    ]);
187
+
188
+                    // Only disable apps which are not shipped and that are not authentication apps
189
+                    \OC::$server->getAppManager()->disableApp($app, true);
190
+                } else {
191
+                    \OC::$server->getLogger()->logException($ex, [
192
+                        'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
193
+                    ]);
194
+                }
195
+            }
196
+            \OC::$server->getEventLogger()->end('load_app_' . $app);
197
+        }
198
+        $coordinator->bootApp($app);
199
+
200
+        $info = self::getAppInfo($app);
201
+        if (!empty($info['activity']['filters'])) {
202
+            foreach ($info['activity']['filters'] as $filter) {
203
+                \OC::$server->getActivityManager()->registerFilter($filter);
204
+            }
205
+        }
206
+        if (!empty($info['activity']['settings'])) {
207
+            foreach ($info['activity']['settings'] as $setting) {
208
+                \OC::$server->getActivityManager()->registerSetting($setting);
209
+            }
210
+        }
211
+        if (!empty($info['activity']['providers'])) {
212
+            foreach ($info['activity']['providers'] as $provider) {
213
+                \OC::$server->getActivityManager()->registerProvider($provider);
214
+            }
215
+        }
216
+
217
+        if (!empty($info['settings']['admin'])) {
218
+            foreach ($info['settings']['admin'] as $setting) {
219
+                \OC::$server->getSettingsManager()->registerSetting('admin', $setting);
220
+            }
221
+        }
222
+        if (!empty($info['settings']['admin-section'])) {
223
+            foreach ($info['settings']['admin-section'] as $section) {
224
+                \OC::$server->getSettingsManager()->registerSection('admin', $section);
225
+            }
226
+        }
227
+        if (!empty($info['settings']['personal'])) {
228
+            foreach ($info['settings']['personal'] as $setting) {
229
+                \OC::$server->getSettingsManager()->registerSetting('personal', $setting);
230
+            }
231
+        }
232
+        if (!empty($info['settings']['personal-section'])) {
233
+            foreach ($info['settings']['personal-section'] as $section) {
234
+                \OC::$server->getSettingsManager()->registerSection('personal', $section);
235
+            }
236
+        }
237
+
238
+        if (!empty($info['collaboration']['plugins'])) {
239
+            // deal with one or many plugin entries
240
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
241
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
242
+            foreach ($plugins as $plugin) {
243
+                if ($plugin['@attributes']['type'] === 'collaborator-search') {
244
+                    $pluginInfo = [
245
+                        'shareType' => $plugin['@attributes']['share-type'],
246
+                        'class' => $plugin['@value'],
247
+                    ];
248
+                    \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
249
+                } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
250
+                    \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
251
+                }
252
+            }
253
+        }
254
+    }
255
+
256
+    /**
257
+     * @internal
258
+     * @param string $app
259
+     * @param string $path
260
+     * @param bool $force
261
+     */
262
+    public static function registerAutoloading(string $app, string $path, bool $force = false) {
263
+        $key = $app . '-' . $path;
264
+        if (!$force && isset(self::$alreadyRegistered[$key])) {
265
+            return;
266
+        }
267
+
268
+        self::$alreadyRegistered[$key] = true;
269
+
270
+        // Register on PSR-4 composer autoloader
271
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
272
+        \OC::$server->registerNamespace($app, $appNamespace);
273
+
274
+        if (file_exists($path . '/composer/autoload.php')) {
275
+            require_once $path . '/composer/autoload.php';
276
+        } else {
277
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
278
+            // Register on legacy autoloader
279
+            \OC::$loader->addValidRoot($path);
280
+        }
281
+
282
+        // Register Test namespace only when testing
283
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
284
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
285
+        }
286
+    }
287
+
288
+    /**
289
+     * Load app.php from the given app
290
+     *
291
+     * @param string $app app name
292
+     * @throws Error
293
+     */
294
+    private static function requireAppFile(string $app) {
295
+        // encapsulated here to avoid variable scope conflicts
296
+        require_once $app . '/appinfo/app.php';
297
+    }
298
+
299
+    /**
300
+     * check if an app is of a specific type
301
+     *
302
+     * @param string $app
303
+     * @param array $types
304
+     * @return bool
305
+     */
306
+    public static function isType(string $app, array $types): bool {
307
+        $appTypes = self::getAppTypes($app);
308
+        foreach ($types as $type) {
309
+            if (array_search($type, $appTypes) !== false) {
310
+                return true;
311
+            }
312
+        }
313
+        return false;
314
+    }
315
+
316
+    /**
317
+     * get the types of an app
318
+     *
319
+     * @param string $app
320
+     * @return array
321
+     */
322
+    private static function getAppTypes(string $app): array {
323
+        //load the cache
324
+        if (count(self::$appTypes) == 0) {
325
+            self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
326
+        }
327
+
328
+        if (isset(self::$appTypes[$app])) {
329
+            return explode(',', self::$appTypes[$app]);
330
+        }
331
+
332
+        return [];
333
+    }
334
+
335
+    /**
336
+     * read app types from info.xml and cache them in the database
337
+     */
338
+    public static function setAppTypes(string $app) {
339
+        $appManager = \OC::$server->getAppManager();
340
+        $appData = $appManager->getAppInfo($app);
341
+        if (!is_array($appData)) {
342
+            return;
343
+        }
344
+
345
+        if (isset($appData['types'])) {
346
+            $appTypes = implode(',', $appData['types']);
347
+        } else {
348
+            $appTypes = '';
349
+            $appData['types'] = [];
350
+        }
351
+
352
+        $config = \OC::$server->getConfig();
353
+        $config->setAppValue($app, 'types', $appTypes);
354
+
355
+        if ($appManager->hasProtectedAppType($appData['types'])) {
356
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
357
+            if ($enabled !== 'yes' && $enabled !== 'no') {
358
+                $config->setAppValue($app, 'enabled', 'yes');
359
+            }
360
+        }
361
+    }
362
+
363
+    /**
364
+     * Returns apps enabled for the current user.
365
+     *
366
+     * @param bool $forceRefresh whether to refresh the cache
367
+     * @param bool $all whether to return apps for all users, not only the
368
+     * currently logged in one
369
+     * @return string[]
370
+     */
371
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
372
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
373
+            return [];
374
+        }
375
+        // in incognito mode or when logged out, $user will be false,
376
+        // which is also the case during an upgrade
377
+        $appManager = \OC::$server->getAppManager();
378
+        if ($all) {
379
+            $user = null;
380
+        } else {
381
+            $user = \OC::$server->getUserSession()->getUser();
382
+        }
383
+
384
+        if (is_null($user)) {
385
+            $apps = $appManager->getInstalledApps();
386
+        } else {
387
+            $apps = $appManager->getEnabledAppsForUser($user);
388
+        }
389
+        $apps = array_filter($apps, function ($app) {
390
+            return $app !== 'files';//we add this manually
391
+        });
392
+        sort($apps);
393
+        array_unshift($apps, 'files');
394
+        return $apps;
395
+    }
396
+
397
+    /**
398
+     * checks whether or not an app is enabled
399
+     *
400
+     * @param string $app app
401
+     * @return bool
402
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
403
+     *
404
+     * This function checks whether or not an app is enabled.
405
+     */
406
+    public static function isEnabled(string $app): bool {
407
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
408
+    }
409
+
410
+    /**
411
+     * enables an app
412
+     *
413
+     * @param string $appId
414
+     * @param array $groups (optional) when set, only these groups will have access to the app
415
+     * @throws \Exception
416
+     * @return void
417
+     *
418
+     * This function set an app as enabled in appconfig.
419
+     */
420
+    public function enable(string $appId,
421
+                            array $groups = []) {
422
+
423
+        // Check if app is already downloaded
424
+        /** @var Installer $installer */
425
+        $installer = \OC::$server->query(Installer::class);
426
+        $isDownloaded = $installer->isDownloaded($appId);
427
+
428
+        if (!$isDownloaded) {
429
+            $installer->downloadApp($appId);
430
+        }
431
+
432
+        $installer->installApp($appId);
433
+
434
+        $appManager = \OC::$server->getAppManager();
435
+        if ($groups !== []) {
436
+            $groupManager = \OC::$server->getGroupManager();
437
+            $groupsList = [];
438
+            foreach ($groups as $group) {
439
+                $groupItem = $groupManager->get($group);
440
+                if ($groupItem instanceof \OCP\IGroup) {
441
+                    $groupsList[] = $groupManager->get($group);
442
+                }
443
+            }
444
+            $appManager->enableAppForGroups($appId, $groupsList);
445
+        } else {
446
+            $appManager->enableApp($appId);
447
+        }
448
+    }
449
+
450
+    /**
451
+     * Get the path where to install apps
452
+     *
453
+     * @return string|false
454
+     */
455
+    public static function getInstallPath() {
456
+        if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
457
+            return false;
458
+        }
459
+
460
+        foreach (OC::$APPSROOTS as $dir) {
461
+            if (isset($dir['writable']) && $dir['writable'] === true) {
462
+                return $dir['path'];
463
+            }
464
+        }
465
+
466
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
467
+        return null;
468
+    }
469
+
470
+
471
+    /**
472
+     * search for an app in all app-directories
473
+     *
474
+     * @param string $appId
475
+     * @return false|string
476
+     */
477
+    public static function findAppInDirectories(string $appId) {
478
+        $sanitizedAppId = self::cleanAppId($appId);
479
+        if ($sanitizedAppId !== $appId) {
480
+            return false;
481
+        }
482
+        static $app_dir = [];
483
+
484
+        if (isset($app_dir[$appId])) {
485
+            return $app_dir[$appId];
486
+        }
487
+
488
+        $possibleApps = [];
489
+        foreach (OC::$APPSROOTS as $dir) {
490
+            if (file_exists($dir['path'] . '/' . $appId)) {
491
+                $possibleApps[] = $dir;
492
+            }
493
+        }
494
+
495
+        if (empty($possibleApps)) {
496
+            return false;
497
+        } elseif (count($possibleApps) === 1) {
498
+            $dir = array_shift($possibleApps);
499
+            $app_dir[$appId] = $dir;
500
+            return $dir;
501
+        } else {
502
+            $versionToLoad = [];
503
+            foreach ($possibleApps as $possibleApp) {
504
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
505
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
506
+                    $versionToLoad = [
507
+                        'dir' => $possibleApp,
508
+                        'version' => $version,
509
+                    ];
510
+                }
511
+            }
512
+            $app_dir[$appId] = $versionToLoad['dir'];
513
+            return $versionToLoad['dir'];
514
+            //TODO - write test
515
+        }
516
+    }
517
+
518
+    /**
519
+     * Get the directory for the given app.
520
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
521
+     *
522
+     * @psalm-taint-specialize
523
+     *
524
+     * @param string $appId
525
+     * @return string|false
526
+     * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
527
+     */
528
+    public static function getAppPath(string $appId) {
529
+        if ($appId === null || trim($appId) === '') {
530
+            return false;
531
+        }
532
+
533
+        if (($dir = self::findAppInDirectories($appId)) != false) {
534
+            return $dir['path'] . '/' . $appId;
535
+        }
536
+        return false;
537
+    }
538
+
539
+    /**
540
+     * Get the path for the given app on the access
541
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
542
+     *
543
+     * @param string $appId
544
+     * @return string|false
545
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
546
+     */
547
+    public static function getAppWebPath(string $appId) {
548
+        if (($dir = self::findAppInDirectories($appId)) != false) {
549
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
550
+        }
551
+        return false;
552
+    }
553
+
554
+    /**
555
+     * get the last version of the app from appinfo/info.xml
556
+     *
557
+     * @param string $appId
558
+     * @param bool $useCache
559
+     * @return string
560
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
561
+     */
562
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
563
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
564
+    }
565
+
566
+    /**
567
+     * get app's version based on it's path
568
+     *
569
+     * @param string $path
570
+     * @return string
571
+     */
572
+    public static function getAppVersionByPath(string $path): string {
573
+        $infoFile = $path . '/appinfo/info.xml';
574
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
575
+        return isset($appData['version']) ? $appData['version'] : '';
576
+    }
577
+
578
+
579
+    /**
580
+     * Read all app metadata from the info.xml file
581
+     *
582
+     * @param string $appId id of the app or the path of the info.xml file
583
+     * @param bool $path
584
+     * @param string $lang
585
+     * @return array|null
586
+     * @note all data is read from info.xml, not just pre-defined fields
587
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
588
+     */
589
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
590
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
591
+    }
592
+
593
+    /**
594
+     * Returns the navigation
595
+     *
596
+     * @return array
597
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
598
+     *
599
+     * This function returns an array containing all entries added. The
600
+     * entries are sorted by the key 'order' ascending. Additional to the keys
601
+     * given for each app the following keys exist:
602
+     *   - active: boolean, signals if the user is on this navigation entry
603
+     */
604
+    public static function getNavigation(): array {
605
+        return OC::$server->getNavigationManager()->getAll();
606
+    }
607
+
608
+    /**
609
+     * Returns the Settings Navigation
610
+     *
611
+     * @return string[]
612
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
613
+     *
614
+     * This function returns an array containing all settings pages added. The
615
+     * entries are sorted by the key 'order' ascending.
616
+     */
617
+    public static function getSettingsNavigation(): array {
618
+        return OC::$server->getNavigationManager()->getAll('settings');
619
+    }
620
+
621
+    /**
622
+     * get the id of loaded app
623
+     *
624
+     * @return string
625
+     */
626
+    public static function getCurrentApp(): string {
627
+        $request = \OC::$server->getRequest();
628
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
629
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
630
+        if (empty($topFolder)) {
631
+            $path_info = $request->getPathInfo();
632
+            if ($path_info) {
633
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
634
+            }
635
+        }
636
+        if ($topFolder == 'apps') {
637
+            $length = strlen($topFolder);
638
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
639
+        } else {
640
+            return $topFolder;
641
+        }
642
+    }
643
+
644
+    /**
645
+     * @param string $type
646
+     * @return array
647
+     */
648
+    public static function getForms(string $type): array {
649
+        $forms = [];
650
+        switch ($type) {
651
+            case 'admin':
652
+                $source = self::$adminForms;
653
+                break;
654
+            case 'personal':
655
+                $source = self::$personalForms;
656
+                break;
657
+            default:
658
+                return [];
659
+        }
660
+        foreach ($source as $form) {
661
+            $forms[] = include $form;
662
+        }
663
+        return $forms;
664
+    }
665
+
666
+    /**
667
+     * register an admin form to be shown
668
+     *
669
+     * @param string $app
670
+     * @param string $page
671
+     */
672
+    public static function registerAdmin(string $app, string $page) {
673
+        self::$adminForms[] = $app . '/' . $page . '.php';
674
+    }
675
+
676
+    /**
677
+     * register a personal form to be shown
678
+     * @param string $app
679
+     * @param string $page
680
+     */
681
+    public static function registerPersonal(string $app, string $page) {
682
+        self::$personalForms[] = $app . '/' . $page . '.php';
683
+    }
684
+
685
+    /**
686
+     * @param array $entry
687
+     * @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
688
+     */
689
+    public static function registerLogIn(array $entry) {
690
+        \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');
691
+        self::$altLogin[] = $entry;
692
+    }
693
+
694
+    /**
695
+     * @return array
696
+     */
697
+    public static function getAlternativeLogIns(): array {
698
+        /** @var Coordinator $bootstrapCoordinator */
699
+        $bootstrapCoordinator = \OC::$server->query(Coordinator::class);
700
+
701
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
702
+            if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
703
+                \OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
704
+                    'option' => $registration->getService(),
705
+                    'interface' => IAlternativeLogin::class,
706
+                    'app' => $registration->getAppId(),
707
+                ]);
708
+                continue;
709
+            }
710
+
711
+            try {
712
+                /** @var IAlternativeLogin $provider */
713
+                $provider = \OC::$server->query($registration->getService());
714
+            } catch (QueryException $e) {
715
+                \OC::$server->getLogger()->logException($e, [
716
+                    'message' => 'Alternative login option {option} can not be initialised.',
717
+                    'option' => $registration->getService(),
718
+                    'app' => $registration->getAppId(),
719
+                ]);
720
+            }
721
+
722
+            try {
723
+                $provider->load();
724
+
725
+                self::$altLogin[] = [
726
+                    'name' => $provider->getLabel(),
727
+                    'href' => $provider->getLink(),
728
+                    'style' => $provider->getClass(),
729
+                ];
730
+            } catch (Throwable $e) {
731
+                \OC::$server->getLogger()->logException($e, [
732
+                    'message' => 'Alternative login option {option} had an error while loading.',
733
+                    'option' => $registration->getService(),
734
+                    'app' => $registration->getAppId(),
735
+                ]);
736
+            }
737
+        }
738
+
739
+        return self::$altLogin;
740
+    }
741
+
742
+    /**
743
+     * get a list of all apps in the apps folder
744
+     *
745
+     * @return string[] an array of app names (string IDs)
746
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
747
+     */
748
+    public static function getAllApps(): array {
749
+        $apps = [];
750
+
751
+        foreach (OC::$APPSROOTS as $apps_dir) {
752
+            if (!is_readable($apps_dir['path'])) {
753
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
754
+                continue;
755
+            }
756
+            $dh = opendir($apps_dir['path']);
757
+
758
+            if (is_resource($dh)) {
759
+                while (($file = readdir($dh)) !== false) {
760
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
761
+                        $apps[] = $file;
762
+                    }
763
+                }
764
+            }
765
+        }
766
+
767
+        $apps = array_unique($apps);
768
+
769
+        return $apps;
770
+    }
771
+
772
+    /**
773
+     * List all apps, this is used in apps.php
774
+     *
775
+     * @return array
776
+     */
777
+    public function listAllApps(): array {
778
+        $installedApps = OC_App::getAllApps();
779
+
780
+        $appManager = \OC::$server->getAppManager();
781
+        //we don't want to show configuration for these
782
+        $blacklist = $appManager->getAlwaysEnabledApps();
783
+        $appList = [];
784
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
785
+        $urlGenerator = \OC::$server->getURLGenerator();
786
+        /** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
787
+        $subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
788
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
789
+
790
+        foreach ($installedApps as $app) {
791
+            if (array_search($app, $blacklist) === false) {
792
+                $info = OC_App::getAppInfo($app, false, $langCode);
793
+                if (!is_array($info)) {
794
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
795
+                    continue;
796
+                }
797
+
798
+                if (!isset($info['name'])) {
799
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
800
+                    continue;
801
+                }
802
+
803
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
804
+                $info['groups'] = null;
805
+                if ($enabled === 'yes') {
806
+                    $active = true;
807
+                } elseif ($enabled === 'no') {
808
+                    $active = false;
809
+                } else {
810
+                    $active = true;
811
+                    $info['groups'] = $enabled;
812
+                }
813
+
814
+                $info['active'] = $active;
815
+
816
+                if ($appManager->isShipped($app)) {
817
+                    $info['internal'] = true;
818
+                    $info['level'] = self::officialApp;
819
+                    $info['removable'] = false;
820
+                } else {
821
+                    $info['internal'] = false;
822
+                    $info['removable'] = true;
823
+                }
824
+
825
+                if (in_array($app, $supportedApps)) {
826
+                    $info['level'] = self::supportedApp;
827
+                }
828
+
829
+                $appPath = self::getAppPath($app);
830
+                if ($appPath !== false) {
831
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
832
+                    if (file_exists($appIcon)) {
833
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
834
+                        $info['previewAsIcon'] = true;
835
+                    } else {
836
+                        $appIcon = $appPath . '/img/app.svg';
837
+                        if (file_exists($appIcon)) {
838
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
839
+                            $info['previewAsIcon'] = true;
840
+                        }
841
+                    }
842
+                }
843
+                // fix documentation
844
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
845
+                    foreach ($info['documentation'] as $key => $url) {
846
+                        // If it is not an absolute URL we assume it is a key
847
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
848
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
849
+                            $url = $urlGenerator->linkToDocs($url);
850
+                        }
851
+
852
+                        $info['documentation'][$key] = $url;
853
+                    }
854
+                }
855
+
856
+                $info['version'] = OC_App::getAppVersion($app);
857
+                $appList[] = $info;
858
+            }
859
+        }
860
+
861
+        return $appList;
862
+    }
863
+
864
+    public static function shouldUpgrade(string $app): bool {
865
+        $versions = self::getAppVersions();
866
+        $currentVersion = OC_App::getAppVersion($app);
867
+        if ($currentVersion && isset($versions[$app])) {
868
+            $installedVersion = $versions[$app];
869
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
870
+                return true;
871
+            }
872
+        }
873
+        return false;
874
+    }
875
+
876
+    /**
877
+     * Adjust the number of version parts of $version1 to match
878
+     * the number of version parts of $version2.
879
+     *
880
+     * @param string $version1 version to adjust
881
+     * @param string $version2 version to take the number of parts from
882
+     * @return string shortened $version1
883
+     */
884
+    private static function adjustVersionParts(string $version1, string $version2): string {
885
+        $version1 = explode('.', $version1);
886
+        $version2 = explode('.', $version2);
887
+        // reduce $version1 to match the number of parts in $version2
888
+        while (count($version1) > count($version2)) {
889
+            array_pop($version1);
890
+        }
891
+        // if $version1 does not have enough parts, add some
892
+        while (count($version1) < count($version2)) {
893
+            $version1[] = '0';
894
+        }
895
+        return implode('.', $version1);
896
+    }
897
+
898
+    /**
899
+     * Check whether the current ownCloud version matches the given
900
+     * application's version requirements.
901
+     *
902
+     * The comparison is made based on the number of parts that the
903
+     * app info version has. For example for ownCloud 6.0.3 if the
904
+     * app info version is expecting version 6.0, the comparison is
905
+     * made on the first two parts of the ownCloud version.
906
+     * This means that it's possible to specify "requiremin" => 6
907
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
908
+     *
909
+     * @param string $ocVersion ownCloud version to check against
910
+     * @param array $appInfo app info (from xml)
911
+     *
912
+     * @return boolean true if compatible, otherwise false
913
+     */
914
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
915
+        $requireMin = '';
916
+        $requireMax = '';
917
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
918
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
919
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
920
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
921
+        } elseif (isset($appInfo['requiremin'])) {
922
+            $requireMin = $appInfo['requiremin'];
923
+        } elseif (isset($appInfo['require'])) {
924
+            $requireMin = $appInfo['require'];
925
+        }
926
+
927
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
928
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
929
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
930
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
931
+        } elseif (isset($appInfo['requiremax'])) {
932
+            $requireMax = $appInfo['requiremax'];
933
+        }
934
+
935
+        if (!empty($requireMin)
936
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
937
+        ) {
938
+            return false;
939
+        }
940
+
941
+        if (!$ignoreMax && !empty($requireMax)
942
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
943
+        ) {
944
+            return false;
945
+        }
946
+
947
+        return true;
948
+    }
949
+
950
+    /**
951
+     * get the installed version of all apps
952
+     */
953
+    public static function getAppVersions() {
954
+        static $versions;
955
+
956
+        if (!$versions) {
957
+            $appConfig = \OC::$server->getAppConfig();
958
+            $versions = $appConfig->getValues(false, 'installed_version');
959
+        }
960
+        return $versions;
961
+    }
962
+
963
+    /**
964
+     * update the database for the app and call the update script
965
+     *
966
+     * @param string $appId
967
+     * @return bool
968
+     */
969
+    public static function updateApp(string $appId): bool {
970
+        $appPath = self::getAppPath($appId);
971
+        if ($appPath === false) {
972
+            return false;
973
+        }
974
+
975
+        if (is_file($appPath . '/appinfo/database.xml')) {
976
+            \OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
977
+            return false;
978
+        }
979
+
980
+        \OC::$server->getAppManager()->clearAppsCache();
981
+        $appData = self::getAppInfo($appId);
982
+
983
+        $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
984
+        $ignoreMax = in_array($appId, $ignoreMaxApps, true);
985
+        \OC_App::checkAppDependencies(
986
+            \OC::$server->getConfig(),
987
+            \OC::$server->getL10N('core'),
988
+            $appData,
989
+            $ignoreMax
990
+        );
991
+
992
+        self::registerAutoloading($appId, $appPath, true);
993
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
994
+
995
+        $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
996
+        $ms->migrate();
997
+
998
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
999
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1000
+        // update appversion in app manager
1001
+        \OC::$server->getAppManager()->clearAppsCache();
1002
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
1003
+
1004
+        self::setupBackgroundJobs($appData['background-jobs']);
1005
+
1006
+        //set remote/public handlers
1007
+        if (array_key_exists('ocsid', $appData)) {
1008
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1009
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1010
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1011
+        }
1012
+        foreach ($appData['remote'] as $name => $path) {
1013
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1014
+        }
1015
+        foreach ($appData['public'] as $name => $path) {
1016
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1017
+        }
1018
+
1019
+        self::setAppTypes($appId);
1020
+
1021
+        $version = \OC_App::getAppVersion($appId);
1022
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
1023
+
1024
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1025
+            ManagerEvent::EVENT_APP_UPDATE, $appId
1026
+        ));
1027
+
1028
+        return true;
1029
+    }
1030
+
1031
+    /**
1032
+     * @param string $appId
1033
+     * @param string[] $steps
1034
+     * @throws \OC\NeedsUpdateException
1035
+     */
1036
+    public static function executeRepairSteps(string $appId, array $steps) {
1037
+        if (empty($steps)) {
1038
+            return;
1039
+        }
1040
+        // load the app
1041
+        self::loadApp($appId);
1042
+
1043
+        $dispatcher = OC::$server->getEventDispatcher();
1044
+
1045
+        // load the steps
1046
+        $r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
1047
+        foreach ($steps as $step) {
1048
+            try {
1049
+                $r->addStep($step);
1050
+            } catch (Exception $ex) {
1051
+                $r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1052
+                \OC::$server->getLogger()->logException($ex);
1053
+            }
1054
+        }
1055
+        // run the steps
1056
+        $r->run();
1057
+    }
1058
+
1059
+    public static function setupBackgroundJobs(array $jobs) {
1060
+        $queue = \OC::$server->getJobList();
1061
+        foreach ($jobs as $job) {
1062
+            $queue->add($job);
1063
+        }
1064
+    }
1065
+
1066
+    /**
1067
+     * @param string $appId
1068
+     * @param string[] $steps
1069
+     */
1070
+    private static function setupLiveMigrations(string $appId, array $steps) {
1071
+        $queue = \OC::$server->getJobList();
1072
+        foreach ($steps as $step) {
1073
+            $queue->add('OC\Migration\BackgroundRepair', [
1074
+                'app' => $appId,
1075
+                'step' => $step]);
1076
+        }
1077
+    }
1078
+
1079
+    /**
1080
+     * @param string $appId
1081
+     * @return \OC\Files\View|false
1082
+     */
1083
+    public static function getStorage(string $appId) {
1084
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1085
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
1086
+                $view = new \OC\Files\View('/' . OC_User::getUser());
1087
+                if (!$view->file_exists($appId)) {
1088
+                    $view->mkdir($appId);
1089
+                }
1090
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1091
+            } else {
1092
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1093
+                return false;
1094
+            }
1095
+        } else {
1096
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1097
+            return false;
1098
+        }
1099
+    }
1100
+
1101
+    protected static function findBestL10NOption(array $options, string $lang): string {
1102
+        // only a single option
1103
+        if (isset($options['@value'])) {
1104
+            return $options['@value'];
1105
+        }
1106
+
1107
+        $fallback = $similarLangFallback = $englishFallback = false;
1108
+
1109
+        $lang = strtolower($lang);
1110
+        $similarLang = $lang;
1111
+        if (strpos($similarLang, '_')) {
1112
+            // For "de_DE" we want to find "de" and the other way around
1113
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
1114
+        }
1115
+
1116
+        foreach ($options as $option) {
1117
+            if (is_array($option)) {
1118
+                if ($fallback === false) {
1119
+                    $fallback = $option['@value'];
1120
+                }
1121
+
1122
+                if (!isset($option['@attributes']['lang'])) {
1123
+                    continue;
1124
+                }
1125
+
1126
+                $attributeLang = strtolower($option['@attributes']['lang']);
1127
+                if ($attributeLang === $lang) {
1128
+                    return $option['@value'];
1129
+                }
1130
+
1131
+                if ($attributeLang === $similarLang) {
1132
+                    $similarLangFallback = $option['@value'];
1133
+                } elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1134
+                    if ($similarLangFallback === false) {
1135
+                        $similarLangFallback = $option['@value'];
1136
+                    }
1137
+                }
1138
+            } else {
1139
+                $englishFallback = $option;
1140
+            }
1141
+        }
1142
+
1143
+        if ($similarLangFallback !== false) {
1144
+            return $similarLangFallback;
1145
+        } elseif ($englishFallback !== false) {
1146
+            return $englishFallback;
1147
+        }
1148
+        return (string) $fallback;
1149
+    }
1150
+
1151
+    /**
1152
+     * parses the app data array and enhanced the 'description' value
1153
+     *
1154
+     * @param array $data the app data
1155
+     * @param string $lang
1156
+     * @return array improved app data
1157
+     */
1158
+    public static function parseAppInfo(array $data, $lang = null): array {
1159
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1160
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1161
+        }
1162
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1163
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1164
+        }
1165
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1166
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1167
+        } elseif (isset($data['description']) && is_string($data['description'])) {
1168
+            $data['description'] = trim($data['description']);
1169
+        } else {
1170
+            $data['description'] = '';
1171
+        }
1172
+
1173
+        return $data;
1174
+    }
1175
+
1176
+    /**
1177
+     * @param \OCP\IConfig $config
1178
+     * @param \OCP\IL10N $l
1179
+     * @param array $info
1180
+     * @throws \Exception
1181
+     */
1182
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1183
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1184
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1185
+        if (!empty($missing)) {
1186
+            $missingMsg = implode(PHP_EOL, $missing);
1187
+            throw new \Exception(
1188
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1189
+                    [$info['name'], $missingMsg]
1190
+                )
1191
+            );
1192
+        }
1193
+    }
1194 1194
 }
Please login to merge, or discard this patch.
Spacing   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
 		$coordinator = \OC::$server->query(Coordinator::class);
164 164
 		$isBootable = $coordinator->isBootable($app);
165 165
 
166
-		$hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
166
+		$hasAppPhpFile = is_file($appPath.'/appinfo/app.php');
167 167
 
168 168
 		if ($isBootable && $hasAppPhpFile) {
169 169
 			\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.', [
@@ -173,7 +173,7 @@  discard block
 block discarded – undo
173 173
 			\OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
174 174
 				'app' => $app,
175 175
 			]);
176
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
176
+			\OC::$server->getEventLogger()->start('load_app_'.$app, 'Load app: '.$app);
177 177
 			try {
178 178
 				self::requireAppFile($app);
179 179
 			} catch (Throwable $ex) {
@@ -182,18 +182,18 @@  discard block
 block discarded – undo
182 182
 				}
183 183
 				if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
184 184
 					\OC::$server->getLogger()->logException($ex, [
185
-						'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
185
+						'message' => "App $app threw an error during app.php load and will be disabled: ".$ex->getMessage(),
186 186
 					]);
187 187
 
188 188
 					// Only disable apps which are not shipped and that are not authentication apps
189 189
 					\OC::$server->getAppManager()->disableApp($app, true);
190 190
 				} else {
191 191
 					\OC::$server->getLogger()->logException($ex, [
192
-						'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
192
+						'message' => "App $app threw an error during app.php load: ".$ex->getMessage(),
193 193
 					]);
194 194
 				}
195 195
 			}
196
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
196
+			\OC::$server->getEventLogger()->end('load_app_'.$app);
197 197
 		}
198 198
 		$coordinator->bootApp($app);
199 199
 
@@ -260,7 +260,7 @@  discard block
 block discarded – undo
260 260
 	 * @param bool $force
261 261
 	 */
262 262
 	public static function registerAutoloading(string $app, string $path, bool $force = false) {
263
-		$key = $app . '-' . $path;
263
+		$key = $app.'-'.$path;
264 264
 		if (!$force && isset(self::$alreadyRegistered[$key])) {
265 265
 			return;
266 266
 		}
@@ -271,17 +271,17 @@  discard block
 block discarded – undo
271 271
 		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
272 272
 		\OC::$server->registerNamespace($app, $appNamespace);
273 273
 
274
-		if (file_exists($path . '/composer/autoload.php')) {
275
-			require_once $path . '/composer/autoload.php';
274
+		if (file_exists($path.'/composer/autoload.php')) {
275
+			require_once $path.'/composer/autoload.php';
276 276
 		} else {
277
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
277
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\', $path.'/lib/', true);
278 278
 			// Register on legacy autoloader
279 279
 			\OC::$loader->addValidRoot($path);
280 280
 		}
281 281
 
282 282
 		// Register Test namespace only when testing
283 283
 		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
284
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
284
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\Tests\\', $path.'/tests/', true);
285 285
 		}
286 286
 	}
287 287
 
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 	 */
294 294
 	private static function requireAppFile(string $app) {
295 295
 		// encapsulated here to avoid variable scope conflicts
296
-		require_once $app . '/appinfo/app.php';
296
+		require_once $app.'/appinfo/app.php';
297 297
 	}
298 298
 
299 299
 	/**
@@ -386,8 +386,8 @@  discard block
 block discarded – undo
386 386
 		} else {
387 387
 			$apps = $appManager->getEnabledAppsForUser($user);
388 388
 		}
389
-		$apps = array_filter($apps, function ($app) {
390
-			return $app !== 'files';//we add this manually
389
+		$apps = array_filter($apps, function($app) {
390
+			return $app !== 'files'; //we add this manually
391 391
 		});
392 392
 		sort($apps);
393 393
 		array_unshift($apps, 'files');
@@ -487,7 +487,7 @@  discard block
 block discarded – undo
487 487
 
488 488
 		$possibleApps = [];
489 489
 		foreach (OC::$APPSROOTS as $dir) {
490
-			if (file_exists($dir['path'] . '/' . $appId)) {
490
+			if (file_exists($dir['path'].'/'.$appId)) {
491 491
 				$possibleApps[] = $dir;
492 492
 			}
493 493
 		}
@@ -501,7 +501,7 @@  discard block
 block discarded – undo
501 501
 		} else {
502 502
 			$versionToLoad = [];
503 503
 			foreach ($possibleApps as $possibleApp) {
504
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
504
+				$version = self::getAppVersionByPath($possibleApp['path'].'/'.$appId);
505 505
 				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
506 506
 					$versionToLoad = [
507 507
 						'dir' => $possibleApp,
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 		}
532 532
 
533 533
 		if (($dir = self::findAppInDirectories($appId)) != false) {
534
-			return $dir['path'] . '/' . $appId;
534
+			return $dir['path'].'/'.$appId;
535 535
 		}
536 536
 		return false;
537 537
 	}
@@ -546,7 +546,7 @@  discard block
 block discarded – undo
546 546
 	 */
547 547
 	public static function getAppWebPath(string $appId) {
548 548
 		if (($dir = self::findAppInDirectories($appId)) != false) {
549
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
549
+			return OC::$WEBROOT.$dir['url'].'/'.$appId;
550 550
 		}
551 551
 		return false;
552 552
 	}
@@ -570,7 +570,7 @@  discard block
 block discarded – undo
570 570
 	 * @return string
571 571
 	 */
572 572
 	public static function getAppVersionByPath(string $path): string {
573
-		$infoFile = $path . '/appinfo/info.xml';
573
+		$infoFile = $path.'/appinfo/info.xml';
574 574
 		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
575 575
 		return isset($appData['version']) ? $appData['version'] : '';
576 576
 	}
@@ -670,7 +670,7 @@  discard block
 block discarded – undo
670 670
 	 * @param string $page
671 671
 	 */
672 672
 	public static function registerAdmin(string $app, string $page) {
673
-		self::$adminForms[] = $app . '/' . $page . '.php';
673
+		self::$adminForms[] = $app.'/'.$page.'.php';
674 674
 	}
675 675
 
676 676
 	/**
@@ -679,7 +679,7 @@  discard block
 block discarded – undo
679 679
 	 * @param string $page
680 680
 	 */
681 681
 	public static function registerPersonal(string $app, string $page) {
682
-		self::$personalForms[] = $app . '/' . $page . '.php';
682
+		self::$personalForms[] = $app.'/'.$page.'.php';
683 683
 	}
684 684
 
685 685
 	/**
@@ -750,14 +750,14 @@  discard block
 block discarded – undo
750 750
 
751 751
 		foreach (OC::$APPSROOTS as $apps_dir) {
752 752
 			if (!is_readable($apps_dir['path'])) {
753
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
753
+				\OCP\Util::writeLog('core', 'unable to read app folder : '.$apps_dir['path'], ILogger::WARN);
754 754
 				continue;
755 755
 			}
756 756
 			$dh = opendir($apps_dir['path']);
757 757
 
758 758
 			if (is_resource($dh)) {
759 759
 				while (($file = readdir($dh)) !== false) {
760
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
760
+					if ($file[0] != '.' and is_dir($apps_dir['path'].'/'.$file) and is_file($apps_dir['path'].'/'.$file.'/appinfo/info.xml')) {
761 761
 						$apps[] = $file;
762 762
 					}
763 763
 				}
@@ -791,12 +791,12 @@  discard block
 block discarded – undo
791 791
 			if (array_search($app, $blacklist) === false) {
792 792
 				$info = OC_App::getAppInfo($app, false, $langCode);
793 793
 				if (!is_array($info)) {
794
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
794
+					\OCP\Util::writeLog('core', 'Could not read app info file for app "'.$app.'"', ILogger::ERROR);
795 795
 					continue;
796 796
 				}
797 797
 
798 798
 				if (!isset($info['name'])) {
799
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
799
+					\OCP\Util::writeLog('core', 'App id "'.$app.'" has no name in appinfo', ILogger::ERROR);
800 800
 					continue;
801 801
 				}
802 802
 
@@ -828,12 +828,12 @@  discard block
 block discarded – undo
828 828
 
829 829
 				$appPath = self::getAppPath($app);
830 830
 				if ($appPath !== false) {
831
-					$appIcon = $appPath . '/img/' . $app . '.svg';
831
+					$appIcon = $appPath.'/img/'.$app.'.svg';
832 832
 					if (file_exists($appIcon)) {
833
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
833
+						$info['preview'] = $urlGenerator->imagePath($app, $app.'.svg');
834 834
 						$info['previewAsIcon'] = true;
835 835
 					} else {
836
-						$appIcon = $appPath . '/img/app.svg';
836
+						$appIcon = $appPath.'/img/app.svg';
837 837
 						if (file_exists($appIcon)) {
838 838
 							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
839 839
 							$info['previewAsIcon'] = true;
@@ -972,8 +972,8 @@  discard block
 block discarded – undo
972 972
 			return false;
973 973
 		}
974 974
 
975
-		if (is_file($appPath . '/appinfo/database.xml')) {
976
-			\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
975
+		if (is_file($appPath.'/appinfo/database.xml')) {
976
+			\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in '.$appId);
977 977
 			return false;
978 978
 		}
979 979
 
@@ -1010,10 +1010,10 @@  discard block
 block discarded – undo
1010 1010
 			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1011 1011
 		}
1012 1012
 		foreach ($appData['remote'] as $name => $path) {
1013
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1013
+			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $appId.'/'.$path);
1014 1014
 		}
1015 1015
 		foreach ($appData['public'] as $name => $path) {
1016
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1016
+			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $appId.'/'.$path);
1017 1017
 		}
1018 1018
 
1019 1019
 		self::setAppTypes($appId);
@@ -1083,17 +1083,17 @@  discard block
 block discarded – undo
1083 1083
 	public static function getStorage(string $appId) {
1084 1084
 		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1085 1085
 			if (\OC::$server->getUserSession()->isLoggedIn()) {
1086
-				$view = new \OC\Files\View('/' . OC_User::getUser());
1086
+				$view = new \OC\Files\View('/'.OC_User::getUser());
1087 1087
 				if (!$view->file_exists($appId)) {
1088 1088
 					$view->mkdir($appId);
1089 1089
 				}
1090
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1090
+				return new \OC\Files\View('/'.OC_User::getUser().'/'.$appId);
1091 1091
 			} else {
1092
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1092
+				\OCP\Util::writeLog('core', 'Can\'t get app storage, app '.$appId.', user not logged in', ILogger::ERROR);
1093 1093
 				return false;
1094 1094
 			}
1095 1095
 		} else {
1096
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1096
+			\OCP\Util::writeLog('core', 'Can\'t get app storage, app '.$appId.' not enabled', ILogger::ERROR);
1097 1097
 			return false;
1098 1098
 		}
1099 1099
 	}
@@ -1130,7 +1130,7 @@  discard block
 block discarded – undo
1130 1130
 
1131 1131
 				if ($attributeLang === $similarLang) {
1132 1132
 					$similarLangFallback = $option['@value'];
1133
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
1133
+				} elseif (strpos($attributeLang, $similarLang.'_') === 0) {
1134 1134
 					if ($similarLangFallback === false) {
1135 1135
 						$similarLangFallback = $option['@value'];
1136 1136
 					}
Please login to merge, or discard this patch.