Completed
Push — master ( 554dfb...d8ff05 )
by
unknown
22:53 queued 01:20
created
lib/private/TemplateLayout.php 1 patch
Indentation   +380 added lines, -380 removed lines patch added patch discarded remove patch
@@ -35,384 +35,384 @@
 block discarded – undo
35 35
 use OCP\Util;
36 36
 
37 37
 class TemplateLayout {
38
-	private static string $versionHash = '';
39
-	/** @var string[] */
40
-	private static array $cacheBusterCache = [];
41
-
42
-	public static ?CSSResourceLocator $cssLocator = null;
43
-	public static ?JSResourceLocator $jsLocator = null;
44
-
45
-	public function __construct(
46
-		private IConfig $config,
47
-		private IAppManager $appManager,
48
-		private InitialStateService $initialState,
49
-		private INavigationManager $navigationManager,
50
-		private ITemplateManager $templateManager,
51
-		private ServerVersion $serverVersion,
52
-	) {
53
-	}
54
-
55
-	public function getPageTemplate(string $renderAs, string $appId): ITemplate {
56
-		// Add fallback theming variables if not rendered as user
57
-		if ($renderAs !== TemplateResponse::RENDER_AS_USER) {
58
-			// TODO cache generated default theme if enabled for fallback if server is erroring ?
59
-			Util::addStyle('theming', 'default');
60
-		}
61
-
62
-		// Decide which page we show
63
-		switch ($renderAs) {
64
-			case TemplateResponse::RENDER_AS_USER:
65
-				$page = $this->templateManager->getTemplate('core', 'layout.user');
66
-				if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
67
-					$page->assign('bodyid', 'body-settings');
68
-				} else {
69
-					$page->assign('bodyid', 'body-user');
70
-				}
71
-
72
-				$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
73
-				$this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));
74
-
75
-				if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
76
-					$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
77
-					$this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
78
-					$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
79
-					Util::addScript('core', 'legacy-unified-search', 'core');
80
-				} else {
81
-					Util::addScript('core', 'unified-search', 'core');
82
-				}
83
-				// Set body data-theme
84
-				$page->assign('enabledThemes', []);
85
-				if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
86
-					$themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
87
-					$page->assign('enabledThemes', $themesService->getEnabledThemes());
88
-				}
89
-
90
-				// Set logo link target
91
-				$logoUrl = $this->config->getSystemValueString('logo_url', '');
92
-				$page->assign('logoUrl', $logoUrl);
93
-
94
-				// Set default entry name
95
-				$defaultEntryId = $this->navigationManager->getDefaultEntryIdForUser();
96
-				$defaultEntry = $this->navigationManager->get($defaultEntryId);
97
-				$page->assign('defaultAppName', $defaultEntry['name'] ?? '');
98
-
99
-				// Add navigation entry
100
-				$page->assign('application', '');
101
-				$page->assign('appid', $appId);
102
-
103
-				$navigation = $this->navigationManager->getAll();
104
-				$page->assign('navigation', $navigation);
105
-				$settingsNavigation = $this->navigationManager->getAll('settings');
106
-				$this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation);
107
-
108
-				foreach ($navigation as $entry) {
109
-					if ($entry['active']) {
110
-						$page->assign('application', $entry['name']);
111
-						break;
112
-					}
113
-				}
114
-
115
-				foreach ($settingsNavigation as $entry) {
116
-					if ($entry['active']) {
117
-						$page->assign('application', $entry['name']);
118
-						break;
119
-					}
120
-				}
121
-
122
-				$user = Server::get(IUserSession::class)->getUser();
123
-
124
-				if ($user === null) {
125
-					$page->assign('user_uid', false);
126
-					$page->assign('user_displayname', false);
127
-					$page->assign('userAvatarSet', false);
128
-					$page->assign('userStatus', false);
129
-				} else {
130
-					$page->assign('user_uid', $user->getUID());
131
-					$page->assign('user_displayname', $user->getDisplayName());
132
-					$page->assign('userAvatarSet', true);
133
-					$page->assign('userAvatarVersion', $this->config->getUserValue($user->getUID(), 'avatar', 'version', 0));
134
-				}
135
-				break;
136
-			case TemplateResponse::RENDER_AS_ERROR:
137
-				$page = $this->templateManager->getTemplate('core', 'layout.guest', '', false);
138
-				$page->assign('bodyid', 'body-login');
139
-				$page->assign('user_displayname', '');
140
-				$page->assign('user_uid', '');
141
-				break;
142
-			case TemplateResponse::RENDER_AS_GUEST:
143
-				$page = $this->templateManager->getTemplate('core', 'layout.guest');
144
-				Util::addStyle('guest');
145
-				$page->assign('bodyid', 'body-login');
146
-
147
-				$userDisplayName = false;
148
-				$user = Server::get(IUserSession::class)->getUser();
149
-				if ($user) {
150
-					$userDisplayName = $user->getDisplayName();
151
-				}
152
-
153
-				$page->assign('enabledThemes', []);
154
-				if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
155
-					$themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
156
-					$page->assign('enabledThemes', $themesService->getEnabledThemes());
157
-				}
158
-
159
-				$page->assign('user_displayname', $userDisplayName);
160
-				$page->assign('user_uid', \OC_User::getUser());
161
-				break;
162
-			case TemplateResponse::RENDER_AS_PUBLIC:
163
-				$page = $this->templateManager->getTemplate('core', 'layout.public');
164
-				$page->assign('appid', $appId);
165
-				$page->assign('bodyid', 'body-public');
166
-
167
-				// Set body data-theme
168
-				$page->assign('enabledThemes', []);
169
-				if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
170
-					$themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
171
-					$page->assign('enabledThemes', $themesService->getEnabledThemes());
172
-				}
173
-
174
-				// Set logo link target
175
-				$logoUrl = $this->config->getSystemValueString('logo_url', '');
176
-				$page->assign('logoUrl', $logoUrl);
177
-
178
-				$subscription = Server::get(IRegistry::class);
179
-				$showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
180
-				if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
181
-					$showSimpleSignup = false;
182
-				}
183
-
184
-				$defaultSignUpLink = 'https://nextcloud.com/signup/';
185
-				$signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
186
-				if ($signUpLink !== $defaultSignUpLink) {
187
-					$showSimpleSignup = true;
188
-				}
189
-
190
-				if ($this->appManager->isEnabledForUser('registration')) {
191
-					$urlGenerator = Server::get(IURLGenerator::class);
192
-					$signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
193
-				}
194
-
195
-				$page->assign('showSimpleSignUpLink', $showSimpleSignup);
196
-				$page->assign('signUpLink', $signUpLink);
197
-				break;
198
-			default:
199
-				$page = $this->templateManager->getTemplate('core', 'layout.base');
200
-				break;
201
-		}
202
-		// Send the language, locale, and direction to our layouts
203
-		$l10nFactory = Server::get(IFactory::class);
204
-		$lang = $l10nFactory->findLanguage();
205
-		$locale = $l10nFactory->findLocale($lang);
206
-		$direction = $l10nFactory->getLanguageDirection($lang);
207
-
208
-		$lang = str_replace('_', '-', $lang);
209
-		$page->assign('language', $lang);
210
-		$page->assign('locale', $locale);
211
-		$page->assign('direction', $direction);
212
-
213
-		if ($this->config->getSystemValueBool('installed', false)) {
214
-			if (empty(self::$versionHash)) {
215
-				$v = $this->appManager->getAppInstalledVersions();
216
-				$v['core'] = implode('.', $this->serverVersion->getVersion());
217
-				self::$versionHash = substr(md5(implode(',', $v)), 0, 8);
218
-			}
219
-		} else {
220
-			self::$versionHash = md5('not installed');
221
-		}
222
-
223
-		// Add the js files
224
-		$jsFiles = self::findJavascriptFiles(Util::getScripts());
225
-		$page->assign('jsfiles', []);
226
-		if ($this->config->getSystemValueBool('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) {
227
-			// this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call)
228
-			// see https://github.com/nextcloud/server/pull/22636 for details
229
-			$jsConfigHelper = new JSConfigHelper(
230
-				$this->serverVersion,
231
-				\OCP\Util::getL10N('lib'),
232
-				\OCP\Server::get(Defaults::class),
233
-				$this->appManager,
234
-				\OC::$server->getSession(),
235
-				\OC::$server->getUserSession()->getUser(),
236
-				$this->config,
237
-				\OC::$server->getGroupManager(),
238
-				\OC::$server->get(IniGetWrapper::class),
239
-				\OC::$server->getURLGenerator(),
240
-				\OC::$server->get(CapabilitiesManager::class),
241
-				\OCP\Server::get(IInitialStateService::class),
242
-				\OCP\Server::get(IProvider::class),
243
-				\OCP\Server::get(FilenameValidator::class),
244
-			);
245
-			$config = $jsConfigHelper->getConfig();
246
-			if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) {
247
-				$page->assign('inline_ocjs', $config);
248
-			} else {
249
-				$page->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash]));
250
-			}
251
-		}
252
-		foreach ($jsFiles as $info) {
253
-			$web = $info[1];
254
-			$file = $info[2];
255
-			$page->append('jsfiles', $web . '/' . $file . $this->getVersionHashSuffix());
256
-		}
257
-
258
-		$request = \OCP\Server::get(IRequest::class);
259
-
260
-		try {
261
-			$pathInfo = $request->getPathInfo();
262
-		} catch (\Exception $e) {
263
-			$pathInfo = '';
264
-		}
265
-
266
-		// Do not initialise scss appdata until we have a fully installed instance
267
-		// Do not load scss for update, errors, installation or login page
268
-		if ($this->config->getSystemValueBool('installed', false)
269
-			&& !\OCP\Util::needUpgrade()
270
-			&& $pathInfo !== ''
271
-			&& !preg_match('/^\/login/', $pathInfo)
272
-			&& $renderAs !== TemplateResponse::RENDER_AS_ERROR
273
-		) {
274
-			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
275
-		} else {
276
-			// If we ignore the scss compiler,
277
-			// we need to load the guest css fallback
278
-			Util::addStyle('guest');
279
-			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
280
-		}
281
-
282
-		$page->assign('cssfiles', []);
283
-		$page->assign('printcssfiles', []);
284
-		$this->initialState->provideInitialState('core', 'versionHash', self::$versionHash);
285
-		foreach ($cssFiles as $info) {
286
-			$web = $info[1];
287
-			$file = $info[2];
288
-
289
-			if (str_ends_with($file, 'print.css')) {
290
-				$page->append('printcssfiles', $web . '/' . $file . $this->getVersionHashSuffix());
291
-			} else {
292
-				$suffix = $this->getVersionHashSuffix($web, $file);
293
-
294
-				if (!str_contains($file, '?v=')) {
295
-					$page->append('cssfiles', $web . '/' . $file . $suffix);
296
-				} else {
297
-					$page->append('cssfiles', $web . '/' . $file . '-' . substr($suffix, 3));
298
-				}
299
-			}
300
-		}
301
-
302
-		if ($request->isUserAgent([Request::USER_AGENT_CLIENT_IOS, Request::USER_AGENT_SAFARI, Request::USER_AGENT_SAFARI_MOBILE])) {
303
-			// Prevent auto zoom with iOS but still allow user zoom
304
-			// On chrome (and others) this does not work (will also disable user zoom)
305
-			$page->assign('viewport_maximum_scale', '1.0');
306
-		}
307
-
308
-		$page->assign('initialStates', $this->initialState->getInitialStates());
309
-
310
-		$page->assign('id-app-content', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-content' : '#content');
311
-		$page->assign('id-app-navigation', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-navigation' : null);
312
-
313
-		return $page;
314
-	}
315
-
316
-	protected function getVersionHashSuffix(string $path = '', string $file = ''): string {
317
-		if ($this->config->getSystemValueBool('debug', false)) {
318
-			// allows chrome workspace mapping in debug mode
319
-			return '';
320
-		}
321
-
322
-		if ($this->config->getSystemValueBool('installed', false) === false) {
323
-			// if not installed just return the version hash
324
-			return '?v=' . self::$versionHash;
325
-		}
326
-
327
-		$hash = false;
328
-		// Try the web-root first
329
-		if ($path !== '') {
330
-			$hash = $this->getVersionHashByPath($path);
331
-		}
332
-		// If not found try the file
333
-		if ($hash === false && $file !== '') {
334
-			$hash = $this->getVersionHashByPath($file);
335
-		}
336
-		// As a last resort we use the server version hash
337
-		if ($hash === false) {
338
-			$hash = self::$versionHash;
339
-		}
340
-
341
-		// The theming app is force-enabled thus the cache buster is always available
342
-		$themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0');
343
-
344
-		return '?v=' . $hash . $themingSuffix;
345
-	}
346
-
347
-	private function getVersionHashByPath(string $path): string|false {
348
-		if (array_key_exists($path, self::$cacheBusterCache) === false) {
349
-			// Not yet cached, so lets find the cache buster string
350
-			$appId = $this->getAppNamefromPath($path);
351
-			if ($appId === false) {
352
-				// No app Id could be guessed
353
-				return false;
354
-			}
355
-
356
-			if ($appId === 'core') {
357
-				// core is not a real app but the server itself
358
-				$hash = self::$versionHash;
359
-			} else {
360
-				$appVersion = $this->appManager->getAppVersion($appId);
361
-				// For shipped apps the app version is not a single source of truth, we rather also need to consider the Nextcloud version
362
-				if ($this->appManager->isShipped($appId)) {
363
-					$appVersion .= '-' . self::$versionHash;
364
-				}
365
-
366
-				$hash = substr(md5($appVersion), 0, 8);
367
-			}
368
-			self::$cacheBusterCache[$path] = $hash;
369
-		}
370
-
371
-		return self::$cacheBusterCache[$path];
372
-	}
373
-
374
-	public static function findStylesheetFiles(array $styles): array {
375
-		if (!self::$cssLocator) {
376
-			self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
377
-		}
378
-		self::$cssLocator->find($styles);
379
-		return self::$cssLocator->getResources();
380
-	}
381
-
382
-	public function getAppNamefromPath(string $path): string|false {
383
-		if ($path !== '') {
384
-			$pathParts = explode('/', $path);
385
-			if ($pathParts[0] === 'css') {
386
-				// This is a scss request
387
-				return $pathParts[1];
388
-			} elseif ($pathParts[0] === 'core') {
389
-				return 'core';
390
-			}
391
-			return end($pathParts);
392
-		}
393
-		return false;
394
-	}
395
-
396
-	public static function findJavascriptFiles(array $scripts): array {
397
-		if (!self::$jsLocator) {
398
-			self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
399
-		}
400
-		self::$jsLocator->find($scripts);
401
-		return self::$jsLocator->getResources();
402
-	}
403
-
404
-	/**
405
-	 * Converts the absolute file path to a relative path from \OC::$SERVERROOT
406
-	 * @param string $filePath Absolute path
407
-	 * @return string Relative path
408
-	 * @throws \Exception If $filePath is not under \OC::$SERVERROOT
409
-	 */
410
-	public static function convertToRelativePath(string $filePath) {
411
-		$relativePath = explode(\OC::$SERVERROOT, $filePath);
412
-		if (count($relativePath) !== 2) {
413
-			throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
414
-		}
415
-
416
-		return $relativePath[1];
417
-	}
38
+    private static string $versionHash = '';
39
+    /** @var string[] */
40
+    private static array $cacheBusterCache = [];
41
+
42
+    public static ?CSSResourceLocator $cssLocator = null;
43
+    public static ?JSResourceLocator $jsLocator = null;
44
+
45
+    public function __construct(
46
+        private IConfig $config,
47
+        private IAppManager $appManager,
48
+        private InitialStateService $initialState,
49
+        private INavigationManager $navigationManager,
50
+        private ITemplateManager $templateManager,
51
+        private ServerVersion $serverVersion,
52
+    ) {
53
+    }
54
+
55
+    public function getPageTemplate(string $renderAs, string $appId): ITemplate {
56
+        // Add fallback theming variables if not rendered as user
57
+        if ($renderAs !== TemplateResponse::RENDER_AS_USER) {
58
+            // TODO cache generated default theme if enabled for fallback if server is erroring ?
59
+            Util::addStyle('theming', 'default');
60
+        }
61
+
62
+        // Decide which page we show
63
+        switch ($renderAs) {
64
+            case TemplateResponse::RENDER_AS_USER:
65
+                $page = $this->templateManager->getTemplate('core', 'layout.user');
66
+                if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
67
+                    $page->assign('bodyid', 'body-settings');
68
+                } else {
69
+                    $page->assign('bodyid', 'body-user');
70
+                }
71
+
72
+                $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
73
+                $this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));
74
+
75
+                if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
76
+                    $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
77
+                    $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
78
+                    $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
79
+                    Util::addScript('core', 'legacy-unified-search', 'core');
80
+                } else {
81
+                    Util::addScript('core', 'unified-search', 'core');
82
+                }
83
+                // Set body data-theme
84
+                $page->assign('enabledThemes', []);
85
+                if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
86
+                    $themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
87
+                    $page->assign('enabledThemes', $themesService->getEnabledThemes());
88
+                }
89
+
90
+                // Set logo link target
91
+                $logoUrl = $this->config->getSystemValueString('logo_url', '');
92
+                $page->assign('logoUrl', $logoUrl);
93
+
94
+                // Set default entry name
95
+                $defaultEntryId = $this->navigationManager->getDefaultEntryIdForUser();
96
+                $defaultEntry = $this->navigationManager->get($defaultEntryId);
97
+                $page->assign('defaultAppName', $defaultEntry['name'] ?? '');
98
+
99
+                // Add navigation entry
100
+                $page->assign('application', '');
101
+                $page->assign('appid', $appId);
102
+
103
+                $navigation = $this->navigationManager->getAll();
104
+                $page->assign('navigation', $navigation);
105
+                $settingsNavigation = $this->navigationManager->getAll('settings');
106
+                $this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation);
107
+
108
+                foreach ($navigation as $entry) {
109
+                    if ($entry['active']) {
110
+                        $page->assign('application', $entry['name']);
111
+                        break;
112
+                    }
113
+                }
114
+
115
+                foreach ($settingsNavigation as $entry) {
116
+                    if ($entry['active']) {
117
+                        $page->assign('application', $entry['name']);
118
+                        break;
119
+                    }
120
+                }
121
+
122
+                $user = Server::get(IUserSession::class)->getUser();
123
+
124
+                if ($user === null) {
125
+                    $page->assign('user_uid', false);
126
+                    $page->assign('user_displayname', false);
127
+                    $page->assign('userAvatarSet', false);
128
+                    $page->assign('userStatus', false);
129
+                } else {
130
+                    $page->assign('user_uid', $user->getUID());
131
+                    $page->assign('user_displayname', $user->getDisplayName());
132
+                    $page->assign('userAvatarSet', true);
133
+                    $page->assign('userAvatarVersion', $this->config->getUserValue($user->getUID(), 'avatar', 'version', 0));
134
+                }
135
+                break;
136
+            case TemplateResponse::RENDER_AS_ERROR:
137
+                $page = $this->templateManager->getTemplate('core', 'layout.guest', '', false);
138
+                $page->assign('bodyid', 'body-login');
139
+                $page->assign('user_displayname', '');
140
+                $page->assign('user_uid', '');
141
+                break;
142
+            case TemplateResponse::RENDER_AS_GUEST:
143
+                $page = $this->templateManager->getTemplate('core', 'layout.guest');
144
+                Util::addStyle('guest');
145
+                $page->assign('bodyid', 'body-login');
146
+
147
+                $userDisplayName = false;
148
+                $user = Server::get(IUserSession::class)->getUser();
149
+                if ($user) {
150
+                    $userDisplayName = $user->getDisplayName();
151
+                }
152
+
153
+                $page->assign('enabledThemes', []);
154
+                if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
155
+                    $themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
156
+                    $page->assign('enabledThemes', $themesService->getEnabledThemes());
157
+                }
158
+
159
+                $page->assign('user_displayname', $userDisplayName);
160
+                $page->assign('user_uid', \OC_User::getUser());
161
+                break;
162
+            case TemplateResponse::RENDER_AS_PUBLIC:
163
+                $page = $this->templateManager->getTemplate('core', 'layout.public');
164
+                $page->assign('appid', $appId);
165
+                $page->assign('bodyid', 'body-public');
166
+
167
+                // Set body data-theme
168
+                $page->assign('enabledThemes', []);
169
+                if ($this->appManager->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
170
+                    $themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
171
+                    $page->assign('enabledThemes', $themesService->getEnabledThemes());
172
+                }
173
+
174
+                // Set logo link target
175
+                $logoUrl = $this->config->getSystemValueString('logo_url', '');
176
+                $page->assign('logoUrl', $logoUrl);
177
+
178
+                $subscription = Server::get(IRegistry::class);
179
+                $showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
180
+                if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
181
+                    $showSimpleSignup = false;
182
+                }
183
+
184
+                $defaultSignUpLink = 'https://nextcloud.com/signup/';
185
+                $signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
186
+                if ($signUpLink !== $defaultSignUpLink) {
187
+                    $showSimpleSignup = true;
188
+                }
189
+
190
+                if ($this->appManager->isEnabledForUser('registration')) {
191
+                    $urlGenerator = Server::get(IURLGenerator::class);
192
+                    $signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
193
+                }
194
+
195
+                $page->assign('showSimpleSignUpLink', $showSimpleSignup);
196
+                $page->assign('signUpLink', $signUpLink);
197
+                break;
198
+            default:
199
+                $page = $this->templateManager->getTemplate('core', 'layout.base');
200
+                break;
201
+        }
202
+        // Send the language, locale, and direction to our layouts
203
+        $l10nFactory = Server::get(IFactory::class);
204
+        $lang = $l10nFactory->findLanguage();
205
+        $locale = $l10nFactory->findLocale($lang);
206
+        $direction = $l10nFactory->getLanguageDirection($lang);
207
+
208
+        $lang = str_replace('_', '-', $lang);
209
+        $page->assign('language', $lang);
210
+        $page->assign('locale', $locale);
211
+        $page->assign('direction', $direction);
212
+
213
+        if ($this->config->getSystemValueBool('installed', false)) {
214
+            if (empty(self::$versionHash)) {
215
+                $v = $this->appManager->getAppInstalledVersions();
216
+                $v['core'] = implode('.', $this->serverVersion->getVersion());
217
+                self::$versionHash = substr(md5(implode(',', $v)), 0, 8);
218
+            }
219
+        } else {
220
+            self::$versionHash = md5('not installed');
221
+        }
222
+
223
+        // Add the js files
224
+        $jsFiles = self::findJavascriptFiles(Util::getScripts());
225
+        $page->assign('jsfiles', []);
226
+        if ($this->config->getSystemValueBool('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) {
227
+            // this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call)
228
+            // see https://github.com/nextcloud/server/pull/22636 for details
229
+            $jsConfigHelper = new JSConfigHelper(
230
+                $this->serverVersion,
231
+                \OCP\Util::getL10N('lib'),
232
+                \OCP\Server::get(Defaults::class),
233
+                $this->appManager,
234
+                \OC::$server->getSession(),
235
+                \OC::$server->getUserSession()->getUser(),
236
+                $this->config,
237
+                \OC::$server->getGroupManager(),
238
+                \OC::$server->get(IniGetWrapper::class),
239
+                \OC::$server->getURLGenerator(),
240
+                \OC::$server->get(CapabilitiesManager::class),
241
+                \OCP\Server::get(IInitialStateService::class),
242
+                \OCP\Server::get(IProvider::class),
243
+                \OCP\Server::get(FilenameValidator::class),
244
+            );
245
+            $config = $jsConfigHelper->getConfig();
246
+            if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) {
247
+                $page->assign('inline_ocjs', $config);
248
+            } else {
249
+                $page->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash]));
250
+            }
251
+        }
252
+        foreach ($jsFiles as $info) {
253
+            $web = $info[1];
254
+            $file = $info[2];
255
+            $page->append('jsfiles', $web . '/' . $file . $this->getVersionHashSuffix());
256
+        }
257
+
258
+        $request = \OCP\Server::get(IRequest::class);
259
+
260
+        try {
261
+            $pathInfo = $request->getPathInfo();
262
+        } catch (\Exception $e) {
263
+            $pathInfo = '';
264
+        }
265
+
266
+        // Do not initialise scss appdata until we have a fully installed instance
267
+        // Do not load scss for update, errors, installation or login page
268
+        if ($this->config->getSystemValueBool('installed', false)
269
+            && !\OCP\Util::needUpgrade()
270
+            && $pathInfo !== ''
271
+            && !preg_match('/^\/login/', $pathInfo)
272
+            && $renderAs !== TemplateResponse::RENDER_AS_ERROR
273
+        ) {
274
+            $cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
275
+        } else {
276
+            // If we ignore the scss compiler,
277
+            // we need to load the guest css fallback
278
+            Util::addStyle('guest');
279
+            $cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
280
+        }
281
+
282
+        $page->assign('cssfiles', []);
283
+        $page->assign('printcssfiles', []);
284
+        $this->initialState->provideInitialState('core', 'versionHash', self::$versionHash);
285
+        foreach ($cssFiles as $info) {
286
+            $web = $info[1];
287
+            $file = $info[2];
288
+
289
+            if (str_ends_with($file, 'print.css')) {
290
+                $page->append('printcssfiles', $web . '/' . $file . $this->getVersionHashSuffix());
291
+            } else {
292
+                $suffix = $this->getVersionHashSuffix($web, $file);
293
+
294
+                if (!str_contains($file, '?v=')) {
295
+                    $page->append('cssfiles', $web . '/' . $file . $suffix);
296
+                } else {
297
+                    $page->append('cssfiles', $web . '/' . $file . '-' . substr($suffix, 3));
298
+                }
299
+            }
300
+        }
301
+
302
+        if ($request->isUserAgent([Request::USER_AGENT_CLIENT_IOS, Request::USER_AGENT_SAFARI, Request::USER_AGENT_SAFARI_MOBILE])) {
303
+            // Prevent auto zoom with iOS but still allow user zoom
304
+            // On chrome (and others) this does not work (will also disable user zoom)
305
+            $page->assign('viewport_maximum_scale', '1.0');
306
+        }
307
+
308
+        $page->assign('initialStates', $this->initialState->getInitialStates());
309
+
310
+        $page->assign('id-app-content', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-content' : '#content');
311
+        $page->assign('id-app-navigation', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-navigation' : null);
312
+
313
+        return $page;
314
+    }
315
+
316
+    protected function getVersionHashSuffix(string $path = '', string $file = ''): string {
317
+        if ($this->config->getSystemValueBool('debug', false)) {
318
+            // allows chrome workspace mapping in debug mode
319
+            return '';
320
+        }
321
+
322
+        if ($this->config->getSystemValueBool('installed', false) === false) {
323
+            // if not installed just return the version hash
324
+            return '?v=' . self::$versionHash;
325
+        }
326
+
327
+        $hash = false;
328
+        // Try the web-root first
329
+        if ($path !== '') {
330
+            $hash = $this->getVersionHashByPath($path);
331
+        }
332
+        // If not found try the file
333
+        if ($hash === false && $file !== '') {
334
+            $hash = $this->getVersionHashByPath($file);
335
+        }
336
+        // As a last resort we use the server version hash
337
+        if ($hash === false) {
338
+            $hash = self::$versionHash;
339
+        }
340
+
341
+        // The theming app is force-enabled thus the cache buster is always available
342
+        $themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0');
343
+
344
+        return '?v=' . $hash . $themingSuffix;
345
+    }
346
+
347
+    private function getVersionHashByPath(string $path): string|false {
348
+        if (array_key_exists($path, self::$cacheBusterCache) === false) {
349
+            // Not yet cached, so lets find the cache buster string
350
+            $appId = $this->getAppNamefromPath($path);
351
+            if ($appId === false) {
352
+                // No app Id could be guessed
353
+                return false;
354
+            }
355
+
356
+            if ($appId === 'core') {
357
+                // core is not a real app but the server itself
358
+                $hash = self::$versionHash;
359
+            } else {
360
+                $appVersion = $this->appManager->getAppVersion($appId);
361
+                // For shipped apps the app version is not a single source of truth, we rather also need to consider the Nextcloud version
362
+                if ($this->appManager->isShipped($appId)) {
363
+                    $appVersion .= '-' . self::$versionHash;
364
+                }
365
+
366
+                $hash = substr(md5($appVersion), 0, 8);
367
+            }
368
+            self::$cacheBusterCache[$path] = $hash;
369
+        }
370
+
371
+        return self::$cacheBusterCache[$path];
372
+    }
373
+
374
+    public static function findStylesheetFiles(array $styles): array {
375
+        if (!self::$cssLocator) {
376
+            self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
377
+        }
378
+        self::$cssLocator->find($styles);
379
+        return self::$cssLocator->getResources();
380
+    }
381
+
382
+    public function getAppNamefromPath(string $path): string|false {
383
+        if ($path !== '') {
384
+            $pathParts = explode('/', $path);
385
+            if ($pathParts[0] === 'css') {
386
+                // This is a scss request
387
+                return $pathParts[1];
388
+            } elseif ($pathParts[0] === 'core') {
389
+                return 'core';
390
+            }
391
+            return end($pathParts);
392
+        }
393
+        return false;
394
+    }
395
+
396
+    public static function findJavascriptFiles(array $scripts): array {
397
+        if (!self::$jsLocator) {
398
+            self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
399
+        }
400
+        self::$jsLocator->find($scripts);
401
+        return self::$jsLocator->getResources();
402
+    }
403
+
404
+    /**
405
+     * Converts the absolute file path to a relative path from \OC::$SERVERROOT
406
+     * @param string $filePath Absolute path
407
+     * @return string Relative path
408
+     * @throws \Exception If $filePath is not under \OC::$SERVERROOT
409
+     */
410
+    public static function convertToRelativePath(string $filePath) {
411
+        $relativePath = explode(\OC::$SERVERROOT, $filePath);
412
+        if (count($relativePath) !== 2) {
413
+            throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
414
+        }
415
+
416
+        return $relativePath[1];
417
+    }
418 418
 }
