aboutsummaryrefslogtreecommitdiffstats
path: root/tests/unit/droppable/droppable.html
Commit message (Expand)AuthorAgeFilesLines
* Core: Make back compat disabled by defaultMichał Gołębiowski-Owczarek2024-05-131-2/+1
* Tests: Fix AMD loading in IE8Scott González2015-04-171-0/+1
* Droppable: Convert tests to new infrastructureScott González2015-04-091-30/+3
* Droppable: Use new has/lacksClasses assertions for all class checksAlexander Schmitz2015-03-111-0/+1
* Droppable: Add classes optionAlexander Schmitz2015-03-111-0/+3
* Build: Pull in jquery.simulate.js from BowerMike Sherov2014-08-221-1/+1
* Build: Reorganize external directoryScott González2014-06-241-2/+2
* Tests: Update to sane QUnit markupJörn Zaefferer2014-02-201-5/+1
* All: Rename all files, removing the "jquery.ui." prefix;Rafael Xavier de Souza2014-01-241-6/+6
* Droppable: Changed drop event to loop over a copied array instead of the drop...Zaven Muradyan2013-02-261-0/+1
* Dev: remove globals from tests and moved test helpers to correct location - F...Mike Sherov2012-11-031-0/+1
* Tests: Removed old code that kept bad test suites out of TestSwarm.Scott González2012-10-261-10/+0
* Tests: Moved loadResources to TestHelpers.Scott González2012-04-231-7/+5
* Tests: Rename {widget}_defaults.js -> {widget}_common.jsScott González2012-04-231-1/+1
* Tests: Added ability to run tests against minified files via ?min=1Scott González2011-10-281-5/+13
* Tests: Cleanup.Scott González2011-08-031-27/+26
* Tests: Added ability to run tests suites against different versions of jQuery...Scott González2011-07-281-1/+1
* Upgrade jQuery to 1.6.2.Scott González2011-07-251-1/+1
* Droppable: Removed empty tickets test file.Scott González2011-03-281-1/+0
* Switched to latest stable jQuery 1.5.1Richard Worth2011-03-021-1/+1
* Use #qunit-fixture instead of deprecated #main. Fixes #7031 - Tests usejzaefferer2011-02-231-1/+1
* Unit tests: Add QUnit toolbar for hiding passed testsjzaefferer2011-02-211-0/+1
* Upgraded jQuery to 1.4.4.Scott González2010-11-121-1/+1
* Upgraded jQuery to 1.4.3.Scott González2010-10-211-1/+1
* Seperated testswarm injection from testsuite extensions, to fix 'failing' tes...jzaefferer2010-08-291-1/+2
* Unit tests: Disable stale interaction tests only on testswarm, but allow them...jzaefferer2010-04-141-4/+12
* Tuning disabled testsuites to run with ?draggable that TestSwarm addsjzaefferer2010-04-011-1/+1
* Disabling interaction testsuites until we get to their rewrites; required to ...jzaefferer2010-04-011-0/+3
* html pages: added HTML5 meta charset UTF-8 tag, changed DOCTYPE to uppercaseRichard Worth2010-03-121-1/+2
* Externals: updated QUnit to http://github.com/jquery/qunit commit 67ba5a338d8...Richard Worth2010-02-251-4/+10
* Fixed #5182 - Update to jQuery 1.4.2Richard Worth2010-02-161-1/+1
* switched to jQuery 1.4.1Richard Worth2010-01-271-1/+1
* switched to jQuery 1.4Richard Worth2010-01-201-1/+1
* Split mouse into its own file and udpdated dependency lists.Scott González2009-12-221-0/+1
* extracting widget factory into jquery.ui.widget.jsJörn Zaefferer2009-12-161-0/+1
* update to latest QUnitJörn Zaefferer2009-09-291-1/+1
* renamed all ui.*.js files to jquery.ui.*.js, all effects.*.js files to jquery...Richard Worth2009-09-171-3/+3
* updated to latest QUnitJörn Zaefferer2009-09-151-1/+1
* change script ordering to load testswarm agent after QUnitJörn Zaefferer2009-09-111-1/+1
* update to lastest QUnitJörn Zaefferer2009-08-301-1/+1
* Removed jquery.simulate from external/ as it's being removed from jqueryjs.go...Richard Worth2009-08-101-1/+1
* replaced svn:externals with versioned files within trunk/externalsJörn Zaefferer2009-05-271-2/+2
* Switched to jQuery 1.3.2Richard Worth2009-02-201-1/+1
* droppable unit tests: created separate file for each module: core, common wid...Richard Worth2009-02-021-1/+6
* switched to jQuery 1.3.1Richard Worth2009-01-221-1/+1
* Happy Birthday jQuery (switched to jQuery 1.3)Richard Worth2009-01-141-1/+1
* switched to jQuery 1.3rc2Richard Worth2009-01-131-1/+1
* reverted r1544 (it was temporary)Richard Worth2009-01-081-1/+1
* temporarily switched back to 1.2.6Richard Worth2009-01-081-1/+1
* restructured unit tests folderRichard Worth2009-01-071-0/+27
table28 Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/TemplateLayout.php
blob: 93142444c72d2740f7cc495593b393ddd53227c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
<?php

