Passed
Push — master ( e9aefb...af214b )
by Joas
14:53 queued 12s
created
lib/private/Files/Filesystem.php 1 patch
Indentation   +713 added lines, -713 removed lines patch added patch discarded remove patch
@@ -50,717 +50,717 @@
 block discarded – undo
50 50
 use OCP\IUserSession;
51 51
 
52 52
 class Filesystem {
53
-	private static ?Mount\Manager $mounts = null;
54
-
55
-	public static bool $loaded = false;
56
-
57
-	private static ?View $defaultInstance = null;
58
-
59
-	private static ?CappedMemoryCache $normalizedPathCache = null;
60
-
61
-	/** @var string[]|null */
62
-	private static ?array $blacklist = null;
63
-
64
-	/**
65
-	 * classname which used for hooks handling
66
-	 * used as signalclass in OC_Hooks::emit()
67
-	 */
68
-	public const CLASSNAME = 'OC_Filesystem';
69
-
70
-	/**
71
-	 * signalname emitted before file renaming
72
-	 *
73
-	 * @param string $oldpath
74
-	 * @param string $newpath
75
-	 */
76
-	public const signal_rename = 'rename';
77
-
78
-	/**
79
-	 * signal emitted after file renaming
80
-	 *
81
-	 * @param string $oldpath
82
-	 * @param string $newpath
83
-	 */
84
-	public const signal_post_rename = 'post_rename';
85
-
86
-	/**
87
-	 * signal emitted before file/dir creation
88
-	 *
89
-	 * @param string $path
90
-	 * @param bool $run changing this flag to false in hook handler will cancel event
91
-	 */
92
-	public const signal_create = 'create';
93
-
94
-	/**
95
-	 * signal emitted after file/dir creation
96
-	 *
97
-	 * @param string $path
98
-	 * @param bool $run changing this flag to false in hook handler will cancel event
99
-	 */
100
-	public const signal_post_create = 'post_create';
101
-
102
-	/**
103
-	 * signal emits before file/dir copy
104
-	 *
105
-	 * @param string $oldpath
106
-	 * @param string $newpath
107
-	 * @param bool $run changing this flag to false in hook handler will cancel event
108
-	 */
109
-	public const signal_copy = 'copy';
110
-
111
-	/**
112
-	 * signal emits after file/dir copy
113
-	 *
114
-	 * @param string $oldpath
115
-	 * @param string $newpath
116
-	 */
117
-	public const signal_post_copy = 'post_copy';
118
-
119
-	/**
120
-	 * signal emits before file/dir save
121
-	 *
122
-	 * @param string $path
123
-	 * @param bool $run changing this flag to false in hook handler will cancel event
124
-	 */
125
-	public const signal_write = 'write';
126
-
127
-	/**
128
-	 * signal emits after file/dir save
129
-	 *
130
-	 * @param string $path
131
-	 */
132
-	public const signal_post_write = 'post_write';
133
-
134
-	/**
135
-	 * signal emitted before file/dir update
136
-	 *
137
-	 * @param string $path
138
-	 * @param bool $run changing this flag to false in hook handler will cancel event
139
-	 */
140
-	public const signal_update = 'update';
141
-
142
-	/**
143
-	 * signal emitted after file/dir update
144
-	 *
145
-	 * @param string $path
146
-	 * @param bool $run changing this flag to false in hook handler will cancel event
147
-	 */
148
-	public const signal_post_update = 'post_update';
149
-
150
-	/**
151
-	 * signal emits when reading file/dir
152
-	 *
153
-	 * @param string $path
154
-	 */
155
-	public const signal_read = 'read';
156
-
157
-	/**
158
-	 * signal emits when removing file/dir
159
-	 *
160
-	 * @param string $path
161
-	 */
162
-	public const signal_delete = 'delete';
163
-
164
-	/**
165
-	 * parameters definitions for signals
166
-	 */
167
-	public const signal_param_path = 'path';
168
-	public const signal_param_oldpath = 'oldpath';
169
-	public const signal_param_newpath = 'newpath';
170
-
171
-	/**
172
-	 * run - changing this flag to false in hook handler will cancel event
173
-	 */
174
-	public const signal_param_run = 'run';
175
-
176
-	public const signal_create_mount = 'create_mount';
177
-	public const signal_delete_mount = 'delete_mount';
178
-	public const signal_param_mount_type = 'mounttype';
179
-	public const signal_param_users = 'users';
180
-
181
-	private static ?\OC\Files\Storage\StorageFactory $loader = null;
182
-
183
-	private static bool $logWarningWhenAddingStorageWrapper = true;
184
-
185
-	/**
186
-	 * @param bool $shouldLog
187
-	 * @return bool previous value
188
-	 * @internal
189
-	 */
190
-	public static function logWarningWhenAddingStorageWrapper(bool $shouldLog): bool {
191
-		$previousValue = self::$logWarningWhenAddingStorageWrapper;
192
-		self::$logWarningWhenAddingStorageWrapper = $shouldLog;
193
-		return $previousValue;
194
-	}
195
-
196
-	/**
197
-	 * @param string $wrapperName
198
-	 * @param callable $wrapper
199
-	 * @param int $priority
200
-	 */
201
-	public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) {
202
-		if (self::$logWarningWhenAddingStorageWrapper) {
203
-			\OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [
204
-				'wrapper' => $wrapperName,
205
-				'app' => 'filesystem',
206
-			]);
207
-		}
208
-
209
-		$mounts = self::getMountManager()->getAll();
210
-		if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
211
-			// do not re-wrap if storage with this name already existed
212
-			return;
213
-		}
214
-	}
215
-
216
-	/**
217
-	 * Returns the storage factory
218
-	 *
219
-	 * @return IStorageFactory
220
-	 */
221
-	public static function getLoader() {
222
-		if (!self::$loader) {
223
-			self::$loader = \OC::$server->get(IStorageFactory::class);
224
-		}
225
-		return self::$loader;
226
-	}
227
-
228
-	/**
229
-	 * Returns the mount manager
230
-	 */
231
-	public static function getMountManager(): Mount\Manager {
232
-		self::initMountManager();
233
-		assert(self::$mounts !== null);
234
-		return self::$mounts;
235
-	}
236
-
237
-	/**
238
-	 * get the mountpoint of the storage object for a path
239
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
240
-	 * returned mountpoint is relative to the absolute root of the filesystem
241
-	 * and doesn't take the chroot into account )
242
-	 *
243
-	 * @param string $path
244
-	 * @return string
245
-	 */
246
-	public static function getMountPoint($path) {
247
-		if (!self::$mounts) {
248
-			\OC_Util::setupFS();
249
-		}
250
-		$mount = self::$mounts->find($path);
251
-		return $mount->getMountPoint();
252
-	}
253
-
254
-	/**
255
-	 * get a list of all mount points in a directory
256
-	 *
257
-	 * @param string $path
258
-	 * @return string[]
259
-	 */
260
-	public static function getMountPoints($path) {
261
-		if (!self::$mounts) {
262
-			\OC_Util::setupFS();
263
-		}
264
-		$result = [];
265
-		$mounts = self::$mounts->findIn($path);
266
-		foreach ($mounts as $mount) {
267
-			$result[] = $mount->getMountPoint();
268
-		}
269
-		return $result;
270
-	}
271
-
272
-	/**
273
-	 * get the storage mounted at $mountPoint
274
-	 *
275
-	 * @param string $mountPoint
276
-	 * @return \OC\Files\Storage\Storage|null
277
-	 */
278
-	public static function getStorage($mountPoint) {
279
-		$mount = self::getMountManager()->find($mountPoint);
280
-		return $mount->getStorage();
281
-	}
282
-
283
-	/**
284
-	 * @param string $id
285
-	 * @return Mount\MountPoint[]
286
-	 */
287
-	public static function getMountByStorageId($id) {
288
-		return self::getMountManager()->findByStorageId($id);
289
-	}
290
-
291
-	/**
292
-	 * @param int $id
293
-	 * @return Mount\MountPoint[]
294
-	 */
295
-	public static function getMountByNumericId($id) {
296
-		return self::getMountManager()->findByNumericId($id);
297
-	}
298
-
299
-	/**
300
-	 * resolve a path to a storage and internal path
301
-	 *
302
-	 * @param string $path
303
-	 * @return array{?\OCP\Files\Storage\IStorage, string} an array consisting of the storage and the internal path
304
-	 */
305
-	public static function resolvePath($path): array {
306
-		$mount = self::getMountManager()->find($path);
307
-		return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')];
308
-	}
309
-
310
-	public static function init(string|IUser|null $user, string $root): bool {
311
-		if (self::$defaultInstance) {
312
-			return false;
313
-		}
314
-		self::initInternal($root);
315
-
316
-		//load custom mount config
317
-		self::initMountPoints($user);
318
-
319
-		return true;
320
-	}
321
-
322
-	public static function initInternal(string $root): bool {
323
-		if (self::$defaultInstance) {
324
-			return false;
325
-		}
326
-		self::getLoader();
327
-		self::$defaultInstance = new View($root);
328
-		/** @var IEventDispatcher $eventDispatcher */
329
-		$eventDispatcher = \OC::$server->get(IEventDispatcher::class);
330
-		$eventDispatcher->addListener(FilesystemTornDownEvent::class, function () {
331
-			self::$defaultInstance = null;
332
-			self::$loaded = false;
333
-		});
334
-
335
-		self::initMountManager();
336
-
337
-		self::$loaded = true;
338
-
339
-		return true;
340
-	}
341
-
342
-	public static function initMountManager(): void {
343
-		if (!self::$mounts) {
344
-			self::$mounts = \OC::$server->get(IMountManager::class);
345
-		}
346
-	}
347
-
348
-	/**
349
-	 * Initialize system and personal mount points for a user
350
-	 *
351
-	 * @throws \OC\User\NoUserException if the user is not available
352
-	 */
353
-	public static function initMountPoints(string|IUser|null $user = ''): void {
354
-		/** @var IUserManager $userManager */
355
-		$userManager = \OC::$server->get(IUserManager::class);
356
-
357
-		$userObject = ($user instanceof IUser) ? $user : $userManager->get($user);
358
-		if ($userObject) {
359
-			/** @var SetupManager $setupManager */
360
-			$setupManager = \OC::$server->get(SetupManager::class);
361
-			$setupManager->setupForUser($userObject);
362
-		} else {
363
-			throw new NoUserException();
364
-		}
365
-	}
366
-
367
-	/**
368
-	 * Get the default filesystem view
369
-	 */
370
-	public static function getView(): ?View {
371
-		if (!self::$defaultInstance) {
372
-			/** @var IUserSession $session */
373
-			$session = \OC::$server->get(IUserSession::class);
374
-			$user = $session->getUser();
375
-			if ($user) {
376
-				$userDir = '/' . $user->getUID() . '/files';
377
-				self::initInternal($userDir);
378
-			}
379
-		}
380
-		return self::$defaultInstance;
381
-	}
382
-
383
-	/**
384
-	 * tear down the filesystem, removing all storage providers
385
-	 */
386
-	public static function tearDown() {
387
-		\OC_Util::tearDownFS();
388
-	}
389
-
390
-	/**
391
-	 * get the relative path of the root data directory for the current user
392
-	 *
393
-	 * @return ?string
394
-	 *
395
-	 * Returns path like /admin/files
396
-	 */
397
-	public static function getRoot() {
398
-		if (!self::$defaultInstance) {
399
-			return null;
400
-		}
401
-		return self::$defaultInstance->getRoot();
402
-	}
403
-
404
-	/**
405
-	 * mount an \OC\Files\Storage\Storage in our virtual filesystem
406
-	 *
407
-	 * @param \OC\Files\Storage\Storage|string $class
408
-	 * @param array $arguments
409
-	 * @param string $mountpoint
410
-	 */
411
-	public static function mount($class, $arguments, $mountpoint) {
412
-		if (!self::$mounts) {
413
-			\OC_Util::setupFS();
414
-		}
415
-		$mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader());
416
-		self::$mounts->addMount($mount);
417
-	}
418
-
419
-	/**
420
-	 * return the path to a local version of the file
421
-	 * we need this because we can't know if a file is stored local or not from
422
-	 * outside the filestorage and for some purposes a local file is needed
423
-	 */
424
-	public static function getLocalFile(string $path): string|false {
425
-		return self::$defaultInstance->getLocalFile($path);
426
-	}
427
-
428
-	/**
429
-	 * return path to file which reflects one visible in browser
430
-	 *
431
-	 * @param string $path
432
-	 * @return string
433
-	 */
434
-	public static function getLocalPath($path) {
435
-		$datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
436
-		$newpath = $path;
437
-		if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
438
-			$newpath = substr($path, strlen($datadir));
439
-		}
440
-		return $newpath;
441
-	}
442
-
443
-	/**
444
-	 * check if the requested path is valid
445
-	 *
446
-	 * @param string $path
447
-	 * @return bool
448
-	 */
449
-	public static function isValidPath($path) {
450
-		$path = self::normalizePath($path);
451
-		if (!$path || $path[0] !== '/') {
452
-			$path = '/' . $path;
453
-		}
454
-		if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
455
-			return false;
456
-		}
457
-		return true;
458
-	}
459
-
460
-	/**
461
-	 * @param string $filename
462
-	 * @return bool
463
-	 */
464
-	public static function isFileBlacklisted($filename) {
465
-		$filename = self::normalizePath($filename);
466
-
467
-		if (self::$blacklist === null) {
468
-			self::$blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']);
469
-		}
470
-
471
-		$filename = strtolower(basename($filename));
472
-		return in_array($filename, self::$blacklist);
473
-	}
474
-
475
-	/**
476
-	 * check if the directory should be ignored when scanning
477
-	 * NOTE: the special directories . and .. would cause never ending recursion
478
-	 *
479
-	 * @param string $dir
480
-	 * @return boolean
481
-	 */
482
-	public static function isIgnoredDir($dir) {
483
-		if ($dir === '.' || $dir === '..') {
484
-			return true;
485
-		}
486
-		return false;
487
-	}
488
-
489
-	/**
490
-	 * following functions are equivalent to their php builtin equivalents for arguments/return values.
491
-	 */
492
-	public static function mkdir($path) {
493
-		return self::$defaultInstance->mkdir($path);
494
-	}
495
-
496
-	public static function rmdir($path) {
497
-		return self::$defaultInstance->rmdir($path);
498
-	}
499
-
500
-	public static function is_dir($path) {
501
-		return self::$defaultInstance->is_dir($path);
502
-	}
503
-
504
-	public static function is_file($path) {
505
-		return self::$defaultInstance->is_file($path);
506
-	}
507
-
508
-	public static function stat($path) {
509
-		return self::$defaultInstance->stat($path);
510
-	}
511
-
512
-	public static function filetype($path) {
513
-		return self::$defaultInstance->filetype($path);
514
-	}
515
-
516
-	public static function filesize($path) {
517
-		return self::$defaultInstance->filesize($path);
518
-	}
519
-
520
-	public static function readfile($path) {
521
-		return self::$defaultInstance->readfile($path);
522
-	}
523
-
524
-	public static function isCreatable($path) {
525
-		return self::$defaultInstance->isCreatable($path);
526
-	}
527
-
528
-	public static function isReadable($path) {
529
-		return self::$defaultInstance->isReadable($path);
530
-	}
531
-
532
-	public static function isUpdatable($path) {
533
-		return self::$defaultInstance->isUpdatable($path);
534
-	}
535
-
536
-	public static function isDeletable($path) {
537
-		return self::$defaultInstance->isDeletable($path);
538
-	}
539
-
540
-	public static function isSharable($path) {
541
-		return self::$defaultInstance->isSharable($path);
542
-	}
543
-
544
-	public static function file_exists($path) {
545
-		return self::$defaultInstance->file_exists($path);
546
-	}
547
-
548
-	public static function filemtime($path) {
549
-		return self::$defaultInstance->filemtime($path);
550
-	}
551
-
552
-	public static function touch($path, $mtime = null) {
553
-		return self::$defaultInstance->touch($path, $mtime);
554
-	}
555
-
556
-	/**
557
-	 * @return string
558
-	 */
559
-	public static function file_get_contents($path) {
560
-		return self::$defaultInstance->file_get_contents($path);
561
-	}
562
-
563
-	public static function file_put_contents($path, $data) {
564
-		return self::$defaultInstance->file_put_contents($path, $data);
565
-	}
566
-
567
-	public static function unlink($path) {
568
-		return self::$defaultInstance->unlink($path);
569
-	}
570
-
571
-	public static function rename($source, $target) {
572
-		return self::$defaultInstance->rename($source, $target);
573
-	}
574
-
575
-	public static function copy($source, $target) {
576
-		return self::$defaultInstance->copy($source, $target);
577
-	}
578
-
579
-	public static function fopen($path, $mode) {
580
-		return self::$defaultInstance->fopen($path, $mode);
581
-	}
582
-
583
-	/**
584
-	 * @param string $path
585
-	 * @throws \OCP\Files\InvalidPathException
586
-	 */
587
-	public static function toTmpFile($path): string|false {
588
-		return self::$defaultInstance->toTmpFile($path);
589
-	}
590
-
591
-	public static function fromTmpFile($tmpFile, $path) {
592
-		return self::$defaultInstance->fromTmpFile($tmpFile, $path);
593
-	}
594
-
595
-	public static function getMimeType($path) {
596
-		return self::$defaultInstance->getMimeType($path);
597
-	}
598
-
599
-	public static function hash($type, $path, $raw = false) {
600
-		return self::$defaultInstance->hash($type, $path, $raw);
601
-	}
602
-
603
-	public static function free_space($path = '/') {
604
-		return self::$defaultInstance->free_space($path);
605
-	}
606
-
607
-	public static function search($query) {
608
-		return self::$defaultInstance->search($query);
609
-	}
610
-
611
-	/**
612
-	 * @param string $query
613
-	 */
614
-	public static function searchByMime($query) {
615
-		return self::$defaultInstance->searchByMime($query);
616
-	}
617
-
618
-	/**
619
-	 * @param string|int $tag name or tag id
620
-	 * @param string $userId owner of the tags
621
-	 * @return FileInfo[] array or file info
622
-	 */
623
-	public static function searchByTag($tag, $userId) {
624
-		return self::$defaultInstance->searchByTag($tag, $userId);
625
-	}
626
-
627
-	/**
628
-	 * check if a file or folder has been updated since $time
629
-	 *
630
-	 * @param string $path
631
-	 * @param int $time
632
-	 * @return bool
633
-	 */
634
-	public static function hasUpdated($path, $time) {
635
-		return self::$defaultInstance->hasUpdated($path, $time);
636
-	}
637
-
638
-	/**
639
-	 * Fix common problems with a file path
640
-	 *
641
-	 * @param string $path
642
-	 * @param bool $stripTrailingSlash whether to strip the trailing slash
643
-	 * @param bool $isAbsolutePath whether the given path is absolute
644
-	 * @param bool $keepUnicode true to disable unicode normalization
645
-	 * @psalm-taint-escape file
646
-	 * @return string
647
-	 */
648
-	public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
649
-		/**
650
-		 * FIXME: This is a workaround for existing classes and files which call
651
-		 *        this function with another type than a valid string. This
652
-		 *        conversion should get removed as soon as all existing
653
-		 *        function calls have been fixed.
654
-		 */
655
-		$path = (string)$path;
656
-
657
-		if ($path === '') {
658
-			return '/';
659
-		}
660
-
661
-		if (is_null(self::$normalizedPathCache)) {
662
-			self::$normalizedPathCache = new CappedMemoryCache(2048);
663
-		}
664
-
665
-		$cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
666
-
667
-		if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
668
-			return self::$normalizedPathCache[$cacheKey];
669
-		}
670
-
671
-		//normalize unicode if possible
672
-		if (!$keepUnicode) {
673
-			$path = \OC_Util::normalizeUnicode($path);
674
-		}
675
-
676
-		//add leading slash, if it is already there we strip it anyway
677
-		$path = '/' . $path;
678
-
679
-		$patterns = [
680
-			'#\\\\#s',       // no windows style '\\' slashes
681
-			'#/\.(/\.)*/#s', // remove '/./'
682
-			'#\//+#s',       // remove sequence of slashes
683
-			'#/\.$#s',       // remove trailing '/.'
684
-		];
685
-
686
-		do {
687
-			$count = 0;
688
-			$path = preg_replace($patterns, '/', $path, -1, $count);
689
-		} while ($count > 0);
690
-
691
-		//remove trailing slash
692
-		if ($stripTrailingSlash && strlen($path) > 1) {
693
-			$path = rtrim($path, '/');
694
-		}
695
-
696
-		self::$normalizedPathCache[$cacheKey] = $path;
697
-
698
-		return $path;
699
-	}
700
-
701
-	/**
702
-	 * get the filesystem info
703
-	 *
704
-	 * @param string $path
705
-	 * @param bool|string $includeMountPoints whether to add mountpoint sizes,
706
-	 * defaults to true
707
-	 * @return \OC\Files\FileInfo|false False if file does not exist
708
-	 */
709
-	public static function getFileInfo($path, $includeMountPoints = true) {
710
-		return self::getView()->getFileInfo($path, $includeMountPoints);
711
-	}
712
-
713
-	/**
714
-	 * change file metadata
715
-	 *
716
-	 * @param string $path
717
-	 * @param array $data
718
-	 * @return int
719
-	 *
720
-	 * returns the fileid of the updated file
721
-	 */
722
-	public static function putFileInfo($path, $data) {
723
-		return self::$defaultInstance->putFileInfo($path, $data);
724
-	}
725
-
726
-	/**
727
-	 * get the content of a directory
728
-	 *
729
-	 * @param string $directory path under datadirectory
730
-	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
731
-	 * @return \OC\Files\FileInfo[]
732
-	 */
733
-	public static function getDirectoryContent($directory, $mimetype_filter = '') {
734
-		return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
735
-	}
736
-
737
-	/**
738
-	 * Get the path of a file by id
739
-	 *
740
-	 * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
741
-	 *
742
-	 * @param int $id
743
-	 * @throws NotFoundException
744
-	 * @return string
745
-	 */
746
-	public static function getPath($id) {
747
-		return self::$defaultInstance->getPath($id);
748
-	}
749
-
750
-	/**
751
-	 * Get the owner for a file or folder
752
-	 *
753
-	 * @param string $path
754
-	 * @return string
755
-	 */
756
-	public static function getOwner($path) {
757
-		return self::$defaultInstance->getOwner($path);
758
-	}
759
-
760
-	/**
761
-	 * get the ETag for a file or folder
762
-	 */
763
-	public static function getETag(string $path): string|false {
764
-		return self::$defaultInstance->getETag($path);
765
-	}
53
+    private static ?Mount\Manager $mounts = null;
54
+
55
+    public static bool $loaded = false;
56
+
57
+    private static ?View $defaultInstance = null;
58
+
59
+    private static ?CappedMemoryCache $normalizedPathCache = null;
60
+
61
+    /** @var string[]|null */
62
+    private static ?array $blacklist = null;
63
+
64
+    /**
65
+     * classname which used for hooks handling
66
+     * used as signalclass in OC_Hooks::emit()
67
+     */
68
+    public const CLASSNAME = 'OC_Filesystem';
69
+
70
+    /**
71
+     * signalname emitted before file renaming
72
+     *
73
+     * @param string $oldpath
74
+     * @param string $newpath
75
+     */
76
+    public const signal_rename = 'rename';
77
+
78
+    /**
79
+     * signal emitted after file renaming
80
+     *
81
+     * @param string $oldpath
82
+     * @param string $newpath
83
+     */
84
+    public const signal_post_rename = 'post_rename';
85
+
86
+    /**
87
+     * signal emitted before file/dir creation
88
+     *
89
+     * @param string $path
90
+     * @param bool $run changing this flag to false in hook handler will cancel event
91
+     */
92
+    public const signal_create = 'create';
93
+
94
+    /**
95
+     * signal emitted after file/dir creation
96
+     *
97
+     * @param string $path
98
+     * @param bool $run changing this flag to false in hook handler will cancel event
99
+     */
100
+    public const signal_post_create = 'post_create';
101
+
102
+    /**
103
+     * signal emits before file/dir copy
104
+     *
105
+     * @param string $oldpath
106
+     * @param string $newpath
107
+     * @param bool $run changing this flag to false in hook handler will cancel event
108
+     */
109
+    public const signal_copy = 'copy';
110
+
111
+    /**
112
+     * signal emits after file/dir copy
113
+     *
114
+     * @param string $oldpath
115
+     * @param string $newpath
116
+     */
117
+    public const signal_post_copy = 'post_copy';
118
+
119
+    /**
120
+     * signal emits before file/dir save
121
+     *
122
+     * @param string $path
123
+     * @param bool $run changing this flag to false in hook handler will cancel event
124
+     */
125
+    public const signal_write = 'write';
126
+
127
+    /**
128
+     * signal emits after file/dir save
129
+     *
130
+     * @param string $path
131
+     */
132
+    public const signal_post_write = 'post_write';
133
+
134
+    /**
135
+     * signal emitted before file/dir update
136
+     *
137
+     * @param string $path
138
+     * @param bool $run changing this flag to false in hook handler will cancel event
139
+     */
140
+    public const signal_update = 'update';
141
+
142
+    /**
143
+     * signal emitted after file/dir update
144
+     *
145
+     * @param string $path
146
+     * @param bool $run changing this flag to false in hook handler will cancel event
147
+     */
148
+    public const signal_post_update = 'post_update';
149
+
150
+    /**
151
+     * signal emits when reading file/dir
152
+     *
153
+     * @param string $path
154
+     */
155
+    public const signal_read = 'read';
156
+
157
+    /**
158
+     * signal emits when removing file/dir
159
+     *
160
+     * @param string $path
161
+     */
162
+    public const signal_delete = 'delete';
163
+
164
+    /**
165
+     * parameters definitions for signals
166
+     */
167
+    public const signal_param_path = 'path';
168
+    public const signal_param_oldpath = 'oldpath';
169
+    public const signal_param_newpath = 'newpath';
170
+
171
+    /**
172
+     * run - changing this flag to false in hook handler will cancel event
173
+     */
174
+    public const signal_param_run = 'run';
175
+
176
+    public const signal_create_mount = 'create_mount';
177
+    public const signal_delete_mount = 'delete_mount';
178
+    public const signal_param_mount_type = 'mounttype';
179
+    public const signal_param_users = 'users';
180
+
181
+    private static ?\OC\Files\Storage\StorageFactory $loader = null;
182
+
183
+    private static bool $logWarningWhenAddingStorageWrapper = true;
184
+
185
+    /**
186
+     * @param bool $shouldLog
187
+     * @return bool previous value
188
+     * @internal
189
+     */
190
+    public static function logWarningWhenAddingStorageWrapper(bool $shouldLog): bool {
191
+        $previousValue = self::$logWarningWhenAddingStorageWrapper;
192
+        self::$logWarningWhenAddingStorageWrapper = $shouldLog;
193
+        return $previousValue;
194
+    }
195
+
196
+    /**
197
+     * @param string $wrapperName
198
+     * @param callable $wrapper
199
+     * @param int $priority
200
+     */
201
+    public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) {
202
+        if (self::$logWarningWhenAddingStorageWrapper) {
203
+            \OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [
204
+                'wrapper' => $wrapperName,
205
+                'app' => 'filesystem',
206
+            ]);
207
+        }
208
+
209
+        $mounts = self::getMountManager()->getAll();
210
+        if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
211
+            // do not re-wrap if storage with this name already existed
212
+            return;
213
+        }
214
+    }
215
+
216
+    /**
217
+     * Returns the storage factory
218
+     *
219
+     * @return IStorageFactory
220
+     */
221
+    public static function getLoader() {
222
+        if (!self::$loader) {
223
+            self::$loader = \OC::$server->get(IStorageFactory::class);
224
+        }
225
+        return self::$loader;
226
+    }
227
+
228
+    /**
229
+     * Returns the mount manager
230
+     */
231
+    public static function getMountManager(): Mount\Manager {
232
+        self::initMountManager();
233
+        assert(self::$mounts !== null);
234
+        return self::$mounts;
235
+    }
236
+
237
+    /**
238
+     * get the mountpoint of the storage object for a path
239
+     * ( note: because a storage is not always mounted inside the fakeroot, the
240
+     * returned mountpoint is relative to the absolute root of the filesystem
241
+     * and doesn't take the chroot into account )
242
+     *
243
+     * @param string $path
244
+     * @return string
245
+     */
246
+    public static function getMountPoint($path) {
247
+        if (!self::$mounts) {
248
+            \OC_Util::setupFS();
249
+        }
250
+        $mount = self::$mounts->find($path);
251
+        return $mount->getMountPoint();
252
+    }
253
+
254
+    /**
255
+     * get a list of all mount points in a directory
256
+     *
257
+     * @param string $path
258
+     * @return string[]
259
+     */
260
+    public static function getMountPoints($path) {
261
+        if (!self::$mounts) {
262
+            \OC_Util::setupFS();
263
+        }
264
+        $result = [];
265
+        $mounts = self::$mounts->findIn($path);
266
+        foreach ($mounts as $mount) {
267
+            $result[] = $mount->getMountPoint();
268
+        }
269
+        return $result;
270
+    }
271
+
272
+    /**
273
+     * get the storage mounted at $mountPoint
274
+     *
275
+     * @param string $mountPoint
276
+     * @return \OC\Files\Storage\Storage|null
277
+     */
278
+    public static function getStorage($mountPoint) {
279
+        $mount = self::getMountManager()->find($mountPoint);
280
+        return $mount->getStorage();
281
+    }
282
+
283
+    /**
284
+     * @param string $id
285
+     * @return Mount\MountPoint[]
286
+     */
287
+    public static function getMountByStorageId($id) {
288
+        return self::getMountManager()->findByStorageId($id);
289
+    }
290
+
291
+    /**
292
+     * @param int $id
293
+     * @return Mount\MountPoint[]
294
+     */
295
+    public static function getMountByNumericId($id) {
296
+        return self::getMountManager()->findByNumericId($id);
297
+    }
298
+
299
+    /**
300
+     * resolve a path to a storage and internal path
301
+     *
302
+     * @param string $path
303
+     * @return array{?\OCP\Files\Storage\IStorage, string} an array consisting of the storage and the internal path
304
+     */
305
+    public static function resolvePath($path): array {
306
+        $mount = self::getMountManager()->find($path);
307
+        return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')];
308
+    }
309
+
310
+    public static function init(string|IUser|null $user, string $root): bool {
311
+        if (self::$defaultInstance) {
312
+            return false;
313
+        }
314
+        self::initInternal($root);
315
+
316
+        //load custom mount config
317
+        self::initMountPoints($user);
318
+
319
+        return true;
320
+    }
321
+
322
+    public static function initInternal(string $root): bool {
323
+        if (self::$defaultInstance) {
324
+            return false;
325
+        }
326
+        self::getLoader();
327
+        self::$defaultInstance = new View($root);
328
+        /** @var IEventDispatcher $eventDispatcher */
329
+        $eventDispatcher = \OC::$server->get(IEventDispatcher::class);
330
+        $eventDispatcher->addListener(FilesystemTornDownEvent::class, function () {
331
+            self::$defaultInstance = null;
332
+            self::$loaded = false;
333
+        });
334
+
335
+        self::initMountManager();
336
+
337
+        self::$loaded = true;
338
+
339
+        return true;
340
+    }
341
+
342
+    public static function initMountManager(): void {
343
+        if (!self::$mounts) {
344
+            self::$mounts = \OC::$server->get(IMountManager::class);
345
+        }
346
+    }
347
+
348
+    /**
349
+     * Initialize system and personal mount points for a user
350
+     *
351
+     * @throws \OC\User\NoUserException if the user is not available
352
+     */
353
+    public static function initMountPoints(string|IUser|null $user = ''): void {
354
+        /** @var IUserManager $userManager */
355
+        $userManager = \OC::$server->get(IUserManager::class);
356
+
357
+        $userObject = ($user instanceof IUser) ? $user : $userManager->get($user);
358
+        if ($userObject) {
359
+            /** @var SetupManager $setupManager */
360
+            $setupManager = \OC::$server->get(SetupManager::class);
361
+            $setupManager->setupForUser($userObject);
362
+        } else {
363
+            throw new NoUserException();
364
+        }
365
+    }
366
+
367
+    /**
368
+     * Get the default filesystem view
369
+     */
370
+    public static function getView(): ?View {
371
+        if (!self::$defaultInstance) {
372
+            /** @var IUserSession $session */
373
+            $session = \OC::$server->get(IUserSession::class);
374
+            $user = $session->getUser();
375
+            if ($user) {
376
+                $userDir = '/' . $user->getUID() . '/files';
377
+                self::initInternal($userDir);
378
+            }
379
+        }
380
+        return self::$defaultInstance;
381
+    }
382
+
383
+    /**
384
+     * tear down the filesystem, removing all storage providers
385
+     */
386
+    public static function tearDown() {
387
+        \OC_Util::tearDownFS();
388
+    }
389
+
390
+    /**
391
+     * get the relative path of the root data directory for the current user
392
+     *
393
+     * @return ?string
394
+     *
395
+     * Returns path like /admin/files
396
+     */
397
+    public static function getRoot() {
398
+        if (!self::$defaultInstance) {
399
+            return null;
400
+        }
401
+        return self::$defaultInstance->getRoot();
402
+    }
403
+
404
+    /**
405
+     * mount an \OC\Files\Storage\Storage in our virtual filesystem
406
+     *
407
+     * @param \OC\Files\Storage\Storage|string $class
408
+     * @param array $arguments
409
+     * @param string $mountpoint
410
+     */
411
+    public static function mount($class, $arguments, $mountpoint) {
412
+        if (!self::$mounts) {
413
+            \OC_Util::setupFS();
414
+        }
415
+        $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader());
416
+        self::$mounts->addMount($mount);
417
+    }
418
+
419
+    /**
420
+     * return the path to a local version of the file
421
+     * we need this because we can't know if a file is stored local or not from
422
+     * outside the filestorage and for some purposes a local file is needed
423
+     */
424
+    public static function getLocalFile(string $path): string|false {
425
+        return self::$defaultInstance->getLocalFile($path);
426
+    }
427
+
428
+    /**
429
+     * return path to file which reflects one visible in browser
430
+     *
431
+     * @param string $path
432
+     * @return string
433
+     */
434
+    public static function getLocalPath($path) {
435
+        $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
436
+        $newpath = $path;
437
+        if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
438
+            $newpath = substr($path, strlen($datadir));
439
+        }
440
+        return $newpath;
441
+    }
442
+
443
+    /**
444
+     * check if the requested path is valid
445
+     *
446
+     * @param string $path
447
+     * @return bool
448
+     */
449
+    public static function isValidPath($path) {
450
+        $path = self::normalizePath($path);
451
+        if (!$path || $path[0] !== '/') {
452
+            $path = '/' . $path;
453
+        }
454
+        if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
455
+            return false;
456
+        }
457
+        return true;
458
+    }
459
+
460
+    /**
461
+     * @param string $filename
462
+     * @return bool
463
+     */
464
+    public static function isFileBlacklisted($filename) {
465
+        $filename = self::normalizePath($filename);
466
+
467
+        if (self::$blacklist === null) {
468
+            self::$blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']);
469
+        }
470
+
471
+        $filename = strtolower(basename($filename));
472
+        return in_array($filename, self::$blacklist);
473
+    }
474
+
475
+    /**
476
+     * check if the directory should be ignored when scanning
477
+     * NOTE: the special directories . and .. would cause never ending recursion
478
+     *
479
+     * @param string $dir
480
+     * @return boolean
481
+     */
482
+    public static function isIgnoredDir($dir) {
483
+        if ($dir === '.' || $dir === '..') {
484
+            return true;
485
+        }
486
+        return false;
487
+    }
488
+
489
+    /**
490
+     * following functions are equivalent to their php builtin equivalents for arguments/return values.
491
+     */
492
+    public static function mkdir($path) {
493
+        return self::$defaultInstance->mkdir($path);
494
+    }
495
+
496
+    public static function rmdir($path) {
497
+        return self::$defaultInstance->rmdir($path);
498
+    }
499
+
500
+    public static function is_dir($path) {
501
+        return self::$defaultInstance->is_dir($path);
502
+    }
503
+
504
+    public static function is_file($path) {
505
+        return self::$defaultInstance->is_file($path);
506
+    }
507
+
508
+    public static function stat($path) {
509
+        return self::$defaultInstance->stat($path);
510
+    }
511
+
512
+    public static function filetype($path) {
513
+        return self::$defaultInstance->filetype($path);
514
+    }
515
+
516
+    public static function filesize($path) {
517
+        return self::$defaultInstance->filesize($path);
518
+    }
519
+
520
+    public static function readfile($path) {
521
+        return self::$defaultInstance->readfile($path);
522
+    }
523
+
524
+    public static function isCreatable($path) {
525
+        return self::$defaultInstance->isCreatable($path);
526
+    }
527
+
528
+    public static function isReadable($path) {
529
+        return self::$defaultInstance->isReadable($path);
530
+    }
531
+
532
+    public static function isUpdatable($path) {
533
+        return self::$defaultInstance->isUpdatable($path);
534
+    }
535
+
536
+    public static function isDeletable($path) {
537
+        return self::$defaultInstance->isDeletable($path);
538
+    }
539
+
540
+    public static function isSharable($path) {
541
+        return self::$defaultInstance->isSharable($path);
542
+    }
543
+
544
+    public static function file_exists($path) {
545
+        return self::$defaultInstance->file_exists($path);
546
+    }
547
+
548
+    public static function filemtime($path) {
549
+        return self::$defaultInstance->filemtime($path);
550
+    }
551
+
552
+    public static function touch($path, $mtime = null) {
553
+        return self::$defaultInstance->touch($path, $mtime);
554
+    }
555
+
556
+    /**
557
+     * @return string
558
+     */
559
+    public static function file_get_contents($path) {
560
+        return self::$defaultInstance->file_get_contents($path);
561
+    }
562
+
563
+    public static function file_put_contents($path, $data) {
564
+        return self::$defaultInstance->file_put_contents($path, $data);
565
+    }
566
+
567
+    public static function unlink($path) {
568
+        return self::$defaultInstance->unlink($path);
569
+    }
570
+
571
+    public static function rename($source, $target) {
572
+        return self::$defaultInstance->rename($source, $target);
573
+    }
574
+
575
+    public static function copy($source, $target) {
576
+        return self::$defaultInstance->copy($source, $target);
577
+    }
578
+
579
+    public static function fopen($path, $mode) {
580
+        return self::$defaultInstance->fopen($path, $mode);
581
+    }
582
+
583
+    /**
584
+     * @param string $path
585
+     * @throws \OCP\Files\InvalidPathException
586
+     */
587
+    public static function toTmpFile($path): string|false {
588
+        return self::$defaultInstance->toTmpFile($path);
589
+    }
590
+
591
+    public static function fromTmpFile($tmpFile, $path) {
592
+        return self::$defaultInstance->fromTmpFile($tmpFile, $path);
593
+    }
594
+
595
+    public static function getMimeType($path) {
596
+        return self::$defaultInstance->getMimeType($path);
597
+    }
598
+
599
+    public static function hash($type, $path, $raw = false) {
600
+        return self::$defaultInstance->hash($type, $path, $raw);
601
+    }
602
+
603
+    public static function free_space($path = '/') {
604
+        return self::$defaultInstance->free_space($path);
605
+    }
606
+
607
+    public static function search($query) {
608
+        return self::$defaultInstance->search($query);
609
+    }
610
+
611
+    /**
612
+     * @param string $query
613
+     */
614
+    public static function searchByMime($query) {
615
+        return self::$defaultInstance->searchByMime($query);
616
+    }
617
+
618
+    /**
619
+     * @param string|int $tag name or tag id
620
+     * @param string $userId owner of the tags
621
+     * @return FileInfo[] array or file info
622
+     */
623
+    public static function searchByTag($tag, $userId) {
624
+        return self::$defaultInstance->searchByTag($tag, $userId);
625
+    }
626
+
627
+    /**
628
+     * check if a file or folder has been updated since $time
629
+     *
630
+     * @param string $path
631
+     * @param int $time
632
+     * @return bool
633
+     */
634
+    public static function hasUpdated($path, $time) {
635
+        return self::$defaultInstance->hasUpdated($path, $time);
636
+    }
637
+
638
+    /**
639
+     * Fix common problems with a file path
640
+     *
641
+     * @param string $path
642
+     * @param bool $stripTrailingSlash whether to strip the trailing slash
643
+     * @param bool $isAbsolutePath whether the given path is absolute
644
+     * @param bool $keepUnicode true to disable unicode normalization
645
+     * @psalm-taint-escape file
646
+     * @return string
647
+     */
648
+    public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
649
+        /**
650
+         * FIXME: This is a workaround for existing classes and files which call
651
+         *        this function with another type than a valid string. This
652
+         *        conversion should get removed as soon as all existing
653
+         *        function calls have been fixed.
654
+         */
655
+        $path = (string)$path;
656
+
657
+        if ($path === '') {
658
+            return '/';
659
+        }
660
+
661
+        if (is_null(self::$normalizedPathCache)) {
662
+            self::$normalizedPathCache = new CappedMemoryCache(2048);
663
+        }
664
+
665
+        $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
666
+
667
+        if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
668
+            return self::$normalizedPathCache[$cacheKey];
669
+        }
670
+
671
+        //normalize unicode if possible
672
+        if (!$keepUnicode) {
673
+            $path = \OC_Util::normalizeUnicode($path);
674
+        }
675
+
676
+        //add leading slash, if it is already there we strip it anyway
677
+        $path = '/' . $path;
678
+
679
+        $patterns = [
680
+            '#\\\\#s',       // no windows style '\\' slashes
681
+            '#/\.(/\.)*/#s', // remove '/./'
682
+            '#\//+#s',       // remove sequence of slashes
683
+            '#/\.$#s',       // remove trailing '/.'
684
+        ];
685
+
686
+        do {
687
+            $count = 0;
688
+            $path = preg_replace($patterns, '/', $path, -1, $count);
689
+        } while ($count > 0);
690
+
691
+        //remove trailing slash
692
+        if ($stripTrailingSlash && strlen($path) > 1) {
693
+            $path = rtrim($path, '/');
694
+        }
695
+
696
+        self::$normalizedPathCache[$cacheKey] = $path;
697
+
698
+        return $path;
699
+    }
700
+
701
+    /**
702
+     * get the filesystem info
703
+     *
704
+     * @param string $path
705
+     * @param bool|string $includeMountPoints whether to add mountpoint sizes,
706
+     * defaults to true
707
+     * @return \OC\Files\FileInfo|false False if file does not exist
708
+     */
709
+    public static function getFileInfo($path, $includeMountPoints = true) {
710
+        return self::getView()->getFileInfo($path, $includeMountPoints);
711
+    }
712
+
713
+    /**
714
+     * change file metadata
715
+     *
716
+     * @param string $path
717
+     * @param array $data
718
+     * @return int
719
+     *
720
+     * returns the fileid of the updated file
721
+     */
722
+    public static function putFileInfo($path, $data) {
723
+        return self::$defaultInstance->putFileInfo($path, $data);
724
+    }
725
+
726
+    /**
727
+     * get the content of a directory
728
+     *
729
+     * @param string $directory path under datadirectory
730
+     * @param string $mimetype_filter limit returned content to this mimetype or mimepart
731
+     * @return \OC\Files\FileInfo[]
732
+     */
733
+    public static function getDirectoryContent($directory, $mimetype_filter = '') {
734
+        return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
735
+    }
736
+
737
+    /**
738
+     * Get the path of a file by id
739
+     *
740
+     * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
741
+     *
742
+     * @param int $id
743
+     * @throws NotFoundException
744
+     * @return string
745
+     */
746
+    public static function getPath($id) {
747
+        return self::$defaultInstance->getPath($id);
748
+    }
749
+
750
+    /**
751
+     * Get the owner for a file or folder
752
+     *
753
+     * @param string $path
754
+     * @return string
755
+     */
756
+    public static function getOwner($path) {
757
+        return self::$defaultInstance->getOwner($path);
758
+    }
759
+
760
+    /**
761
+     * get the ETag for a file or folder
762
+     */
763
+    public static function getETag(string $path): string|false {
764
+        return self::$defaultInstance->getETag($path);
765
+    }
766 766
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_App.php 1 patch
Indentation   +967 added lines, -967 removed lines patch added patch discarded remove patch
@@ -73,971 +73,971 @@
 block discarded – undo