Please login to merge, or discard this patch.
core/templates/layout.guest.php 2 patches
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -15,14 +15,14 @@  discard block
 block discarded – undo
15 15
 		<meta charset="utf-8">
16 16
 		<title>
17 17
 			<?php
18
-				p(!empty($_['pageTitle']) ? $_['pageTitle'] . ' – ' : '');
18
+                p(!empty($_['pageTitle']) ? $_['pageTitle'] . ' – ' : '');
19 19
 p($theme->getTitle());
20 20
 ?>
21 21
 		</title>
22 22
 		<meta name="csp-nonce" nonce="<?php p($_['cspNonce']); /* Do not pass into "content" to prevent exfiltration */ ?>">
23 23
 		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0<?php if (isset($_['viewport_maximum_scale'])) {
24
-			p(', maximum-scale=' . $_['viewport_maximum_scale']);
25
-		} ?>">
24
+            p(', maximum-scale=' . $_['viewport_maximum_scale']);
25
+        } ?>">
26 26
 		<?php if ($theme->getiTunesAppId() !== '') { ?>
27 27
 		<meta name="apple-itunes-app" content="app-id=<?php p($theme->getiTunesAppId()); ?>">
28 28
 		<?php } ?>
@@ -36,8 +36,8 @@  discard block
 block discarded – undo