/**
 * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
 * SPDX-License-Identifier: AGPL-3.0-only
 */
namespace OC;

use bantu\IniGetWrapper\IniGetWrapper;
use OC\AppFramework\Http\Request;
use OC\Authentication\Token\IProvider;
use OC\Files\FilenameValidator;
use OC\Search\SearchQuery;
use OC\Template\CSSResourceLocator;
use OC\Template\JSConfigHelper;
use OC\Template\JSResourceLocator;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Support\Subscription\IRegistry;
use OCP\Util;

class TemplateLayout extends \OC_Template {
	private static $versionHash = '';
	/** @var string[] */
	private static $cacheBusterCache = [];

	/** @var CSSResourceLocator|null */
	public static $cssLocator = null;

	/** @var JSResourceLocator|null */
	public static $jsLocator = null;

	private IConfig $config;
	private IAppManager $appManager;
	private InitialStateService $initialState;
	private INavigationManager $navigationManager;

	/**
	 * @param string $renderAs
	 * @param string $appId application id
	 */
	public function __construct($renderAs, $appId = '') {
		$this->config = \OCP\Server::get(IConfig::class);
		$this->appManager = \OCP\Server::get(IAppManager::class);
		$this->initialState = \OCP\Server::get(InitialStateService::class);
		$this->navigationManager = \OCP\Server::get(INavigationManager::class);

		// Add fallback theming variables if not rendered as user
		if ($renderAs !== TemplateResponse::RENDER_AS_USER) {
			// TODO cache generated default theme if enabled for fallback if server is erroring ?
			Util::addStyle('theming', 'default');
		}

		// Decide which page we show
		if ($renderAs === TemplateResponse::RENDER_AS_USER) {
			parent::__construct('core', 'layout.user');
			if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
				$this->assign('bodyid', 'body-settings');
			} else {
				$this->assign('bodyid', 'body-user');
			}

			$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
			$this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));

			if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
				$this->initialState->provideInitialState('unified-search', 'limit-default', (int) $this->config->getAppValue('core', 'unified-search.limit-default', (string) SearchQuery::LIMIT_DEFAULT));
				$this->initialState->provideInitialState('unified-search', 'min-search-length', (int) $this->config->getAppValue('core', 'unified-search.min-search-length', (string) 1));
				$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
				Util::addScript('core', 'legacy-unified-search', 'core');
			} else {
				Util::addScript('core', 'unified-search', 'core');
			}

			// Set logo link target
			$logoUrl = $this->config->getSystemValueString('logo_url', '');
			$this->assign('logoUrl', $logoUrl);

			// Set default app name
			$defaultApp = $this->appManager->getDefaultAppForUser();
			$defaultAppInfo = $this->appManager->getAppInfo($defaultApp);
			$l10n = \OC::$server->get(IFactory::class)->get($defaultApp);
			$this->assign('defaultAppName', $l10n->t($defaultAppInfo['name']));

			// Add navigation entry
			$this->assign('application', '');
			$this->assign('appid', $appId);

			$navigation = $this->navigationManager->getAll();
			$this->assign('navigation', $navigation);
			$settingsNavigation = $this->navigationManager->getAll('settings');
			$this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation);

			foreach ($navigation as $entry) {
				if ($entry['active']) {
					$this->assign('application', $entry['name']);
					break;
				}
			}

			foreach ($settingsNavigation as $entry) {
				if ($entry['active']) {
					$this->assign('application', $entry['name']);
					break;
				}
			}

			$userDisplayName = false;
			$user = \OC::$server->get(IUserSession::class)->getUser();
			if ($user) {
				$userDisplayName = $user->getDisplayName();
			}
			$this->assign('user_displayname', $userDisplayName);
			$this->assign('user_uid', \OC_User::getUser());

			if ($user === null) {
				$this->assign('userAvatarSet', false);
				$this->assign('userStatus', false);
			} else {
				$this->assign('userAvatarSet', true);
				$this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0));
			}
		} elseif ($renderAs === TemplateResponse::RENDER_AS_ERROR) {
			parent::__construct('core', 'layout.guest', '', false);
			$this->assign('bodyid', 'body-login');
			$this->assign('user_displayname', '');
			$this->assign('user_uid', '');
		} elseif ($renderAs === TemplateResponse::RENDER_AS_GUEST) {
			parent::__construct('core', 'layout.guest');
			\OC_Util::addStyle('guest');
			$this->assign('bodyid', 'body-login');

			$userDisplayName = false;
			$user = \OC::$server->get(IUserSession::class)->getUser();
			if ($user) {
				$userDisplayName = $user->getDisplayName();
			}
			$this->assign('user_displayname', $userDisplayName);
			$this->assign('user_uid', \OC_User::getUser());
		} elseif ($renderAs === TemplateResponse::RENDER_AS_PUBLIC) {
			parent::__construct('core', 'layout.public');
			$this->assign('appid', $appId);
			$this->assign('bodyid', 'body-public');

			// Set logo link target
			$logoUrl = $this->config->getSystemValueString('logo_url', '');
			$this->assign('logoUrl', $logoUrl);

			/** @var IRegistry $subscription */
			$subscription = \OCP\Server::get(IRegistry::class);
			$showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
			if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
				$showSimpleSignup = false;
			}

			$defaultSignUpLink = 'https://nextcloud.com/signup/';
			$signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
			if ($signUpLink !== $defaultSignUpLink) {
				$showSimpleSignup = true;
			}

			if ($this->appManager->isEnabledForUser('registration')) {
				$urlGenerator = \OCP\Server::get(IURLGenerator::class);
				$signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
			}

			$this->assign('showSimpleSignUpLink', $showSimpleSignup);
			$this->assign('signUpLink', $signUpLink);
		} else {
			parent::__construct('core', 'layout.base');
		}

		// Set body data-theme
		try {
			$themesService = \OCP\Server::get(\OCA\Theming\Service\ThemesService::class);
		} catch (\OCP\AppFramework\QueryException) {
			$themesService = null;
		}
		$this->assign('enabledThemes', $themesService?->getEnabledThemes() ?? []);

		// Send the language, locale, and direction to our layouts
		$lang = \OC::$server->get(IFactory::class)->findLanguage();
		$locale = \OC::$server->get(IFactory::class)->findLocale($lang);

		$lang = str_replace('_', '-', $lang);
		$this->assign('language', $lang);
		$this->assign('locale', $locale);

		if ($this->config->getSystemValueBool('installed', false)) {
			if (empty(self::$versionHash)) {
				$v = \OC_App::getAppVersions();
				$v['core'] = implode('.', \OCP\Util::getVersion());
				self::$versionHash = substr(md5(implode(',', $v)), 0, 8);
			}
		} else {
			self::$versionHash = md5('not installed');
		}

		// Add the js files
		// TODO: remove deprecated OC_Util injection
		$jsFiles = self::findJavascriptFiles(array_merge(\OC_Util::$scripts, Util::getScripts()));
		$this->assign('jsfiles', []);
		if ($this->config->getSystemValueBool('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) {
			// this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call)
			// see https://github.com/nextcloud/server/pull/22636 for details
			$jsConfigHelper = new JSConfigHelper(
				\OCP\Util::getL10N('lib'),
				\OCP\Server::get(Defaults::class),
				$this->appManager,
				\OC::$server->getSession(),
				\OC::$server->getUserSession()->getUser(),
				$this->config,
				\OC::$server->getGroupManager(),
				\OC::$server->get(IniGetWrapper::class),
				\OC::$server->getURLGenerator(),
				\OC::$server->get(CapabilitiesManager::class),
				\OCP\Server::get(IInitialStateService::class),
				\OCP\Server::get(IProvider::class),
				\OCP\Server::get(FilenameValidator::class),
			);
			$config = $jsConfigHelper->getConfig();
			if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) {
				$this->assign('inline_ocjs', $config);
			} else {
				$this->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash]));
			}
		}
		foreach ($jsFiles as $info) {
			$web = $info[1];
			$file = $info[2];
			$this->append('jsfiles', $web.'/'.$file . $this->getVersionHashSuffix());
		}

		try {
			$pathInfo = \OC::$server->getRequest()->getPathInfo();
		} catch (\Exception $e) {
			$pathInfo = '';
		}

		// Do not initialise scss appdata until we have a fully installed instance
		// Do not load scss for update, errors, installation or login page
		if (\OC::$server->getSystemConfig()->getValue('installed', false)
			&& !\OCP\Util::needUpgrade()
			&& $pathInfo !== ''
			&& $pathInfo !== false
			&& !preg_match('/^\/login/', $pathInfo)
			&& $renderAs !== TemplateResponse::RENDER_AS_ERROR
		) {
			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
		} else {
			// If we ignore the scss compiler,
			// we need to load the guest css fallback
			\OC_Util::addStyle('guest');
			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
		}

		$this->assign('cssfiles', []);
		$this->assign('printcssfiles', []);
		$this->initialState->provideInitialState('core', 'versionHash', self::$versionHash);
		foreach ($cssFiles as $info) {
			$web = $info[1];
			$file = $info[2];

			if (str_ends_with($file, 'print.css')) {
				$this->append('printcssfiles', $web.'/'.$file . $this->getVersionHashSuffix());
			} else {
				$suffix = $this->getVersionHashSuffix($web, $file);

				if (!str_contains($file, '?v=')) {
					$this->append('cssfiles', $web.'/'.$file . $suffix);
				} else {
					$this->append('cssfiles', $web.'/'.$file . '-' . substr($suffix, 3));
				}
			}
		}

		$request = \OCP\Server::get(IRequest::class);
		if ($request->isUserAgent([Request::USER_AGENT_CLIENT_IOS, Request::USER_AGENT_SAFARI, Request::USER_AGENT_SAFARI_MOBILE])) {
			// Prevent auto zoom with iOS but still allow user zoom
			// On chrome (and others) this does not work (will also disable user zoom)
			$this->assign('viewport_maximum_scale', '1.0');
		}

		$this->assign('initialStates', $this->initialState->getInitialStates());

		$this->assign('id-app-content', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-content' : '#content');
		$this->assign('id-app-navigation', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-navigation' : null);
	}

	protected function getVersionHashSuffix(string $path = '', string $file = ''): string {
		if ($this->config->getSystemValueBool('debug', false)) {
			// allows chrome workspace mapping in debug mode
			return '';
		}

		if ($this->config->getSystemValueBool('installed', false) === false) {
			// if not installed just return the version hash
			return '?v=' . self::$versionHash;
		}

		$hash = false;
		// Try the web-root first
		if ($path !== '') {
			$hash = $this->getVersionHashByPath($path);
		}
		// If not found try the file
		if ($hash === false && $file !== '') {
			$hash = $this->getVersionHashByPath($file);
		}
		// As a last resort we use the server version hash
		if ($hash === false) {
			$hash = self::$versionHash;
		}

		// The theming app is force-enabled thus the cache buster is always available
		$themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0');

		return '?v=' . $hash . $themingSuffix;
	}

	private function getVersionHashByPath(string $path): string|false {
		if (array_key_exists($path, self::$cacheBusterCache) === false) {
			// Not yet cached, so lets find the cache buster string
			$appId = $this->getAppNamefromPath($path);
			if ($appId === false) {
				// No app Id could be guessed
				return false;
			}

			if ($appId === 'core') {
				// core is not a real app but the server itself
				$hash = self::$versionHash;
			} else {
				$appVersion = $this->appManager->getAppVersion($appId);
				// For shipped apps the app version is not a single source of truth, we rather also need to consider the Nextcloud version
				if ($this->appManager->isShipped($appId)) {
					$appVersion .= '-' . self::$versionHash;
				}

				$hash = substr(md5($appVersion), 0, 8);
			}
			self::$cacheBusterCache[$path] = $hash;
		}

		return self::$cacheBusterCache[$path];
	}

	/**
	 * @param array $styles
	 * @return array
	 */
	public static function findStylesheetFiles($styles) {
		if (!self::$cssLocator) {
			self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
		}
		self::$cssLocator->find($styles);
		return self::$cssLocator->getResources();
	}

	/**
	 * @return string|false
	 */
	public function getAppNamefromPath(string $path) {
		if ($path !== '') {
			$pathParts = explode('/', $path);
			if ($pathParts[0] === 'css') {
				// This is a scss request
				return $pathParts[1];
			} elseif ($pathParts[0] === 'core') {
				return 'core';
			}
			return end($pathParts);
		}
		return false;
	}

	/**
	 * @param array $scripts
	 * @return array
	 */
	public static function findJavascriptFiles($scripts) {
		if (!self::$jsLocator) {
			self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
		}
		self::$jsLocator->find($scripts);
		return self::$jsLocator->getResources();
	}

	/**
	 * Converts the absolute file path to a relative path from \OC::$SERVERROOT
	 * @param string $filePath Absolute path
	 * @return string Relative path
	 * @throws \Exception If $filePath is not under \OC::$SERVERROOT
	 */
	public static function convertToRelativePath($filePath) {
		$relativePath = explode(\OC::$SERVERROOT, $filePath);
		if (count($relativePath) !== 2) {
			throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
		}

		return $relativePath[1];
	}
}