73 73
  * upgrading and removing apps.
74 74
  */
75 75
 class OC_App {
76
-	private static $adminForms = [];
77
-	private static $personalForms = [];
78
-	private static $altLogin = [];
79
-	private static $alreadyRegistered = [];
80
-	public const supportedApp = 300;
81
-	public const officialApp = 200;
82
-
83
-	/**
84
-	 * clean the appId
85
-	 *
86
-	 * @psalm-taint-escape file
87
-	 * @psalm-taint-escape include
88
-	 * @psalm-taint-escape html
89
-	 * @psalm-taint-escape has_quotes
90
-	 *
91
-	 * @param string $app AppId that needs to be cleaned
92
-	 * @return string
93
-	 */
94
-	public static function cleanAppId(string $app): string {
95
-		return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
96
-	}
97
-
98
-	/**
99
-	 * Check if an app is loaded
100
-	 *
101
-	 * @param string $app
102
-	 * @return bool
103
-	 * @deprecated 27.0.0 use IAppManager::isAppLoaded
104
-	 */
105
-	public static function isAppLoaded(string $app): bool {
106
-		return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
107
-	}
108
-
109
-	/**
110
-	 * loads all apps
111
-	 *
112
-	 * @param string[] $types
113
-	 * @return bool
114
-	 *
115
-	 * This function walks through the ownCloud directory and loads all apps
116
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
117
-	 * exists.
118
-	 *
119
-	 * if $types is set to non-empty array, only apps of those types will be loaded
120
-	 */
121
-	public static function loadApps(array $types = []): bool {
122
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
123
-			// This should be done before calling this method so that appmanager can be used
124
-			return false;
125
-		}
126
-		return \OC::$server->get(IAppManager::class)->loadApps($types);
127
-	}
128
-
129
-	/**
130
-	 * load a single app
131
-	 *
132
-	 * @param string $app
133
-	 * @throws Exception
134
-	 * @deprecated 27.0.0 use IAppManager::loadApp
135
-	 */
136
-	public static function loadApp(string $app): void {
137
-		\OC::$server->get(IAppManager::class)->loadApp($app);
138
-	}
139
-
140
-	/**
141
-	 * @internal
142
-	 * @param string $app
143
-	 * @param string $path
144
-	 * @param bool $force
145
-	 */
146
-	public static function registerAutoloading(string $app, string $path, bool $force = false) {
147
-		$key = $app . '-' . $path;
148
-		if (!$force && isset(self::$alreadyRegistered[$key])) {
149
-			return;
150
-		}
151
-
152
-		self::$alreadyRegistered[$key] = true;
153
-
154
-		// Register on PSR-4 composer autoloader
155
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
156
-		\OC::$server->registerNamespace($app, $appNamespace);
157
-
158
-		if (file_exists($path . '/composer/autoload.php')) {
159
-			require_once $path . '/composer/autoload.php';
160
-		} else {
161
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
162
-		}
163
-
164
-		// Register Test namespace only when testing
165
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
166
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
167
-		}
168
-	}
169
-
170
-	/**
171
-	 * check if an app is of a specific type
172
-	 *
173
-	 * @param string $app
174
-	 * @param array $types
175
-	 * @return bool
176
-	 * @deprecated 27.0.0 use IAppManager::isType
177
-	 */
178
-	public static function isType(string $app, array $types): bool {
179
-		return \OC::$server->get(IAppManager::class)->isType($app, $types);
180
-	}
181
-
182
-	/**
183
-	 * read app types from info.xml and cache them in the database
184
-	 */
185
-	public static function setAppTypes(string $app) {
186
-		$appManager = \OC::$server->getAppManager();
187
-		$appData = $appManager->getAppInfo($app);
188
-		if (!is_array($appData)) {
189
-			return;
190
-		}
191
-
192
-		if (isset($appData['types'])) {
193
-			$appTypes = implode(',', $appData['types']);
194
-		} else {
195
-			$appTypes = '';
196
-			$appData['types'] = [];
197
-		}
198
-
199
-		$config = \OC::$server->getConfig();
200
-		$config->setAppValue($app, 'types', $appTypes);
201
-
202
-		if ($appManager->hasProtectedAppType($appData['types'])) {
203
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
204
-			if ($enabled !== 'yes' && $enabled !== 'no') {
205
-				$config->setAppValue($app, 'enabled', 'yes');
206
-			}
207
-		}
208
-	}
209
-
210
-	/**
211
-	 * Returns apps enabled for the current user.
212
-	 *
213
-	 * @param bool $forceRefresh whether to refresh the cache
214
-	 * @param bool $all whether to return apps for all users, not only the
215
-	 * currently logged in one
216
-	 * @return string[]
217
-	 */
218
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
219
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
220
-			return [];
221
-		}
222
-		// in incognito mode or when logged out, $user will be false,
223
-		// which is also the case during an upgrade
224
-		$appManager = \OC::$server->getAppManager();
225
-		if ($all) {
226
-			$user = null;
227
-		} else {
228
-			$user = \OC::$server->getUserSession()->getUser();
229
-		}
230
-
231
-		if (is_null($user)) {
232
-			$apps = $appManager->getInstalledApps();
233
-		} else {
234
-			$apps = $appManager->getEnabledAppsForUser($user);
235
-		}
236
-		$apps = array_filter($apps, function ($app) {
237
-			return $app !== 'files';//we add this manually
238
-		});
239
-		sort($apps);
240
-		array_unshift($apps, 'files');
241
-		return $apps;
242
-	}
243
-
244
-	/**
245
-	 * checks whether or not an app is enabled
246
-	 *
247
-	 * @param string $app app
248
-	 * @return bool
249
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
250
-	 *
251
-	 * This function checks whether or not an app is enabled.
252
-	 */
253
-	public static function isEnabled(string $app): bool {
254
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
255
-	}
256
-
257
-	/**
258
-	 * enables an app
259
-	 *
260
-	 * @param string $appId
261
-	 * @param array $groups (optional) when set, only these groups will have access to the app
262
-	 * @throws \Exception
263
-	 * @return void
264
-	 *
265
-	 * This function set an app as enabled in appconfig.
266
-	 */
267
-	public function enable(string $appId,
268
-						   array $groups = []) {
269
-		// Check if app is already downloaded
270
-		/** @var Installer $installer */
271
-		$installer = \OC::$server->query(Installer::class);
272
-		$isDownloaded = $installer->isDownloaded($appId);
273
-
274
-		if (!$isDownloaded) {
275
-			$installer->downloadApp($appId);
276
-		}
277
-
278
-		$installer->installApp($appId);
279
-
280
-		$appManager = \OC::$server->getAppManager();
281
-		if ($groups !== []) {
282
-			$groupManager = \OC::$server->getGroupManager();
283
-			$groupsList = [];
284
-			foreach ($groups as $group) {
285
-				$groupItem = $groupManager->get($group);
286
-				if ($groupItem instanceof \OCP\IGroup) {
287
-					$groupsList[] = $groupManager->get($group);
288
-				}
289
-			}
290
-			$appManager->enableAppForGroups($appId, $groupsList);
291
-		} else {
292
-			$appManager->enableApp($appId);
293
-		}
294
-	}
295
-
296
-	/**
297
-	 * Get the path where to install apps
298
-	 *
299
-	 * @return string|false
300
-	 */
301
-	public static function getInstallPath() {
302
-		foreach (OC::$APPSROOTS as $dir) {
303
-			if (isset($dir['writable']) && $dir['writable'] === true) {
304
-				return $dir['path'];
305
-			}
306
-		}
307
-
308
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
309
-		return null;
310
-	}
311
-
312
-
313
-	/**
314
-	 * search for an app in all app-directories
315
-	 *
316
-	 * @param string $appId
317
-	 * @param bool $ignoreCache ignore cache and rebuild it
318
-	 * @return false|string
319
-	 */
320
-	public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
321
-		$sanitizedAppId = self::cleanAppId($appId);
322
-		if ($sanitizedAppId !== $appId) {
323
-			return false;
324
-		}
325
-		static $app_dir = [];
326
-
327
-		if (isset($app_dir[$appId]) && !$ignoreCache) {
328
-			return $app_dir[$appId];
329
-		}
330
-
331
-		$possibleApps = [];
332
-		foreach (OC::$APPSROOTS as $dir) {
333
-			if (file_exists($dir['path'] . '/' . $appId)) {
334
-				$possibleApps[] = $dir;
335
-			}
336
-		}
337
-
338
-		if (empty($possibleApps)) {
339
-			return false;
340
-		} elseif (count($possibleApps) === 1) {
341
-			$dir = array_shift($possibleApps);
342
-			$app_dir[$appId] = $dir;
343
-			return $dir;
344
-		} else {
345
-			$versionToLoad = [];
346
-			foreach ($possibleApps as $possibleApp) {
347
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
348
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
349
-					$versionToLoad = [
350
-						'dir' => $possibleApp,
351
-						'version' => $version,
352
-					];
353
-				}
354
-			}
355
-			$app_dir[$appId] = $versionToLoad['dir'];
356
-			return $versionToLoad['dir'];
357
-			//TODO - write test
358
-		}
359
-	}
360
-
361
-	/**
362
-	 * Get the directory for the given app.
363
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
364
-	 *
365
-	 * @psalm-taint-specialize
366
-	 *
367
-	 * @param string $appId
368
-	 * @param bool $refreshAppPath should be set to true only during install/upgrade
369
-	 * @return string|false
370
-	 * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
371
-	 */
372
-	public static function getAppPath(string $appId, bool $refreshAppPath = false) {
373
-		if ($appId === null || trim($appId) === '') {
374
-			return false;
375
-		}
376
-
377
-		if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
378
-			return $dir['path'] . '/' . $appId;
379
-		}
380
-		return false;
381
-	}
382
-
383
-	/**
384
-	 * Get the path for the given app on the access
385
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
386
-	 *
387
-	 * @param string $appId
388
-	 * @return string|false
389
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
390
-	 */
391
-	public static function getAppWebPath(string $appId) {
392
-		if (($dir = self::findAppInDirectories($appId)) != false) {
393
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
394
-		}
395
-		return false;
396
-	}
397
-
398
-	/**
399
-	 * get the last version of the app from appinfo/info.xml
400
-	 *
401
-	 * @param string $appId
402
-	 * @param bool $useCache
403
-	 * @return string
404
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
405
-	 */
406
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
407
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
408
-	}
409
-
410
-	/**
411
-	 * get app's version based on it's path
412
-	 *
413
-	 * @param string $path
414
-	 * @return string
415
-	 */
416
-	public static function getAppVersionByPath(string $path): string {
417
-		$infoFile = $path . '/appinfo/info.xml';
418
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
419
-		return isset($appData['version']) ? $appData['version'] : '';
420
-	}
421
-
422
-
423
-	/**
424
-	 * Read all app metadata from the info.xml file
425
-	 *
426
-	 * @param string $appId id of the app or the path of the info.xml file
427
-	 * @param bool $path
428
-	 * @param string $lang
429
-	 * @return array|null
430
-	 * @note all data is read from info.xml, not just pre-defined fields
431
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
432
-	 */
433
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
434
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
435
-	}
436
-
437
-	/**
438
-	 * Returns the navigation
439
-	 *
440
-	 * @return array
441
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
442
-	 *
443
-	 * This function returns an array containing all entries added. The
444
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
445
-	 * given for each app the following keys exist:
446
-	 *   - active: boolean, signals if the user is on this navigation entry
447
-	 */
448
-	public static function getNavigation(): array {
449
-		return OC::$server->getNavigationManager()->getAll();
450
-	}
451
-
452
-	/**
453
-	 * Returns the Settings Navigation
454
-	 *
455
-	 * @return string[]
456
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
457
-	 *
458
-	 * This function returns an array containing all settings pages added. The
459
-	 * entries are sorted by the key 'order' ascending.
460
-	 */
461
-	public static function getSettingsNavigation(): array {
462
-		return OC::$server->getNavigationManager()->getAll('settings');
463
-	}
464
-
465
-	/**
466
-	 * get the id of loaded app
467
-	 *
468
-	 * @return string
469
-	 */
470
-	public static function getCurrentApp(): string {
471
-		if (\OC::$CLI) {
472
-			return '';
473
-		}
474
-
475
-		$request = \OC::$server->getRequest();
476
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
477
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
478
-		if (empty($topFolder)) {
479
-			try {
480
-				$path_info = $request->getPathInfo();
481
-			} catch (Exception $e) {
482
-				// Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
483
-				\OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
484
-				return '';
485
-			}
486
-			if ($path_info) {
487
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
488
-			}
489
-		}
490
-		if ($topFolder == 'apps') {
491
-			$length = strlen($topFolder);
492
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
493
-		} else {
494
-			return $topFolder;
495
-		}
496
-	}
497
-
498
-	/**
499
-	 * @param string $type
500
-	 * @return array
501
-	 */
502
-	public static function getForms(string $type): array {
503
-		$forms = [];
504
-		switch ($type) {
505
-			case 'admin':
506
-				$source = self::$adminForms;
507
-				break;
508
-			case 'personal':
509
-				$source = self::$personalForms;
510
-				break;
511
-			default:
512
-				return [];
513
-		}
514
-		foreach ($source as $form) {
515
-			$forms[] = include $form;
516
-		}
517
-		return $forms;
518
-	}
519
-
520
-	/**
521
-	 * @param array $entry
522
-	 * @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
523
-	 */
524
-	public static function registerLogIn(array $entry) {
525
-		\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');
526
-		self::$altLogin[] = $entry;
527
-	}
528
-
529
-	/**
530
-	 * @return array
531
-	 */
532
-	public static function getAlternativeLogIns(): array {
533
-		/** @var Coordinator $bootstrapCoordinator */
534
-		$bootstrapCoordinator = \OC::$server->query(Coordinator::class);
535
-
536
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
537
-			if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
538
-				\OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
539
-					'option' => $registration->getService(),
540
-					'interface' => IAlternativeLogin::class,
541
-					'app' => $registration->getAppId(),
542
-				]);
543
-				continue;
544
-			}
545
-
546
-			try {
547
-				/** @var IAlternativeLogin $provider */
548
-				$provider = \OC::$server->query($registration->getService());
549
-			} catch (QueryException $e) {
550
-				\OC::$server->getLogger()->logException($e, [
551
-					'message' => 'Alternative login option {option} can not be initialised.',
552
-					'option' => $registration->getService(),
553
-					'app' => $registration->getAppId(),
554
-				]);
555
-			}
556
-
557
-			try {
558
-				$provider->load();
559
-
560
-				self::$altLogin[] = [
561
-					'name' => $provider->getLabel(),
562
-					'href' => $provider->getLink(),
563
-					'class' => $provider->getClass(),
564
-				];
565
-			} catch (Throwable $e) {
566
-				\OC::$server->getLogger()->logException($e, [
567
-					'message' => 'Alternative login option {option} had an error while loading.',
568
-					'option' => $registration->getService(),
569
-					'app' => $registration->getAppId(),
570
-				]);
571
-			}
572
-		}
573
-
574
-		return self::$altLogin;
575
-	}
576
-
577
-	/**
578
-	 * get a list of all apps in the apps folder
579
-	 *
580
-	 * @return string[] an array of app names (string IDs)
581
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
582
-	 */
583
-	public static function getAllApps(): array {
584
-		$apps = [];
585
-
586
-		foreach (OC::$APPSROOTS as $apps_dir) {
587
-			if (!is_readable($apps_dir['path'])) {
588
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
589
-				continue;
590
-			}
591
-			$dh = opendir($apps_dir['path']);
592
-
593
-			if (is_resource($dh)) {
594
-				while (($file = readdir($dh)) !== false) {
595
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
596
-						$apps[] = $file;
597
-					}
598
-				}
599
-			}
600
-		}
601
-
602
-		$apps = array_unique($apps);
603
-
604
-		return $apps;
605
-	}
606
-
607
-	/**
608
-	 * List all supported apps
609
-	 *
610
-	 * @return array
611
-	 */
612
-	public function getSupportedApps(): array {
613
-		/** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
614
-		$subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
615
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
616
-		return $supportedApps;
617
-	}
618
-
619
-	/**
620
-	 * List all apps, this is used in apps.php
621
-	 *
622
-	 * @return array
623
-	 */
624
-	public function listAllApps(): array {
625
-		$installedApps = OC_App::getAllApps();
626
-
627
-		$appManager = \OC::$server->getAppManager();
628
-		//we don't want to show configuration for these
629
-		$blacklist = $appManager->getAlwaysEnabledApps();
630
-		$appList = [];
631
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
632
-		$urlGenerator = \OC::$server->getURLGenerator();
633
-		$supportedApps = $this->getSupportedApps();
634
-
635
-		foreach ($installedApps as $app) {
636
-			if (array_search($app, $blacklist) === false) {
637
-				$info = OC_App::getAppInfo($app, false, $langCode);
638
-				if (!is_array($info)) {
639
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
640
-					continue;
641
-				}
642
-
643
-				if (!isset($info['name'])) {
644
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
645
-					continue;
646
-				}
647
-
648
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
649
-				$info['groups'] = null;
650
-				if ($enabled === 'yes') {
651
-					$active = true;
652
-				} elseif ($enabled === 'no') {
653
-					$active = false;
654
-				} else {
655
-					$active = true;
656
-					$info['groups'] = $enabled;
657
-				}
658
-
659
-				$info['active'] = $active;
660
-
661
-				if ($appManager->isShipped($app)) {
662
-					$info['internal'] = true;
663
-					$info['level'] = self::officialApp;
664
-					$info['removable'] = false;
665
-				} else {
666
-					$info['internal'] = false;
667
-					$info['removable'] = true;
668
-				}
669
-
670
-				if (in_array($app, $supportedApps)) {
671
-					$info['level'] = self::supportedApp;
672
-				}
673
-
674
-				$appPath = self::getAppPath($app);
675
-				if ($appPath !== false) {
676
-					$appIcon = $appPath . '/img/' . $app . '.svg';
677
-					if (file_exists($appIcon)) {
678
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
679
-						$info['previewAsIcon'] = true;
680
-					} else {
681
-						$appIcon = $appPath . '/img/app.svg';
682
-						if (file_exists($appIcon)) {
683
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
684
-							$info['previewAsIcon'] = true;
685
-						}
686
-					}
687
-				}
688
-				// fix documentation
689
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
690
-					foreach ($info['documentation'] as $key => $url) {
691
-						// If it is not an absolute URL we assume it is a key
692
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
693
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
694
-							$url = $urlGenerator->linkToDocs($url);
695
-						}
696
-
697
-						$info['documentation'][$key] = $url;
698
-					}
699
-				}
700
-
701
-				$info['version'] = OC_App::getAppVersion($app);
702
-				$appList[] = $info;
703
-			}
704
-		}
705
-
706
-		return $appList;
707
-	}
708
-
709
-	public static function shouldUpgrade(string $app): bool {
710
-		$versions = self::getAppVersions();
711
-		$currentVersion = OC_App::getAppVersion($app);
712
-		if ($currentVersion && isset($versions[$app])) {
713
-			$installedVersion = $versions[$app];
714
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
715
-				return true;
716
-			}
717
-		}
718
-		return false;
719
-	}
720
-
721
-	/**
722
-	 * Adjust the number of version parts of $version1 to match
723
-	 * the number of version parts of $version2.
724
-	 *
725
-	 * @param string $version1 version to adjust
726
-	 * @param string $version2 version to take the number of parts from
727
-	 * @return string shortened $version1
728
-	 */
729
-	private static function adjustVersionParts(string $version1, string $version2): string {
730
-		$version1 = explode('.', $version1);
731
-		$version2 = explode('.', $version2);
732
-		// reduce $version1 to match the number of parts in $version2
733
-		while (count($version1) > count($version2)) {
734
-			array_pop($version1);
735
-		}
736
-		// if $version1 does not have enough parts, add some
737
-		while (count($version1) < count($version2)) {
738
-			$version1[] = '0';
739
-		}
740
-		return implode('.', $version1);
741
-	}
742
-
743
-	/**
744
-	 * Check whether the current ownCloud version matches the given
745
-	 * application's version requirements.
746
-	 *
747
-	 * The comparison is made based on the number of parts that the
748
-	 * app info version has. For example for ownCloud 6.0.3 if the
749
-	 * app info version is expecting version 6.0, the comparison is
750
-	 * made on the first two parts of the ownCloud version.
751
-	 * This means that it's possible to specify "requiremin" => 6
752
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
753
-	 *
754
-	 * @param string $ocVersion ownCloud version to check against
755
-	 * @param array $appInfo app info (from xml)
756
-	 *
757
-	 * @return boolean true if compatible, otherwise false
758
-	 */
759
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
760
-		$requireMin = '';
761
-		$requireMax = '';
762
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
763
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
764
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
765
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
766
-		} elseif (isset($appInfo['requiremin'])) {
767
-			$requireMin = $appInfo['requiremin'];
768
-		} elseif (isset($appInfo['require'])) {
769
-			$requireMin = $appInfo['require'];
770
-		}
771
-
772
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
773
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
774
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
775
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
776
-		} elseif (isset($appInfo['requiremax'])) {
777
-			$requireMax = $appInfo['requiremax'];
778
-		}
779
-
780
-		if (!empty($requireMin)
781
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
782
-		) {
783
-			return false;
784
-		}
785
-
786
-		if (!$ignoreMax && !empty($requireMax)
787
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
788
-		) {
789
-			return false;
790
-		}
791
-
792
-		return true;
793
-	}
794
-
795
-	/**
796
-	 * get the installed version of all apps
797
-	 */
798
-	public static function getAppVersions() {
799
-		static $versions;
800
-
801
-		if (!$versions) {
802
-			$appConfig = \OC::$server->getAppConfig();
803
-			$versions = $appConfig->getValues(false, 'installed_version');
804
-		}
805
-		return $versions;
806
-	}
807
-
808
-	/**
809
-	 * update the database for the app and call the update script
810
-	 *
811
-	 * @param string $appId
812
-	 * @return bool
813
-	 */
814
-	public static function updateApp(string $appId): bool {
815
-		// for apps distributed with core, we refresh app path in case the downloaded version
816
-		// have been installed in custom apps and not in the default path
817
-		$appPath = self::getAppPath($appId, true);
818
-		if ($appPath === false) {
819
-			return false;
820
-		}
821
-
822
-		if (is_file($appPath . '/appinfo/database.xml')) {
823
-			\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
824
-			return false;
825
-		}
826
-
827
-		\OC::$server->getAppManager()->clearAppsCache();
828
-		$l = \OC::$server->getL10N('core');
829
-		$appData = self::getAppInfo($appId, false, $l->getLanguageCode());
830
-
831
-		$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
832
-		$ignoreMax = in_array($appId, $ignoreMaxApps, true);
833
-		\OC_App::checkAppDependencies(
834
-			\OC::$server->getConfig(),
835
-			$l,
836
-			$appData,
837
-			$ignoreMax
838
-		);
839
-
840
-		self::registerAutoloading($appId, $appPath, true);
841
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
842
-
843
-		$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
844
-		$ms->migrate();
845
-
846
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
847
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
848
-		// update appversion in app manager
849
-		\OC::$server->getAppManager()->clearAppsCache();
850
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
851
-
852
-		self::setupBackgroundJobs($appData['background-jobs']);
853
-
854
-		//set remote/public handlers
855
-		if (array_key_exists('ocsid', $appData)) {
856
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
857
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
858
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
859
-		}
860
-		foreach ($appData['remote'] as $name => $path) {
861
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
862
-		}
863
-		foreach ($appData['public'] as $name => $path) {
864
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
865
-		}
866
-
867
-		self::setAppTypes($appId);
868
-
869
-		$version = \OC_App::getAppVersion($appId);
870
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
871
-
872
-		\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
873
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
874
-			ManagerEvent::EVENT_APP_UPDATE, $appId
875
-		));
876
-
877
-		return true;
878
-	}
879
-
880
-	/**
881
-	 * @param string $appId
882
-	 * @param string[] $steps
883
-	 * @throws \OC\NeedsUpdateException
884
-	 */
885
-	public static function executeRepairSteps(string $appId, array $steps) {
886
-		if (empty($steps)) {
887
-			return;
888
-		}
889
-		// load the app
890
-		self::loadApp($appId);
891
-
892
-		$dispatcher = \OC::$server->get(IEventDispatcher::class);
893
-
894
-		// load the steps
895
-		$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
896
-		foreach ($steps as $step) {
897
-			try {
898
-				$r->addStep($step);
899
-			} catch (Exception $ex) {
900
-				$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
901
-				\OC::$server->getLogger()->logException($ex);
902
-			}
903
-		}
904
-		// run the steps
905
-		$r->run();
906
-	}
907
-
908
-	public static function setupBackgroundJobs(array $jobs) {
909
-		$queue = \OC::$server->getJobList();
910
-		foreach ($jobs as $job) {
911
-			$queue->add($job);
912
-		}
913
-	}
914
-
915
-	/**
916
-	 * @param string $appId
917
-	 * @param string[] $steps
918
-	 */
919
-	private static function setupLiveMigrations(string $appId, array $steps) {
920
-		$queue = \OC::$server->getJobList();
921
-		foreach ($steps as $step) {
922
-			$queue->add('OC\Migration\BackgroundRepair', [
923
-				'app' => $appId,
924
-				'step' => $step]);
925
-		}
926
-	}
927
-
928
-	/**
929
-	 * @param string $appId
930
-	 * @return \OC\Files\View|false
931
-	 */
932
-	public static function getStorage(string $appId) {
933
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
934
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
935
-				$view = new \OC\Files\View('/' . OC_User::getUser());
936
-				if (!$view->file_exists($appId)) {
937
-					$view->mkdir($appId);
938
-				}
939
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
940
-			} else {
941
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
942
-				return false;
943
-			}
944
-		} else {
945
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
946
-			return false;
947
-		}
948
-	}
949
-
950
-	protected static function findBestL10NOption(array $options, string $lang): string {
951
-		// only a single option
952
-		if (isset($options['@value'])) {
953
-			return $options['@value'];
954
-		}
955
-
956
-		$fallback = $similarLangFallback = $englishFallback = false;
957
-
958
-		$lang = strtolower($lang);
959
-		$similarLang = $lang;
960
-		if (strpos($similarLang, '_')) {
961
-			// For "de_DE" we want to find "de" and the other way around
962
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
963
-		}
964
-
965
-		foreach ($options as $option) {
966
-			if (is_array($option)) {
967
-				if ($fallback === false) {
968
-					$fallback = $option['@value'];
969
-				}
970
-
971
-				if (!isset($option['@attributes']['lang'])) {
972
-					continue;
973
-				}
974
-
975
-				$attributeLang = strtolower($option['@attributes']['lang']);
976
-				if ($attributeLang === $lang) {
977
-					return $option['@value'];
978
-				}
979
-
980
-				if ($attributeLang === $similarLang) {
981
-					$similarLangFallback = $option['@value'];
982
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
983
-					if ($similarLangFallback === false) {
984
-						$similarLangFallback = $option['@value'];
985
-					}
986
-				}
987
-			} else {
988
-				$englishFallback = $option;
989
-			}
990
-		}
991
-
992
-		if ($similarLangFallback !== false) {
993
-			return $similarLangFallback;
994
-		} elseif ($englishFallback !== false) {
995
-			return $englishFallback;
996
-		}
997
-		return (string) $fallback;
998
-	}
999
-
1000
-	/**
1001
-	 * parses the app data array and enhanced the 'description' value
1002
-	 *
1003
-	 * @param array $data the app data
1004
-	 * @param string $lang
1005
-	 * @return array improved app data
1006
-	 */
1007
-	public static function parseAppInfo(array $data, $lang = null): array {
1008
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1009
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1010
-		}
1011
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1012
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1013
-		}
1014
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1015
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1016
-		} elseif (isset($data['description']) && is_string($data['description'])) {
1017
-			$data['description'] = trim($data['description']);
1018
-		} else {
1019
-			$data['description'] = '';
1020
-		}
1021
-
1022
-		return $data;
1023
-	}
1024
-
1025
-	/**
1026
-	 * @param \OCP\IConfig $config
1027
-	 * @param \OCP\IL10N $l
1028
-	 * @param array $info
1029
-	 * @throws \Exception
1030
-	 */
1031
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1032
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1033
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1034
-		if (!empty($missing)) {
1035
-			$missingMsg = implode(PHP_EOL, $missing);
1036
-			throw new \Exception(
1037
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1038
-					[$info['name'], $missingMsg]
1039
-				)
1040
-			);
1041
-		}
1042
-	}
76
+    private static $adminForms = [];
77
+    private static $personalForms = [];
78
+    private static $altLogin = [];
79
+    private static $alreadyRegistered = [];
80
+    public const supportedApp = 300;
81
+    public const officialApp = 200;
82
+
83
+    /**
84
+     * clean the appId
85
+     *
86
+     * @psalm-taint-escape file
87
+     * @psalm-taint-escape include
88
+     * @psalm-taint-escape html
89
+     * @psalm-taint-escape has_quotes
90
+     *
91
+     * @param string $app AppId that needs to be cleaned
92
+     * @return string
93
+     */
94
+    public static function cleanAppId(string $app): string {
95
+        return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
96
+    }
97
+
98
+    /**
99
+     * Check if an app is loaded
100
+     *
101
+     * @param string $app
102
+     * @return bool
103
+     * @deprecated 27.0.0 use IAppManager::isAppLoaded
104
+     */
105
+    public static function isAppLoaded(string $app): bool {
106
+        return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
107
+    }
108
+
109
+    /**
110
+     * loads all apps
111
+     *
112
+     * @param string[] $types
113
+     * @return bool
114
+     *
115
+     * This function walks through the ownCloud directory and loads all apps
116
+     * it can find. A directory contains an app if the file /appinfo/info.xml
117
+     * exists.
118
+     *
119
+     * if $types is set to non-empty array, only apps of those types will be loaded
120
+     */
121
+    public static function loadApps(array $types = []): bool {
122
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
123
+            // This should be done before calling this method so that appmanager can be used
124
+            return false;
125
+        }
126
+        return \OC::$server->get(IAppManager::class)->loadApps($types);
127
+    }
128
+
129
+    /**
130
+     * load a single app
131
+     *
132
+     * @param string $app
133
+     * @throws Exception
134
+     * @deprecated 27.0.0 use IAppManager::loadApp
135
+     */
136
+    public static function loadApp(string $app): void {
137
+        \OC::$server->get(IAppManager::class)->loadApp($app);
138
+    }
139
+
140
+    /**
141
+     * @internal
142
+     * @param string $app
143
+     * @param string $path
144
+     * @param bool $force
145
+     */
146
+    public static function registerAutoloading(string $app, string $path, bool $force = false) {
147
+        $key = $app . '-' . $path;
148
+        if (!$force && isset(self::$alreadyRegistered[$key])) {
149
+            return;
150
+        }
151
+
152
+        self::$alreadyRegistered[$key] = true;
153
+
154
+        // Register on PSR-4 composer autoloader
155
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
156
+        \OC::$server->registerNamespace($app, $appNamespace);
157
+
158
+        if (file_exists($path . '/composer/autoload.php')) {
159
+            require_once $path . '/composer/autoload.php';
160
+        } else {
161
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
162
+        }
163
+
164
+        // Register Test namespace only when testing
165
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
166
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
167
+        }
168
+    }
169
+
170
+    /**
171
+     * check if an app is of a specific type
172
+     *
173
+     * @param string $app
174
+     * @param array $types
175
+     * @return bool
176
+     * @deprecated 27.0.0 use IAppManager::isType
177
+     */
178
+    public static function isType(string $app, array $types): bool {
179
+        return \OC::$server->get(IAppManager::class)->isType($app, $types);
180
+    }
181
+
182
+    /**
183
+     * read app types from info.xml and cache them in the database
184
+     */
185
+    public static function setAppTypes(string $app) {
186
+        $appManager = \OC::$server->getAppManager();
187
+        $appData = $appManager->getAppInfo($app);
188
+        if (!is_array($appData)) {
189
+            return;
190
+        }
191
+
192
+        if (isset($appData['types'])) {
193
+            $appTypes = implode(',', $appData['types']);
194
+        } else {
195
+            $appTypes = '';
196
+            $appData['types'] = [];
197
+        }
198
+
199
+        $config = \OC::$server->getConfig();
200
+        $config->setAppValue($app, 'types', $appTypes);
201
+
202
+        if ($appManager->hasProtectedAppType($appData['types'])) {
203
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
204
+            if ($enabled !== 'yes' && $enabled !== 'no') {
205
+                $config->setAppValue($app, 'enabled', 'yes');
206
+            }
207
+        }
208
+    }
209
+
210
+    /**
211
+     * Returns apps enabled for the current user.
212
+     *
213
+     * @param bool $forceRefresh whether to refresh the cache
214
+     * @param bool $all whether to return apps for all users, not only the
215
+     * currently logged in one
216
+     * @return string[]
217
+     */
218
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
219
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
220
+            return [];
221
+        }
222
+        // in incognito mode or when logged out, $user will be false,
223
+        // which is also the case during an upgrade
224
+        $appManager = \OC::$server->getAppManager();
225
+        if ($all) {
226
+            $user = null;
227
+        } else {
228
+            $user = \OC::$server->getUserSession()->getUser();
229
+        }
230
+
231
+        if (is_null($user)) {
232
+            $apps = $appManager->getInstalledApps();
233
+        } else {
234
+            $apps = $appManager->getEnabledAppsForUser($user);
235
+        }
236
+        $apps = array_filter($apps, function ($app) {
237
+            return $app !== 'files';//we add this manually
238
+        });
239
+        sort($apps);
240
+        array_unshift($apps, 'files');
241
+        return $apps;
242
+    }
243
+
244
+    /**
245
+     * checks whether or not an app is enabled
246
+     *
247
+     * @param string $app app
248
+     * @return bool
249
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
250
+     *
251
+     * This function checks whether or not an app is enabled.
252
+     */
253
+    public static function isEnabled(string $app): bool {
254
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
255
+    }
256
+
257
+    /**
258
+     * enables an app
259
+     *
260
+     * @param string $appId
261
+     * @param array $groups (optional) when set, only these groups will have access to the app
262
+     * @throws \Exception
263
+     * @return void
264
+     *
265
+     * This function set an app as enabled in appconfig.
266
+     */
267
+    public function enable(string $appId,
268
+                            array $groups = []) {
269
+        // Check if app is already downloaded
270
+        /** @var Installer $installer */
271
+        $installer = \OC::$server->query(Installer::class);
272
+        $isDownloaded = $installer->isDownloaded($appId);
273
+
274
+        if (!$isDownloaded) {
275
+            $installer->downloadApp($appId);
276
+        }
277
+
278
+        $installer->installApp($appId);
279
+
280
+        $appManager = \OC::$server->getAppManager();
281
+        if ($groups !== []) {
282
+            $groupManager = \OC::$server->getGroupManager();
283
+            $groupsList = [];
284
+            foreach ($groups as $group) {
285
+                $groupItem = $groupManager->get($group);
286
+                if ($groupItem instanceof \OCP\IGroup) {
287
+                    $groupsList[] = $groupManager->get($group);
288
+                }
289
+            }
290
+            $appManager->enableAppForGroups($appId, $groupsList);
291
+        } else {
292
+            $appManager->enableApp($appId);
293
+        }
294
+    }
295
+
296
+    /**
297
+     * Get the path where to install apps
298
+     *
299
+     * @return string|false
300
+     */
301
+    public static function getInstallPath() {
302
+        foreach (OC::$APPSROOTS as $dir) {
303
+            if (isset($dir['writable']) && $dir['writable'] === true) {
304
+                return $dir['path'];
305
+            }
306
+        }
307
+
308
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
309
+        return null;
310
+    }
311
+
312
+
313
+    /**
314
+     * search for an app in all app-directories
315
+     *
316
+     * @param string $appId
317
+     * @param bool $ignoreCache ignore cache and rebuild it
318
+     * @return false|string
319
+     */
320
+    public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
321
+        $sanitizedAppId = self::cleanAppId($appId);
322
+        if ($sanitizedAppId !== $appId) {
323
+            return false;
324
+        }
325
+        static $app_dir = [];
326
+
327
+        if (isset($app_dir[$appId]) && !$ignoreCache) {
328
+            return $app_dir[$appId];
329
+        }
330
+
331
+        $possibleApps = [];
332
+        foreach (OC::$APPSROOTS as $dir) {
333
+            if (file_exists($dir['path'] . '/' . $appId)) {
334
+                $possibleApps[] = $dir;
335
+            }
336
+        }
337
+
338
+        if (empty($possibleApps)) {
339
+            return false;
340
+        } elseif (count($possibleApps) === 1) {
341
+            $dir = array_shift($possibleApps);
342
+            $app_dir[$appId] = $dir;
343
+            return $dir;
344
+        } else {
345
+            $versionToLoad = [];
346
+            foreach ($possibleApps as $possibleApp) {
347
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
348
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
349
+                    $versionToLoad = [
350
+                        'dir' => $possibleApp,
351
+                        'version' => $version,
352
+                    ];
353
+                }
354
+            }
355
+            $app_dir[$appId] = $versionToLoad['dir'];
356
+            return $versionToLoad['dir'];
357
+            //TODO - write test
358
+        }
359
+    }
360
+
361
+    /**
362
+     * Get the directory for the given app.
363
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
364
+     *
365
+     * @psalm-taint-specialize
366
+     *
367
+     * @param string $appId
368
+     * @param bool $refreshAppPath should be set to true only during install/upgrade
369
+     * @return string|false
370
+     * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
371
+     */
372
+    public static function getAppPath(string $appId, bool $refreshAppPath = false) {
373
+        if ($appId === null || trim($appId) === '') {
374
+            return false;
375
+        }
376
+
377
+        if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
378
+            return $dir['path'] . '/' . $appId;
379
+        }
380
+        return false;
381
+    }
382
+
383
+    /**
384
+     * Get the path for the given app on the access
385
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
386
+     *
387
+     * @param string $appId
388
+     * @return string|false
389
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
390
+     */
391
+    public static function getAppWebPath(string $appId) {
392
+        if (($dir = self::findAppInDirectories($appId)) != false) {
393
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
394
+        }
395
+        return false;
396
+    }
397
+
398
+    /**
399
+     * get the last version of the app from appinfo/info.xml
400
+     *
401
+     * @param string $appId
402
+     * @param bool $useCache
403
+     * @return string
404
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
405
+     */
406
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
407
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
408
+    }
409
+
410
+    /**
411
+     * get app's version based on it's path
412
+     *
413
+     * @param string $path
414
+     * @return string
415
+     */
416
+    public static function getAppVersionByPath(string $path): string {
417
+        $infoFile = $path . '/appinfo/info.xml';
418
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
419
+        return isset($appData['version']) ? $appData['version'] : '';
420
+    }
421
+
422
+
423
+    /**
424
+     * Read all app metadata from the info.xml file
425
+     *
426
+     * @param string $appId id of the app or the path of the info.xml file
427
+     * @param bool $path
428
+     * @param string $lang
429
+     * @return array|null
430
+     * @note all data is read from info.xml, not just pre-defined fields
431
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
432
+     */
433
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
434
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
435
+    }
436
+
437
+    /**
438
+     * Returns the navigation
439
+     *
440
+     * @return array
441
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
442
+     *
443
+     * This function returns an array containing all entries added. The
444
+     * entries are sorted by the key 'order' ascending. Additional to the keys
445
+     * given for each app the following keys exist:
446
+     *   - active: boolean, signals if the user is on this navigation entry
447
+     */
448
+    public static function getNavigation(): array {
449
+        return OC::$server->getNavigationManager()->getAll();
450
+    }
451
+
452
+    /**
453
+     * Returns the Settings Navigation
454
+     *
455
+     * @return string[]
456
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
457
+     *
458
+     * This function returns an array containing all settings pages added. The
459
+     * entries are sorted by the key 'order' ascending.
460
+     */
461
+    public static function getSettingsNavigation(): array {
462
+        return OC::$server->getNavigationManager()->getAll('settings');
463
+    }
464
+
465
+    /**
466
+     * get the id of loaded app
467
+     *
468
+     * @return string
469
+     */
470
+    public static function getCurrentApp(): string {
471
+        if (\OC::$CLI) {
472
+            return '';
473
+        }
474
+
475
+        $request = \OC::$server->getRequest();
476
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
477
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
478
+        if (empty($topFolder)) {
479
+            try {
480
+                $path_info = $request->getPathInfo();
481
+            } catch (Exception $e) {
482
+                // Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
483
+                \OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
484
+                return '';
485
+            }
486
+            if ($path_info) {
487
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
488
+            }
489
+        }
490
+        if ($topFolder == 'apps') {
491
+            $length = strlen($topFolder);
492
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
493
+        } else {
494
+            return $topFolder;
495
+        }
496
+    }
497
+
498
+    /**
499
+     * @param string $type
500
+     * @return array
501
+     */
502
+    public static function getForms(string $type): array {
503
+        $forms = [];
504
+        switch ($type) {
505
+            case 'admin':
506
+                $source = self::$adminForms;
507
+                break;
508
+            case 'personal':
509
+                $source = self::$personalForms;
510
+                break;
511
+            default:
512
+                return [];
513
+        }
514
+        foreach ($source as $form) {
515
+            $forms[] = include $form;
516
+        }
517
+        return $forms;
518
+    }
519
+
520
+    /**
521
+     * @param array $entry
522
+     * @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
523
+     */
524
+    public static function registerLogIn(array $entry) {
525
+        \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');
526
+        self::$altLogin[] = $entry;
527
+    }
528
+
529
+    /**
530
+     * @return array
531
+     */
532
+    public static function getAlternativeLogIns(): array {
533
+        /** @var Coordinator $bootstrapCoordinator */
534
+        $bootstrapCoordinator = \OC::$server->query(Coordinator::class);
535
+
536
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
537
+            if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
538
+                \OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
539
+                    'option' => $registration->getService(),
540
+                    'interface' => IAlternativeLogin::class,
541
+                    'app' => $registration->getAppId(),
542
+                ]);
543
+                continue;
544
+            }
545
+
546
+            try {
547
+                /** @var IAlternativeLogin $provider */
548
+                $provider = \OC::$server->query($registration->getService());
549
+            } catch (QueryException $e) {
550
+                \OC::$server->getLogger()->logException($e, [
551
+                    'message' => 'Alternative login option {option} can not be initialised.',
552
+                    'option' => $registration->getService(),
553
+                    'app' => $registration->getAppId(),
554
+                ]);
555
+            }
556
+
557
+            try {
558
+                $provider->load();
559
+
560
+                self::$altLogin[] = [
561
+                    'name' => $provider->getLabel(),
562
+                    'href' => $provider->getLink(),
563
+                    'class' => $provider->getClass(),
564
+                ];
565
+            } catch (Throwable $e) {
566
+                \OC::$server->getLogger()->logException($e, [
567
+                    'message' => 'Alternative login option {option} had an error while loading.',
568
+                    'option' => $registration->getService(),
569
+                    'app' => $registration->getAppId(),
570
+                ]);
571
+            }
572
+        }
573
+
574
+        return self::$altLogin;
575
+    }
576
+
577
+    /**
578
+     * get a list of all apps in the apps folder
579
+     *
580
+     * @return string[] an array of app names (string IDs)
581
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
582
+     */
583
+    public static function getAllApps(): array {
584
+        $apps = [];
585
+
586
+        foreach (OC::$APPSROOTS as $apps_dir) {
587
+            if (!is_readable($apps_dir['path'])) {
588
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
589
+                continue;
590
+            }
591
+            $dh = opendir($apps_dir['path']);
592
+
593
+            if (is_resource($dh)) {
594
+                while (($file = readdir($dh)) !== false) {
595
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
596
+                        $apps[] = $file;
597
+                    }
598
+                }
599
+            }
600
+        }
601
+
602
+        $apps = array_unique($apps);
603
+
604
+        return $apps;
605
+    }
606
+
607
+    /**
608
+     * List all supported apps
609
+     *
610
+     * @return array
611
+     */
612
+    public function getSupportedApps(): array {
613
+        /** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
614
+        $subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
615
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
616
+        return $supportedApps;
617
+    }
618
+
619
+    /**
620
+     * List all apps, this is used in apps.php
621
+     *
622
+     * @return array
623
+     */
624
+    public function listAllApps(): array {
625
+        $installedApps = OC_App::getAllApps();
626
+
627
+        $appManager = \OC::$server->getAppManager();
628
+        //we don't want to show configuration for these
629
+        $blacklist = $appManager->getAlwaysEnabledApps();
630
+        $appList = [];
631
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
632
+        $urlGenerator = \OC::$server->getURLGenerator();
633
+        $supportedApps = $this->getSupportedApps();
634
+
635
+        foreach ($installedApps as $app) {
636
+            if (array_search($app, $blacklist) === false) {
637
+                $info = OC_App::getAppInfo($app, false, $langCode);
638
+                if (!is_array($info)) {
639
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
640
+                    continue;
641
+                }
642
+
643
+                if (!isset($info['name'])) {
644
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
645
+                    continue;
646
+                }
647
+
648
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
649
+                $info['groups'] = null;
650
+                if ($enabled === 'yes') {
651
+                    $active = true;
652
+                } elseif ($enabled === 'no') {
653
+                    $active = false;
654
+                } else {
655
+                    $active = true;
656
+                    $info['groups'] = $enabled;
657
+                }
658
+
659
+                $info['active'] = $active;
660
+
661
+                if ($appManager->isShipped($app)) {
662
+                    $info['internal'] = true;
663
+                    $info['level'] = self::officialApp;
664
+                    $info['removable'] = false;
665
+                } else {
666
+                    $info['internal'] = false;
667
+                    $info['removable'] = true;
668
+                }
669
+
670
+                if (in_array($app, $supportedApps)) {
671
+                    $info['level'] = self::supportedApp;
672
+                }
673
+
674
+                $appPath = self::getAppPath($app);
675
+                if ($appPath !== false) {
676
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
677
+                    if (file_exists($appIcon)) {
678
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
679
+                        $info['previewAsIcon'] = true;
680
+                    } else {
681
+                        $appIcon = $appPath . '/img/app.svg';
682
+                        if (file_exists($appIcon)) {
683
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
684
+                            $info['previewAsIcon'] = true;
685
+                        }
686
+                    }
687
+                }
688
+                // fix documentation
689
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
690
+                    foreach ($info['documentation'] as $key => $url) {
691
+                        // If it is not an absolute URL we assume it is a key
692
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
693
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
694
+                            $url = $urlGenerator->linkToDocs($url);
695
+                        }
696
+
697
+                        $info['documentation'][$key] = $url;
698
+                    }
699
+                }
700
+
701
+                $info['version'] = OC_App::getAppVersion($app);
702
+                $appList[] = $info;
703
+            }
704
+        }
705
+
706
+        return $appList;
707
+    }
708
+
709
+    public static function shouldUpgrade(string $app): bool {
710
+        $versions = self::getAppVersions();
711
+        $currentVersion = OC_App::getAppVersion($app);
712
+        if ($currentVersion && isset($versions[$app])) {
713
+            $installedVersion = $versions[$app];
714
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
715
+                return true;
716
+            }
717
+        }
718
+        return false;
719
+    }
720
+
721
+    /**
722
+     * Adjust the number of version parts of $version1 to match
723
+     * the number of version parts of $version2.
724
+     *
725
+     * @param string $version1 version to adjust
726
+     * @param string $version2 version to take the number of parts from
727
+     * @return string shortened $version1
728
+     */
729
+    private static function adjustVersionParts(string $version1, string $version2): string {
730
+        $version1 = explode('.', $version1);
731
+        $version2 = explode('.', $version2);
732
+        // reduce $version1 to match the number of parts in $version2
733
+        while (count($version1) > count($version2)) {
734
+            array_pop($version1);
735
+        }
736
+        // if $version1 does not have enough parts, add some
737
+        while (count($version1) < count($version2)) {
738
+            $version1[] = '0';
739
+        }
740
+        return implode('.', $version1);
741
+    }
742
+
743
+    /**
744
+     * Check whether the current ownCloud version matches the given
745
+     * application's version requirements.
746
+     *
747
+     * The comparison is made based on the number of parts that the
748
+     * app info version has. For example for ownCloud 6.0.3 if the
749
+     * app info version is expecting version 6.0, the comparison is
750
+     * made on the first two parts of the ownCloud version.
751
+     * This means that it's possible to specify "requiremin" => 6
752
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
753
+     *
754
+     * @param string $ocVersion ownCloud version to check against
755
+     * @param array $appInfo app info (from xml)
756
+     *
757
+     * @return boolean true if compatible, otherwise false
758
+     */
759
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
760
+        $requireMin = '';
761
+        $requireMax = '';
762
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
763
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
764
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
765
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
766
+        } elseif (isset($appInfo['requiremin'])) {
767
+            $requireMin = $appInfo['requiremin'];
768
+        } elseif (isset($appInfo['require'])) {
769
+            $requireMin = $appInfo['require'];
770
+        }
771
+
772
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
773
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
774
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
775
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
776
+        } elseif (isset($appInfo['requiremax'])) {
777
+            $requireMax = $appInfo['requiremax'];
778
+        }
779
+
780
+        if (!empty($requireMin)
781
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
782
+        ) {
783
+            return false;
784
+        }
785
+
786
+        if (!$ignoreMax && !empty($requireMax)
787
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
788
+        ) {
789
+            return false;
790
+        }
791
+
792
+        return true;
793
+    }
794
+
795
+    /**
796
+     * get the installed version of all apps
797
+     */
798
+    public static function getAppVersions() {
799
+        static $versions;
800
+
801
+        if (!$versions) {
802
+            $appConfig = \OC::$server->getAppConfig();
803
+            $versions = $appConfig->getValues(false, 'installed_version');
804
+        }
805
+        return $versions;
806
+    }
807
+
808
+    /**
809
+     * update the database for the app and call the update script
810
+     *
811
+     * @param string $appId
812
+     * @return bool
813
+     */
814
+    public static function updateApp(string $appId): bool {
815
+        // for apps distributed with core, we refresh app path in case the downloaded version
816
+        // have been installed in custom apps and not in the default path
817
+        $appPath = self::getAppPath($appId, true);
818
+        if ($appPath === false) {
819
+            return false;
820
+        }
821
+
822
+        if (is_file($appPath . '/appinfo/database.xml')) {
823
+            \OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
824
+            return false;
825
+        }
826
+
827
+        \OC::$server->getAppManager()->clearAppsCache();
828
+        $l = \OC::$server->getL10N('core');
829
+        $appData = self::getAppInfo($appId, false, $l->getLanguageCode());
830
+
831
+        $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
832
+        $ignoreMax = in_array($appId, $ignoreMaxApps, true);
833
+        \OC_App::checkAppDependencies(
834
+            \OC::$server->getConfig(),
835
+            $l,
836
+            $appData,
837
+            $ignoreMax
838
+        );
839
+
840
+        self::registerAutoloading($appId, $appPath, true);
841
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
842
+
843
+        $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
844
+        $ms->migrate();
845
+
846
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
847
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
848
+        // update appversion in app manager
849
+        \OC::$server->getAppManager()->clearAppsCache();
850
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
851
+
852
+        self::setupBackgroundJobs($appData['background-jobs']);
853
+
854
+        //set remote/public handlers
855
+        if (array_key_exists('ocsid', $appData)) {
856
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
857
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
858
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
859
+        }
860
+        foreach ($appData['remote'] as $name => $path) {
861
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
862
+        }
863
+        foreach ($appData['public'] as $name => $path) {
864
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
865
+        }
866
+
867
+        self::setAppTypes($appId);
868
+
869
+        $version = \OC_App::getAppVersion($appId);
870
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
871
+
872
+        \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
873
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
874
+            ManagerEvent::EVENT_APP_UPDATE, $appId
875
+        ));
876
+
877
+        return true;
878
+    }
879
+
880
+    /**
881
+     * @param string $appId
882
+     * @param string[] $steps
883
+     * @throws \OC\NeedsUpdateException
884
+     */
885
+    public static function executeRepairSteps(string $appId, array $steps) {
886
+        if (empty($steps)) {
887
+            return;
888
+        }
889
+        // load the app
890
+        self::loadApp($appId);
891
+
892
+        $dispatcher = \OC::$server->get(IEventDispatcher::class);
893
+
894
+        // load the steps
895
+        $r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
896
+        foreach ($steps as $step) {
897
+            try {
898
+                $r->addStep($step);
899
+            } catch (Exception $ex) {
900
+                $dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
901
+                \OC::$server->getLogger()->logException($ex);
902
+            }
903
+        }
904
+        // run the steps
905
+        $r->run();
906
+    }
907
+
908
+    public static function setupBackgroundJobs(array $jobs) {
909
+        $queue = \OC::$server->getJobList();
910
+        foreach ($jobs as $job) {
911
+            $queue->add($job);
912
+        }
913
+    }
914
+
915
+    /**
916
+     * @param string $appId
917
+     * @param string[] $steps
918
+     */
919
+    private static function setupLiveMigrations(string $appId, array $steps) {
920
+        $queue = \OC::$server->getJobList();
921
+        foreach ($steps as $step) {
922
+            $queue->add('OC\Migration\BackgroundRepair', [
923
+                'app' => $appId,
924
+                'step' => $step]);
925
+        }
926
+    }
927
+
928
+    /**
929
+     * @param string $appId
930
+     * @return \OC\Files\View|false
931
+     */
932
+    public static function getStorage(string $appId) {
933
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
934
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
935
+                $view = new \OC\Files\View('/' . OC_User::getUser());
936
+                if (!$view->file_exists($appId)) {
937
+                    $view->mkdir($appId);
938
+                }
939
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
940
+            } else {
941
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
942
+                return false;
943
+            }
944
+        } else {
945
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
946
+            return false;
947
+        }
948
+    }
949
+
950
+    protected static function findBestL10NOption(array $options, string $lang): string {
951
+        // only a single option
952
+        if (isset($options['@value'])) {
953
+            return $options['@value'];
954
+        }
955
+
956
+        $fallback = $similarLangFallback = $englishFallback = false;
957
+
958
+        $lang = strtolower($lang);
959
+        $similarLang = $lang;
960
+        if (strpos($similarLang, '_')) {
961
+            // For "de_DE" we want to find "de" and the other way around
962
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
963
+        }
964
+
965
+        foreach ($options as $option) {
966
+            if (is_array($option)) {
967
+                if ($fallback === false) {
968
+                    $fallback = $option['@value'];
969
+                }
970
+
971
+                if (!isset($option['@attributes']['lang'])) {
972
+                    continue;
973
+                }
974
+
975
+                $attributeLang = strtolower($option['@attributes']['lang']);
976
+                if ($attributeLang === $lang) {
977
+                    return $option['@value'];
978
+                }
979
+
980
+                if ($attributeLang === $similarLang) {
981
+                    $similarLangFallback = $option['@value'];
982
+                } elseif (strpos($attributeLang, $similarLang . '_') === 0) {
983
+                    if ($similarLangFallback === false) {
984
+                        $similarLangFallback = $option['@value'];
985
+                    }
986
+                }
987
+            } else {
988
+                $englishFallback = $option;
989
+            }
990
+        }
991
+
992
+        if ($similarLangFallback !== false) {
993
+            return $similarLangFallback;
994
+        } elseif ($englishFallback !== false) {
995
+            return $englishFallback;
996
+        }
997
+        return (string) $fallback;
998
+    }
999
+
1000
+    /**
1001
+     * parses the app data array and enhanced the 'description' value
1002
+     *
1003
+     * @param array $data the app data
1004
+     * @param string $lang
1005
+     * @return array improved app data
1006
+     */
1007
+    public static function parseAppInfo(array $data, $lang = null): array {
1008
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1009
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1010
+        }
1011
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1012
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1013
+        }
1014
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1015
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1016
+        } elseif (isset($data['description']) && is_string($data['description'])) {
1017
+            $data['description'] = trim($data['description']);
1018
+        } else {
1019
+            $data['description'] = '';
1020
+        }
1021
+
1022
+        return $data;
1023
+    }
1024
+
1025
+    /**
1026
+     * @param \OCP\IConfig $config
1027
+     * @param \OCP\IL10N $l
1028
+     * @param array $info
1029
+     * @throws \Exception
1030
+     */
1031
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1032
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1033
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1034
+        if (!empty($missing)) {
1035
+            $missingMsg = implode(PHP_EOL, $missing);
1036
+            throw new \Exception(
1037
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1038
+                    [$info['name'], $missingMsg]
1039
+                )
1040
+            );
1041
+        }
1042
+    }
1043 1043
 }