36 36
 		<?php print_unescaped($_['headers']); ?>
37 37
 	</head>
38 38
 	<body id="<?php p($_['bodyid']);?>" <?php foreach ($_['enabledThemes'] as $themeId) {
39
-		p("data-theme-$themeId ");
40
-	}?> data-themes="<?php p(join(',', $_['enabledThemes'])) ?>">
39
+        p("data-theme-$themeId ");
40
+    }?> data-themes="<?php p(join(',', $_['enabledThemes'])) ?>">
41 41
 		<?php include 'layout.noscript.warning.php'; ?>
42 42
 		<?php include 'layout.initial-state.php'; ?>
43 43
 		<div class="wrapper">
@@ -58,11 +58,11 @@  discard block
 block discarded – undo
58 58
 			</div>
59 59
 		</div>
60 60
 		<?php
61
-		$longFooter = $theme->getLongFooter();
61
+        $longFooter = $theme->getLongFooter();
62 62
 ?>
63 63
 		<footer class="guest-box <?php if ($longFooter === '') {
64
-			p('hidden');
65
-		} ?>">
64
+            p('hidden');
65
+        } ?>">
66 66
 			<p class="info">
67 67
 				<?php print_unescaped($longFooter); ?>
68 68
 			</p>
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -15,13 +15,13 @@  discard block
 block discarded – undo
