Passed
Push — master ( 82498b...607700 )
by Blizzz
14:39 queued 16s
created
lib/private/Updater.php 1 patch
Indentation   +468 added lines, -468 removed lines patch added patch discarded remove patch
@@ -73,472 +73,472 @@
 block discarded – undo
73 73
  *  - failure(string $message)
74 74
  */
75 75
 class Updater extends BasicEmitter {
76
-	/** @var LoggerInterface */
77
-	private $log;
78
-
79
-	/** @var IConfig */
80
-	private $config;
81
-
82
-	/** @var Checker */
83
-	private $checker;
84
-
85
-	/** @var Installer */
86
-	private $installer;
87
-
88
-	private $logLevelNames = [
89
-		0 => 'Debug',
90
-		1 => 'Info',
91
-		2 => 'Warning',
92
-		3 => 'Error',
93
-		4 => 'Fatal',
94
-	];
95
-
96
-	public function __construct(IConfig $config,
97
-								Checker $checker,
98
-								?LoggerInterface $log,
99
-								Installer $installer) {
100
-		$this->log = $log;
101
-		$this->config = $config;
102
-		$this->checker = $checker;
103
-		$this->installer = $installer;
104
-	}
105
-
106
-	/**
107
-	 * runs the update actions in maintenance mode, does not upgrade the source files
108
-	 * except the main .htaccess file
109
-	 *
110
-	 * @return bool true if the operation succeeded, false otherwise
111
-	 */
112
-	public function upgrade(): bool {
113
-		$this->logAllEvents();
114
-
115
-		$logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
116
-		$this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
117
-		$this->config->setSystemValue('loglevel', ILogger::DEBUG);
118
-
119
-		$wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance');
120
-
121
-		if (!$wasMaintenanceModeEnabled) {
122
-			$this->config->setSystemValue('maintenance', true);
123
-			$this->emit('\OC\Updater', 'maintenanceEnabled');
124
-		}
125
-
126
-		// Clear CAN_INSTALL file if not on git
127
-		if (\OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL')) {
128
-			if (!unlink(\OC::$configDir . '/CAN_INSTALL')) {
129
-				$this->log->error('Could not cleanup CAN_INSTALL from your config folder. Please remove this file manually.');
130
-			}
131
-		}
132
-
133
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
134
-		$currentVersion = implode('.', \OCP\Util::getVersion());
135
-
136
-		$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, ['app' => 'core']);
137
-
138
-		$success = true;
139
-		try {
140
-			$this->doUpgrade($currentVersion, $installedVersion);
141
-		} catch (HintException $exception) {
142
-			$this->log->error($exception->getMessage(), [
143
-				'exception' => $exception,
144
-			]);
145
-			$this->emit('\OC\Updater', 'failure', [$exception->getMessage() . ': ' .$exception->getHint()]);
146
-			$success = false;
147
-		} catch (\Exception $exception) {
148
-			$this->log->error($exception->getMessage(), [
149
-				'exception' => $exception,
150
-			]);
151
-			$this->emit('\OC\Updater', 'failure', [get_class($exception) . ': ' .$exception->getMessage()]);
152
-			$success = false;
153
-		}
154
-
155
-		$this->emit('\OC\Updater', 'updateEnd', [$success]);
156
-
157
-		if (!$wasMaintenanceModeEnabled && $success) {
158
-			$this->config->setSystemValue('maintenance', false);
159
-			$this->emit('\OC\Updater', 'maintenanceDisabled');
160
-		} else {
161
-			$this->emit('\OC\Updater', 'maintenanceActive');
162
-		}
163
-
164
-		$this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
165
-		$this->config->setSystemValue('loglevel', $logLevel);
166
-		$this->config->setSystemValue('installed', true);
167
-
168
-		return $success;
169
-	}
170
-
171
-	/**
172
-	 * Return version from which this version is allowed to upgrade from
173
-	 *
174
-	 * @return array allowed previous versions per vendor
175
-	 */
176
-	private function getAllowedPreviousVersions(): array {
177
-		// this should really be a JSON file
178
-		require \OC::$SERVERROOT . '/version.php';
179
-		/** @var array $OC_VersionCanBeUpgradedFrom */
180
-		return $OC_VersionCanBeUpgradedFrom;
181
-	}
182
-
183
-	/**
184
-	 * Return vendor from which this version was published
185
-	 *
186
-	 * @return string Get the vendor
187
-	 */
188
-	private function getVendor(): string {
189
-		// this should really be a JSON file
190
-		require \OC::$SERVERROOT . '/version.php';
191
-		/** @var string $vendor */
192
-		return (string) $vendor;
193
-	}
194
-
195
-	/**
196
-	 * Whether an upgrade to a specified version is possible
197
-	 * @param string $oldVersion
198
-	 * @param string $newVersion
199
-	 * @param array $allowedPreviousVersions
200
-	 * @return bool
201
-	 */
202
-	public function isUpgradePossible(string $oldVersion, string $newVersion, array $allowedPreviousVersions): bool {
203
-		$version = explode('.', $oldVersion);
204
-		$majorMinor = $version[0] . '.' . $version[1];
205
-
206
-		$currentVendor = $this->config->getAppValue('core', 'vendor', '');
207
-
208
-		// Vendor was not set correctly on install, so we have to white-list known versions
209
-		if ($currentVendor === '' && (
210
-			isset($allowedPreviousVersions['owncloud'][$oldVersion]) ||
211
-			isset($allowedPreviousVersions['owncloud'][$majorMinor])
212
-		)) {
213
-			$currentVendor = 'owncloud';
214
-			$this->config->setAppValue('core', 'vendor', $currentVendor);
215
-		}
216
-
217
-		if ($currentVendor === 'nextcloud') {
218
-			return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
219
-				&& (version_compare($oldVersion, $newVersion, '<=') ||
220
-					$this->config->getSystemValue('debug', false));
221
-		}
222
-
223
-		// Check if the instance can be migrated
224
-		return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
225
-			isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
226
-	}
227
-
228
-	/**
229
-	 * runs the update actions in maintenance mode, does not upgrade the source files
230
-	 * except the main .htaccess file
231
-	 *
232
-	 * @param string $currentVersion current version to upgrade to
233
-	 * @param string $installedVersion previous version from which to upgrade from
234
-	 *
235
-	 * @throws \Exception
236
-	 */
237
-	private function doUpgrade(string $currentVersion, string $installedVersion): void {
238
-		// Stop update if the update is over several major versions
239
-		$allowedPreviousVersions = $this->getAllowedPreviousVersions();
240
-		if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
241
-			throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
242
-		}
243
-
244
-		// Update .htaccess files
245
-		try {
246
-			Setup::updateHtaccess();
247
-			Setup::protectDataDirectory();
248
-		} catch (\Exception $e) {
249
-			throw new \Exception($e->getMessage());
250
-		}
251
-
252
-		// create empty file in data dir, so we can later find
253
-		// out that this is indeed an ownCloud data directory
254
-		// (in case it didn't exist before)
255
-		file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
256
-
257
-		// pre-upgrade repairs
258
-		$repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class), \OC::$server->get(LoggerInterface::class));
259
-		$repair->run();
260
-
261
-		$this->doCoreUpgrade();
262
-
263
-		try {
264
-			// TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
265
-			Setup::installBackgroundJobs();
266
-		} catch (\Exception $e) {
267
-			throw new \Exception($e->getMessage());
268
-		}
269
-
270
-		// update all shipped apps
271
-		$this->checkAppsRequirements();
272
-		$this->doAppUpgrade();
273
-
274
-		// Update the appfetchers version so it downloads the correct list from the appstore
275
-		\OC::$server->getAppFetcher()->setVersion($currentVersion);
276
-
277
-		/** @var IAppManager|AppManager $appManager */
278
-		$appManager = \OC::$server->getAppManager();
279
-
280
-		// upgrade appstore apps
281
-		$this->upgradeAppStoreApps($appManager->getInstalledApps());
282
-		$autoDisabledApps = $appManager->getAutoDisabledApps();
283
-		if (!empty($autoDisabledApps)) {
284
-			$this->upgradeAppStoreApps(array_keys($autoDisabledApps), $autoDisabledApps);
285
-		}
286
-
287
-		// install new shipped apps on upgrade
288
-		$errors = Installer::installShippedApps(true);
289
-		foreach ($errors as $appId => $exception) {
290
-			/** @var \Exception $exception */
291
-			$this->log->error($exception->getMessage(), [
292
-				'exception' => $exception,
293
-				'app' => $appId,
294
-			]);
295
-			$this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
296
-		}
297
-
298
-		// post-upgrade repairs
299
-		$repair = new Repair(Repair::getRepairSteps(), \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class), \OC::$server->get(LoggerInterface::class));
300
-		$repair->run();
301
-
302
-		//Invalidate update feed
303
-		$this->config->setAppValue('core', 'lastupdatedat', '0');
304
-
305
-		// Check for code integrity if not disabled
306
-		if (\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
307
-			$this->emit('\OC\Updater', 'startCheckCodeIntegrity');
308
-			$this->checker->runInstanceVerification();
309
-			$this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
310
-		}
311
-
312
-		// only set the final version if everything went well
313
-		$this->config->setSystemValue('version', implode('.', Util::getVersion()));
314
-		$this->config->setAppValue('core', 'vendor', $this->getVendor());
315
-	}
316
-
317
-	protected function doCoreUpgrade(): void {
318
-		$this->emit('\OC\Updater', 'dbUpgradeBefore');
319
-
320
-		// execute core migrations
321
-		$ms = new MigrationService('core', \OC::$server->get(Connection::class));
322
-		$ms->migrate();
323
-
324
-		$this->emit('\OC\Updater', 'dbUpgrade');
325
-	}
326
-
327
-	/**
328
-	 * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
329
-	 * (types authentication, filesystem, logging, in that order) afterwards.
330
-	 *
331
-	 * @throws NeedsUpdateException
332
-	 */
333
-	protected function doAppUpgrade(): void {
334
-		$apps = \OC_App::getEnabledApps();
335
-		$priorityTypes = ['authentication', 'filesystem', 'logging'];
336
-		$pseudoOtherType = 'other';
337
-		$stacks = [$pseudoOtherType => []];
338
-
339
-		foreach ($apps as $appId) {
340
-			$priorityType = false;
341
-			foreach ($priorityTypes as $type) {
342
-				if (!isset($stacks[$type])) {
343
-					$stacks[$type] = [];
344
-				}
345
-				if (\OC_App::isType($appId, [$type])) {
346
-					$stacks[$type][] = $appId;
347
-					$priorityType = true;
348
-					break;
349
-				}
350
-			}
351
-			if (!$priorityType) {
352
-				$stacks[$pseudoOtherType][] = $appId;
353
-			}
354
-		}
355
-		foreach (array_merge($priorityTypes, [$pseudoOtherType]) as $type) {
356
-			$stack = $stacks[$type];
357
-			foreach ($stack as $appId) {
358
-				if (\OC_App::shouldUpgrade($appId)) {
359
-					$this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
360
-					\OC_App::updateApp($appId);
361
-					$this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
362
-				}
363
-				if ($type !== $pseudoOtherType) {
364
-					// load authentication, filesystem and logging apps after
365
-					// upgrading them. Other apps my need to rely on modifying
366
-					// user and/or filesystem aspects.
367
-					\OC_App::loadApp($appId);
368
-				}
369
-			}
370
-		}
371
-	}
372
-
373
-	/**
374
-	 * check if the current enabled apps are compatible with the current
375
-	 * ownCloud version. disable them if not.
376
-	 * This is important if you upgrade ownCloud and have non ported 3rd
377
-	 * party apps installed.
378
-	 *
379
-	 * @throws \Exception
380
-	 */
381
-	private function checkAppsRequirements(): void {
382
-		$isCoreUpgrade = $this->isCodeUpgrade();
383
-		$apps = OC_App::getEnabledApps();
384
-		$version = implode('.', Util::getVersion());
385
-		$appManager = \OC::$server->getAppManager();
386
-		foreach ($apps as $app) {
387
-			// check if the app is compatible with this version of Nextcloud
388
-			$info = $appManager->getAppInfo($app);
389
-			if ($info === null || !OC_App::isAppCompatible($version, $info)) {
390
-				if ($appManager->isShipped($app)) {
391
-					throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
392
-				}
393
-				$appManager->disableApp($app, true);
394
-				$this->emit('\OC\Updater', 'incompatibleAppDisabled', [$app]);
395
-			}
396
-		}
397
-	}
398
-
399
-	/**
400
-	 * @return bool
401
-	 */
402
-	private function isCodeUpgrade(): bool {
403
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
404
-		$currentVersion = implode('.', Util::getVersion());
405
-		if (version_compare($currentVersion, $installedVersion, '>')) {
406
-			return true;
407
-		}
408
-		return false;
409
-	}
410
-
411
-	/**
412
-	 * @param array $apps
413
-	 * @param array $previousEnableStates
414
-	 * @throws \Exception
415
-	 */
416
-	private function upgradeAppStoreApps(array $apps, array $previousEnableStates = []): void {
417
-		foreach ($apps as $app) {
418
-			try {
419
-				$this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
420
-				if ($this->installer->isUpdateAvailable($app)) {
421
-					$this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
422
-					$this->installer->updateAppstoreApp($app);
423
-				}
424
-				$this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
425
-
426
-				if (!empty($previousEnableStates)) {
427
-					$ocApp = new \OC_App();
428
-					if (!empty($previousEnableStates[$app]) && is_array($previousEnableStates[$app])) {
429
-						$ocApp->enable($app, $previousEnableStates[$app]);
430
-					} else {
431
-						$ocApp->enable($app);
432
-					}
433
-				}
434
-			} catch (\Exception $ex) {
435
-				$this->log->error($ex->getMessage(), [
436
-					'exception' => $ex,
437
-				]);
438
-			}
439
-		}
440
-	}
441
-
442
-	private function logAllEvents(): void {
443
-		$log = $this->log;
444
-
445
-		/** @var IEventDispatcher $dispatcher */
446
-		$dispatcher = \OC::$server->get(IEventDispatcher::class);
447
-		$dispatcher->addListener(
448
-			MigratorExecuteSqlEvent::class,
449
-			function (MigratorExecuteSqlEvent $event) use ($log): void {
450
-				$log->info(get_class($event).': ' . $event->getSql() . ' (' . $event->getCurrentStep() . ' of ' . $event->getMaxStep() . ')', ['app' => 'updater']);
451
-			}
452
-		);
453
-
454
-		$repairListener = function (Event $event) use ($log): void {
455
-			if ($event instanceof RepairStartEvent) {
456
-				$log->info(get_class($event).': Starting ... ' . $event->getMaxStep() .  ' (' . $event->getCurrentStepName() . ')', ['app' => 'updater']);
457
-			} elseif ($event instanceof RepairAdvanceEvent) {
458
-				$desc = $event->getDescription();
459
-				if (empty($desc)) {
460
-					$desc = '';
461
-				}
462
-				$log->info(get_class($event).': ' . $desc . ' (' . $event->getIncrement() . ')', ['app' => 'updater']);
463
-			} elseif ($event instanceof RepairFinishEvent) {
464
-				$log->info(get_class($event), ['app' => 'updater']);
465
-			} elseif ($event instanceof RepairStepEvent) {
466
-				$log->info(get_class($event).': Repair step: ' . $event->getStepName(), ['app' => 'updater']);
467
-			} elseif ($event instanceof RepairInfoEvent) {
468
-				$log->info(get_class($event).': Repair info: ' . $event->getMessage(), ['app' => 'updater']);
469
-			} elseif ($event instanceof RepairWarningEvent) {
470
-				$log->warning(get_class($event).': Repair warning: ' . $event->getMessage(), ['app' => 'updater']);
471
-			} elseif ($event instanceof RepairErrorEvent) {
472
-				$log->error(get_class($event).': Repair error: ' . $event->getMessage(), ['app' => 'updater']);
473
-			}
474
-		};
475
-
476
-		$dispatcher->addListener(RepairStartEvent::class, $repairListener);
477
-		$dispatcher->addListener(RepairAdvanceEvent::class, $repairListener);
478
-		$dispatcher->addListener(RepairFinishEvent::class, $repairListener);
479
-		$dispatcher->addListener(RepairStepEvent::class, $repairListener);
480
-		$dispatcher->addListener(RepairInfoEvent::class, $repairListener);
481
-		$dispatcher->addListener(RepairWarningEvent::class, $repairListener);
482
-		$dispatcher->addListener(RepairErrorEvent::class, $repairListener);
483
-
484
-
485
-		$this->listen('\OC\Updater', 'maintenanceEnabled', function () use ($log) {
486
-			$log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
487
-		});
488
-		$this->listen('\OC\Updater', 'maintenanceDisabled', function () use ($log) {
489
-			$log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
490
-		});
491
-		$this->listen('\OC\Updater', 'maintenanceActive', function () use ($log) {
492
-			$log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
493
-		});
494
-		$this->listen('\OC\Updater', 'updateEnd', function ($success) use ($log) {
495
-			if ($success) {
496
-				$log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
497
-			} else {
498
-				$log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
499
-			}
500
-		});
501
-		$this->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($log) {
502
-			$log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
503
-		});
504
-		$this->listen('\OC\Updater', 'dbUpgrade', function () use ($log) {
505
-			$log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
506
-		});
507
-		$this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use ($log) {
508
-			$log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
509
-		});
510
-		$this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use ($log) {
511
-			$log->debug('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
512
-		});
513
-		$this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($log) {
514
-			$log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
515
-		});
516
-		$this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use ($log) {
517
-			$log->debug('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
518
-		});
519
-		$this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
520
-			$log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
521
-		});
522
-		$this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
523
-			$log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
524
-		});
525
-		$this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
526
-			$log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
527
-		});
528
-		$this->listen('\OC\Updater', 'failure', function ($message) use ($log) {
529
-			$log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
530
-		});
531
-		$this->listen('\OC\Updater', 'setDebugLogLevel', function () use ($log) {
532
-			$log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
533
-		});
534
-		$this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($log) {
535
-			$log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
536
-		});
537
-		$this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($log) {
538
-			$log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
539
-		});
540
-		$this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($log) {
541
-			$log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
542
-		});
543
-	}
76
+    /** @var LoggerInterface */
77
+    private $log;
78
+
79
+    /** @var IConfig */
80
+    private $config;
81
+
82
+    /** @var Checker */
83
+    private $checker;
84
+
85
+    /** @var Installer */
86
+    private $installer;
87
+
88
+    private $logLevelNames = [
89
+        0 => 'Debug',
90
+        1 => 'Info',
91
+        2 => 'Warning',
92
+        3 => 'Error',
93
+        4 => 'Fatal',
94
+    ];
95
+
96
+    public function __construct(IConfig $config,
97
+                                Checker $checker,
98
+                                ?LoggerInterface $log,
99
+                                Installer $installer) {
100
+        $this->log = $log;
101
+        $this->config = $config;
102
+        $this->checker = $checker;
103
+        $this->installer = $installer;
104
+    }
105
+
106
+    /**
107
+     * runs the update actions in maintenance mode, does not upgrade the source files
108
+     * except the main .htaccess file
109
+     *
110
+     * @return bool true if the operation succeeded, false otherwise
111
+     */
112
+    public function upgrade(): bool {
113
+        $this->logAllEvents();
114
+
115
+        $logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
116
+        $this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
117
+        $this->config->setSystemValue('loglevel', ILogger::DEBUG);
118
+
119
+        $wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance');
120
+
121
+        if (!$wasMaintenanceModeEnabled) {
122
+            $this->config->setSystemValue('maintenance', true);
123
+            $this->emit('\OC\Updater', 'maintenanceEnabled');
124
+        }
125
+
126
+        // Clear CAN_INSTALL file if not on git
127
+        if (\OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL')) {
128
+            if (!unlink(\OC::$configDir . '/CAN_INSTALL')) {
129
+                $this->log->error('Could not cleanup CAN_INSTALL from your config folder. Please remove this file manually.');
130
+            }
131
+        }
132
+
133
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
134
+        $currentVersion = implode('.', \OCP\Util::getVersion());
135
+
136
+        $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, ['app' => 'core']);
137
+
138
+        $success = true;
139
+        try {
140
+            $this->doUpgrade($currentVersion, $installedVersion);
141
+        } catch (HintException $exception) {
142
+            $this->log->error($exception->getMessage(), [
143
+                'exception' => $exception,
144
+            ]);
145
+            $this->emit('\OC\Updater', 'failure', [$exception->getMessage() . ': ' .$exception->getHint()]);
146
+            $success = false;
147
+        } catch (\Exception $exception) {
148
+            $this->log->error($exception->getMessage(), [
149
+                'exception' => $exception,
150
+            ]);
151
+            $this->emit('\OC\Updater', 'failure', [get_class($exception) . ': ' .$exception->getMessage()]);
152
+            $success = false;
153
+        }
154
+
155
+        $this->emit('\OC\Updater', 'updateEnd', [$success]);
156
+
157
+        if (!$wasMaintenanceModeEnabled && $success) {
158
+            $this->config->setSystemValue('maintenance', false);
159
+            $this->emit('\OC\Updater', 'maintenanceDisabled');
160
+        } else {
161
+            $this->emit('\OC\Updater', 'maintenanceActive');
162
+        }
163
+
164
+        $this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
165
+        $this->config->setSystemValue('loglevel', $logLevel);
166
+        $this->config->setSystemValue('installed', true);
167
+
168
+        return $success;
169
+    }
170
+
171
+    /**
172
+     * Return version from which this version is allowed to upgrade from
173
+     *
174
+     * @return array allowed previous versions per vendor
175
+     */
176
+    private function getAllowedPreviousVersions(): array {
177
+        // this should really be a JSON file
178
+        require \OC::$SERVERROOT . '/version.php';
179
+        /** @var array $OC_VersionCanBeUpgradedFrom */
180
+        return $OC_VersionCanBeUpgradedFrom;
181
+    }
182
+
183
+    /**
184
+     * Return vendor from which this version was published
185
+     *
186
+     * @return string Get the vendor
187
+     */
188
+    private function getVendor(): string {
189
+        // this should really be a JSON file
190
+        require \OC::$SERVERROOT . '/version.php';
191
+        /** @var string $vendor */
192
+        return (string) $vendor;
193
+    }
194
+
195
+    /**
196
+     * Whether an upgrade to a specified version is possible
197
+     * @param string $oldVersion
198
+     * @param string $newVersion
199
+     * @param array $allowedPreviousVersions
200
+     * @return bool
201
+     */
202
+    public function isUpgradePossible(string $oldVersion, string $newVersion, array $allowedPreviousVersions): bool {
203
+        $version = explode('.', $oldVersion);
204
+        $majorMinor = $version[0] . '.' . $version[1];
205
+
206
+        $currentVendor = $this->config->getAppValue('core', 'vendor', '');
207
+
208
+        // Vendor was not set correctly on install, so we have to white-list known versions
209
+        if ($currentVendor === '' && (
210
+            isset($allowedPreviousVersions['owncloud'][$oldVersion]) ||
211
+            isset($allowedPreviousVersions['owncloud'][$majorMinor])
212
+        )) {
213
+            $currentVendor = 'owncloud';
214
+            $this->config->setAppValue('core', 'vendor', $currentVendor);
215
+        }
216
+
217
+        if ($currentVendor === 'nextcloud') {
218
+            return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
219
+                && (version_compare($oldVersion, $newVersion, '<=') ||
220
+                    $this->config->getSystemValue('debug', false));
221
+        }
222
+
223
+        // Check if the instance can be migrated
224
+        return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
225
+            isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
226
+    }
227
+
228
+    /**
229
+     * runs the update actions in maintenance mode, does not upgrade the source files
230
+     * except the main .htaccess file
231
+     *
232
+     * @param string $currentVersion current version to upgrade to
233
+     * @param string $installedVersion previous version from which to upgrade from
234
+     *
235
+     * @throws \Exception
236
+     */
237
+    private function doUpgrade(string $currentVersion, string $installedVersion): void {
238
+        // Stop update if the update is over several major versions
239
+        $allowedPreviousVersions = $this->getAllowedPreviousVersions();
240
+        if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
241
+            throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
242
+        }
243
+
244
+        // Update .htaccess files
245
+        try {
246
+            Setup::updateHtaccess();
247
+            Setup::protectDataDirectory();
248
+        } catch (\Exception $e) {
249
+            throw new \Exception($e->getMessage());
250
+        }
251
+
252
+        // create empty file in data dir, so we can later find
253
+        // out that this is indeed an ownCloud data directory
254
+        // (in case it didn't exist before)
255
+        file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
256
+
257
+        // pre-upgrade repairs
258
+        $repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class), \OC::$server->get(LoggerInterface::class));
259
+        $repair->run();
260
+
261
+        $this->doCoreUpgrade();
262
+
263
+        try {
264
+            // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
265
+            Setup::installBackgroundJobs();
266
+        } catch (\Exception $e) {
267
+            throw new \Exception($e->getMessage());
268
+        }
269
+
270
+        // update all shipped apps
271
+        $this->checkAppsRequirements();
272
+        $this->doAppUpgrade();
273
+
274
+        // Update the appfetchers version so it downloads the correct list from the appstore
275
+        \OC::$server->getAppFetcher()->setVersion($currentVersion);
276
+
277
+        /** @var IAppManager|AppManager $appManager */
278
+        $appManager = \OC::$server->getAppManager();
279
+
280
+        // upgrade appstore apps
281
+        $this->upgradeAppStoreApps($appManager->getInstalledApps());
282
+        $autoDisabledApps = $appManager->getAutoDisabledApps();
283
+        if (!empty($autoDisabledApps)) {
284
+            $this->upgradeAppStoreApps(array_keys($autoDisabledApps), $autoDisabledApps);
285
+        }
286
+
287
+        // install new shipped apps on upgrade
288
+        $errors = Installer::installShippedApps(true);
289
+        foreach ($errors as $appId => $exception) {
290
+            /** @var \Exception $exception */
291
+            $this->log->error($exception->getMessage(), [
292
+                'exception' => $exception,
293
+                'app' => $appId,
294
+            ]);
295
+            $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
296
+        }
297
+
298
+        // post-upgrade repairs
299
+        $repair = new Repair(Repair::getRepairSteps(), \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class), \OC::$server->get(LoggerInterface::class));
300
+        $repair->run();
301
+
302
+        //Invalidate update feed
303
+        $this->config->setAppValue('core', 'lastupdatedat', '0');
304
+
305
+        // Check for code integrity if not disabled
306
+        if (\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
307
+            $this->emit('\OC\Updater', 'startCheckCodeIntegrity');
308
+            $this->checker->runInstanceVerification();
309
+            $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
310
+        }
311
+
312
+        // only set the final version if everything went well
313
+        $this->config->setSystemValue('version', implode('.', Util::getVersion()));
314
+        $this->config->setAppValue('core', 'vendor', $this->getVendor());
315
+    }
316
+
317
+    protected function doCoreUpgrade(): void {
318
+        $this->emit('\OC\Updater', 'dbUpgradeBefore');
319
+
320
+        // execute core migrations
321
+        $ms = new MigrationService('core', \OC::$server->get(Connection::class));
322
+        $ms->migrate();
323
+
324
+        $this->emit('\OC\Updater', 'dbUpgrade');
325
+    }
326
+
327
+    /**
328
+     * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
329
+     * (types authentication, filesystem, logging, in that order) afterwards.
330
+     *
331
+     * @throws NeedsUpdateException
332
+     */
333
+    protected function doAppUpgrade(): void {
334
+        $apps = \OC_App::getEnabledApps();
335
+        $priorityTypes = ['authentication', 'filesystem', 'logging'];
336
+        $pseudoOtherType = 'other';
337
+        $stacks = [$pseudoOtherType => []];
338
+
339
+        foreach ($apps as $appId) {
340
+            $priorityType = false;
341
+            foreach ($priorityTypes as $type) {
342
+                if (!isset($stacks[$type])) {
343
+                    $stacks[$type] = [];
344
+                }
345
+                if (\OC_App::isType($appId, [$type])) {
346
+                    $stacks[$type][] = $appId;
347
+                    $priorityType = true;
348
+                    break;
349
+                }
350
+            }
351
+            if (!$priorityType) {
352
+                $stacks[$pseudoOtherType][] = $appId;
353
+            }
354
+        }
355
+        foreach (array_merge($priorityTypes, [$pseudoOtherType]) as $type) {
356
+            $stack = $stacks[$type];
357
+            foreach ($stack as $appId) {
358
+                if (\OC_App::shouldUpgrade($appId)) {
359
+                    $this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
360
+                    \OC_App::updateApp($appId);
361
+                    $this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
362
+                }
363
+                if ($type !== $pseudoOtherType) {
364
+                    // load authentication, filesystem and logging apps after
365
+                    // upgrading them. Other apps my need to rely on modifying
366
+                    // user and/or filesystem aspects.
367
+                    \OC_App::loadApp($appId);
368
+                }
369
+            }
370
+        }
371
+    }
372
+
373
+    /**
374
+     * check if the current enabled apps are compatible with the current
375
+     * ownCloud version. disable them if not.
376
+     * This is important if you upgrade ownCloud and have non ported 3rd
377
+     * party apps installed.
378
+     *
379
+     * @throws \Exception
380
+     */
381
+    private function checkAppsRequirements(): void {
382
+        $isCoreUpgrade = $this->isCodeUpgrade();
383
+        $apps = OC_App::getEnabledApps();
384
+        $version = implode('.', Util::getVersion());
385
+        $appManager = \OC::$server->getAppManager();
386
+        foreach ($apps as $app) {
387
+            // check if the app is compatible with this version of Nextcloud
388
+            $info = $appManager->getAppInfo($app);
389
+            if ($info === null || !OC_App::isAppCompatible($version, $info)) {
390
+                if ($appManager->isShipped($app)) {
391
+                    throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
392
+                }
393
+                $appManager->disableApp($app, true);
394
+                $this->emit('\OC\Updater', 'incompatibleAppDisabled', [$app]);
395
+            }
396
+        }
397
+    }
398
+
399
+    /**
400
+     * @return bool
401
+     */
402
+    private function isCodeUpgrade(): bool {
403
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
404
+        $currentVersion = implode('.', Util::getVersion());
405
+        if (version_compare($currentVersion, $installedVersion, '>')) {
406
+            return true;
407
+        }
408
+        return false;
409
+    }
410
+
411
+    /**
412
+     * @param array $apps
413
+     * @param array $previousEnableStates
414
+     * @throws \Exception
415
+     */
416
+    private function upgradeAppStoreApps(array $apps, array $previousEnableStates = []): void {
417
+        foreach ($apps as $app) {
418
+            try {
419
+                $this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
420
+                if ($this->installer->isUpdateAvailable($app)) {
421
+                    $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
422
+                    $this->installer->updateAppstoreApp($app);
423
+                }
424
+                $this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
425
+
426
+                if (!empty($previousEnableStates)) {
427
+                    $ocApp = new \OC_App();
428
+                    if (!empty($previousEnableStates[$app]) && is_array($previousEnableStates[$app])) {
429
+                        $ocApp->enable($app, $previousEnableStates[$app]);
430
+                    } else {
431
+                        $ocApp->enable($app);
432
+                    }
433
+                }
434
+            } catch (\Exception $ex) {
435
+                $this->log->error($ex->getMessage(), [
436
+                    'exception' => $ex,
437
+                ]);
438
+            }
439
+        }
440
+    }
441
+
442
+    private function logAllEvents(): void {
443
+        $log = $this->log;
444
+
445
+        /** @var IEventDispatcher $dispatcher */
446
+        $dispatcher = \OC::$server->get(IEventDispatcher::class);
447
+        $dispatcher->addListener(
448
+            MigratorExecuteSqlEvent::class,
449
+            function (MigratorExecuteSqlEvent $event) use ($log): void {
450
+                $log->info(get_class($event).': ' . $event->getSql() . ' (' . $event->getCurrentStep() . ' of ' . $event->getMaxStep() . ')', ['app' => 'updater']);
451
+            }
452
+        );
453
+
454
+        $repairListener = function (Event $event) use ($log): void {
455
+            if ($event instanceof RepairStartEvent) {
456
+                $log->info(get_class($event).': Starting ... ' . $event->getMaxStep() .  ' (' . $event->getCurrentStepName() . ')', ['app' => 'updater']);
457
+            } elseif ($event instanceof RepairAdvanceEvent) {
458
+                $desc = $event->getDescription();
459
+                if (empty($desc)) {
460
+                    $desc = '';
461
+                }
462
+                $log->info(get_class($event).': ' . $desc . ' (' . $event->getIncrement() . ')', ['app' => 'updater']);
463
+            } elseif ($event instanceof RepairFinishEvent) {
464
+                $log->info(get_class($event), ['app' => 'updater']);
465
+            } elseif ($event instanceof RepairStepEvent) {
466
+                $log->info(get_class($event).': Repair step: ' . $event->getStepName(), ['app' => 'updater']);
467
+            } elseif ($event instanceof RepairInfoEvent) {
468
+                $log->info(get_class($event).': Repair info: ' . $event->getMessage(), ['app' => 'updater']);
469
+            } elseif ($event instanceof RepairWarningEvent) {
470
+                $log->warning(get_class($event).': Repair warning: ' . $event->getMessage(), ['app' => 'updater']);
471
+            } elseif ($event instanceof RepairErrorEvent) {
472
+                $log->error(get_class($event).': Repair error: ' . $event->getMessage(), ['app' => 'updater']);
473
+            }
474
+        };
475
+
476
+        $dispatcher->addListener(RepairStartEvent::class, $repairListener);
477
+        $dispatcher->addListener(RepairAdvanceEvent::class, $repairListener);
478
+        $dispatcher->addListener(RepairFinishEvent::class, $repairListener);
479
+        $dispatcher->addListener(RepairStepEvent::class, $repairListener);
480
+        $dispatcher->addListener(RepairInfoEvent::class, $repairListener);
481
+        $dispatcher->addListener(RepairWarningEvent::class, $repairListener);
482
+        $dispatcher->addListener(RepairErrorEvent::class, $repairListener);
483
+
484
+
485
+        $this->listen('\OC\Updater', 'maintenanceEnabled', function () use ($log) {
486
+            $log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
487
+        });
488
+        $this->listen('\OC\Updater', 'maintenanceDisabled', function () use ($log) {
489
+            $log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
490
+        });
491
+        $this->listen('\OC\Updater', 'maintenanceActive', function () use ($log) {
492
+            $log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
493
+        });
494
+        $this->listen('\OC\Updater', 'updateEnd', function ($success) use ($log) {
495
+            if ($success) {
496
+                $log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
497
+            } else {
498
+                $log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
499
+            }
500
+        });
501
+        $this->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($log) {
502
+            $log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
503
+        });
504
+        $this->listen('\OC\Updater', 'dbUpgrade', function () use ($log) {
505
+            $log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
506
+        });
507
+        $this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use ($log) {
508
+            $log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
509
+        });
510
+        $this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use ($log) {
511
+            $log->debug('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
512
+        });
513
+        $this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($log) {
514
+            $log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
515
+        });
516
+        $this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use ($log) {
517
+            $log->debug('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
518
+        });
519
+        $this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
520
+            $log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
521
+        });
522
+        $this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
523
+            $log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
524
+        });
525
+        $this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
526
+            $log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
527
+        });
528
+        $this->listen('\OC\Updater', 'failure', function ($message) use ($log) {
529
+            $log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
530
+        });
531
+        $this->listen('\OC\Updater', 'setDebugLogLevel', function () use ($log) {
532
+            $log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
533
+        });
534
+        $this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($log) {
535
+            $log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
536
+        });
537
+        $this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($log) {
538
+            $log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
539
+        });
540
+        $this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($log) {
541
+            $log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
542
+        });
543
+    }
544 544
 }
Please login to merge, or discard this patch.