Please login to merge, or discard this patch.
apps/dashboard/lib/Controller/LayoutApiController.php 1 patch
Indentation   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -31,32 +31,32 @@
 block discarded – undo
31 31
 use OCP\IRequest;
32 32
 
33 33
 class LayoutApiController extends OCSController {
34
-	/** @var IConfig */
35
-	private $config;
36
-	/** @var string */
37
-	private $userId;
34
+    /** @var IConfig */
35
+    private $config;
36
+    /** @var string */
37
+    private $userId;
38 38
 
39
-	public function __construct(
40
-		string $appName,
41
-		IRequest $request,
42
-		IConfig $config,
43
-		$userId
44
-	) {
45
-		parent::__construct($appName, $request);
39
+    public function __construct(
40
+        string $appName,
41
+        IRequest $request,
42
+        IConfig $config,
43
+        $userId
44
+    ) {
45
+        parent::__construct($appName, $request);
46 46
 
47
-		$this->config = $config;
48
-		$this->userId = $userId;
49
-	}
47
+        $this->config = $config;
48
+        $this->userId = $userId;
49
+    }
50 50
 
51
-	/**
52
-	 * @NoAdminRequired
53
-	 *
54
-	 * @param string $layout
55
-	 * @return JSONResponse
56
-	 */
57
-	public function create(string $layout): JSONResponse {
58
-		$layout = htmlspecialchars($layout);
59
-		$this->config->setUserValue($this->userId, 'dashboard', 'layout', $layout);
60
-		return new JSONResponse(['layout' => $layout]);
61
-	}
51
+    /**
52
+     * @NoAdminRequired
53
+     *
54
+     * @param string $layout
55
+     * @return JSONResponse
56
+     */
57
+    public function create(string $layout): JSONResponse {
58
+        $layout = htmlspecialchars($layout);
59
+        $this->config->setUserValue($this->userId, 'dashboard', 'layout', $layout);
60
+        return new JSONResponse(['layout' => $layout]);
61
+    }
62 62
 }
Please login to merge, or discard this patch.