15 15
 		<meta charset="utf-8">
16 16
 		<title>
17 17
 			<?php
18
-				p(!empty($_['pageTitle']) ? $_['pageTitle'] . ' – ' : '');
18
+				p(!empty($_['pageTitle']) ? $_['pageTitle'].' – ' : '');
19 19
 p($theme->getTitle());
20 20
 ?>
21 21
 		</title>
22 22
 		<meta name="csp-nonce" nonce="<?php p($_['cspNonce']); /* Do not pass into "content" to prevent exfiltration */ ?>">
23 23
 		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0<?php if (isset($_['viewport_maximum_scale'])) {
24
-			p(', maximum-scale=' . $_['viewport_maximum_scale']);
24
+			p(', maximum-scale='.$_['viewport_maximum_scale']);
25 25
 		} ?>">
26 26
 		<?php if ($theme->getiTunesAppId() !== '') { ?>
27 27
 		<meta name="apple-itunes-app" content="app-id=<?php p($theme->getiTunesAppId()); ?>">
@@ -35,7 +35,7 @@  discard block
 block discarded – undo
35 35
 		<?php emit_script_loading_tags($_); ?>
36 36
 		<?php print_unescaped($_['headers']); ?>
37 37
 	</head>
38
-	<body id="<?php p($_['bodyid']);?>" <?php foreach ($_['enabledThemes'] as $themeId) {
38
+	<body id="<?php p($_['bodyid']); ?>" <?php foreach ($_['enabledThemes'] as $themeId) {
39 39
 		p("data-theme-$themeId ");
40 40
 	}?> data-themes="<?php p(join(',', $_['enabledThemes'])) ?>">
41 41
 		<?php include 'layout.noscript.warning.php'; ?>
Please login to merge, or discard this patch.