aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Template
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Template')
-rw-r--r--lib/private/Template/Base.php39
-rw-r--r--lib/private/Template/JSCombiner.php1
-rw-r--r--lib/private/Template/JSConfigHelper.php17
-rw-r--r--lib/private/Template/JSResourceLocator.php2
-rw-r--r--lib/private/Template/Template.php159
-rw-r--r--lib/private/Template/TemplateFileLocator.php29
-rw-r--r--lib/private/Template/TemplateManager.php169
-rw-r--r--lib/private/Template/functions.php299
8 files changed, 662 insertions, 53 deletions
diff --git a/lib/private/Template/Base.php b/lib/private/Template/Base.php
index 602c8e6257e..a13e6703960 100644
--- a/lib/private/Template/Base.php
+++ b/lib/private/Template/Base.php
@@ -8,11 +8,10 @@
namespace OC\Template;
use OCP\Defaults;
-use Throwable;
class Base {
private $template; // The template
- private $vars; // Vars
+ private array $vars = [];
/** @var \OCP\IL10N */
private $l10n;
@@ -59,11 +58,9 @@ class Base {
}
/**
- * @param string $serverRoot
- * @param string $theme
* @return string[]
*/
- protected function getCoreTemplateDirs($theme, $serverRoot) {
+ protected function getCoreTemplateDirs(string $theme, string $serverRoot): array {
return [
$serverRoot . '/themes/' . $theme . '/core/templates/',
$serverRoot . '/core/templates/',
@@ -72,30 +69,24 @@ class Base {
/**
* Assign variables
- * @param string $key key
- * @param float|array|bool|integer|string|Throwable $value value
- * @return bool
*
* This function assigns a variable. It can be accessed via $_[$key] in
* the template.
*
* If the key existed before, it will be overwritten
*/
- public function assign($key, $value) {
+ public function assign(string $key, mixed $value): void {
$this->vars[$key] = $value;
- return true;
}
/**
* Appends a variable
- * @param string $key key
- * @param mixed $value value
*
* This function assigns a variable in an array context. If the key already
* exists, the value will be appended. It can be accessed via
* $_[$key][$position] in the template.
*/
- public function append($key, $value) {
+ public function append(string $key, mixed $value): void {
if (array_key_exists($key, $this->vars)) {
$this->vars[$key][] = $value;
} else {
@@ -105,42 +96,29 @@ class Base {
/**
* Prints the proceeded template
- * @return bool
*
* This function proceeds the template and prints its output.
*/
- public function printPage() {
+ public function printPage(): void {
$data = $this->fetchPage();
- if ($data === false) {
- return false;
- } else {
- print $data;
- return true;
- }
+ print $data;
}
/**
* Process the template
*
- * @param array|null $additionalParams
- * @return string This function processes the template.
- *
* This function processes the template.
*/
- public function fetchPage($additionalParams = null) {
+ public function fetchPage(?array $additionalParams = null): string {
return $this->load($this->template, $additionalParams);
}
/**
* doing the actual work
*
- * @param string $file
- * @param array|null $additionalParams
- * @return string content
- *
* Includes the template file, fetches its output
*/
- protected function load($file, $additionalParams = null) {
+ protected function load(string $file, ?array $additionalParams = null): string {
// Register the variables
$_ = $this->vars;
$l = $this->l10n;
@@ -158,6 +136,7 @@ class Base {
// Include
ob_start();
try {
+ require_once __DIR__ . '/functions.php';
include $file;
$data = ob_get_contents();
} catch (\Exception $e) {
diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php
index 5fce3effb3f..a94f822a448 100644
--- a/lib/private/Template/JSCombiner.php
+++ b/lib/private/Template/JSCombiner.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index e6d3c6af87e..07e557d0706 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -12,7 +12,7 @@ use OC\Authentication\Token\IProvider;
use OC\CapabilitiesManager;
use OC\Files\FilenameValidator;
use OC\Share\Share;
-use OCA\Provisioning_API\Controller\AUserData;
+use OCA\Provisioning_API\Controller\AUserDataOCSController;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\Authentication\Exceptions\ExpiredTokenException;
@@ -30,6 +30,7 @@ use OCP\ILogger;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
+use OCP\Server;
use OCP\ServerVersion;
use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\Share\IManager as IShareManager;
@@ -66,9 +67,11 @@ class JSConfigHelper {
$backend = $this->currentUser->getBackend();
if ($backend instanceof IPasswordConfirmationBackend) {
- $userBackendAllowsPasswordConfirmation = $backend->canConfirmPassword($uid);
+ $userBackendAllowsPasswordConfirmation = $backend->canConfirmPassword($uid) && $this->canUserValidatePassword();
} elseif (isset($this->excludedUserBackEnds[$this->currentUser->getBackendClassName()])) {
$userBackendAllowsPasswordConfirmation = false;
+ } else {
+ $userBackendAllowsPasswordConfirmation = $this->canUserValidatePassword();
}
} else {
$uid = null;
@@ -78,7 +81,7 @@ class JSConfigHelper {
$apps_paths = [];
if ($this->currentUser === null) {
- $apps = $this->appManager->getInstalledApps();
+ $apps = $this->appManager->getEnabledApps();
} else {
$apps = $this->appManager->getEnabledAppsForUser($this->currentUser);
}
@@ -136,7 +139,7 @@ class JSConfigHelper {
$capabilities = $this->capabilitiesManager->getCapabilities(false, true);
- $userFirstDay = $this->config->getUserValue($uid, 'core', AUserData::USER_FIELD_FIRST_DAY_OF_WEEK, null);
+ $userFirstDay = $this->config->getUserValue($uid, 'core', AUserDataOCSController::USER_FIELD_FIRST_DAY_OF_WEEK, null);
$firstDay = (int)($userFirstDay ?? $this->l->l('firstday', null));
$config = [
@@ -161,6 +164,8 @@ class JSConfigHelper {
'enable_non-accessible_features' => $this->config->getSystemValueBool('enable_non-accessible_features', true),
];
+ $shareManager = Server::get(IShareManager::class);
+
$array = [
'_oc_debug' => $this->config->getSystemValue('debug', false) ? 'true' : 'false',
'_oc_isadmin' => $uid !== null && $this->groupManager->isAdmin($uid) ? 'true' : 'false',
@@ -235,11 +240,11 @@ class JSConfigHelper {
'defaultExpireDateEnforced' => $enforceDefaultExpireDate,
'enforcePasswordForPublicLink' => Util::isPublicLinkPasswordRequired(),
'enableLinkPasswordByDefault' => $enableLinkPasswordByDefault,
- 'sharingDisabledForUser' => Util::isSharingDisabledForUser(),
+ 'sharingDisabledForUser' => $shareManager->sharingDisabledForUser($uid),
'resharingAllowed' => Share::isResharingAllowed(),
'remoteShareAllowed' => $outgoingServer2serverShareEnabled,
'federatedCloudShareDoc' => $this->urlGenerator->linkToDocs('user-sharing-federated'),
- 'allowGroupSharing' => \OC::$server->get(IShareManager::class)->allowGroupSharing(),
+ 'allowGroupSharing' => $shareManager->allowGroupSharing(),
'defaultInternalExpireDateEnabled' => $defaultInternalExpireDateEnabled,
'defaultInternalExpireDate' => $defaultInternalExpireDate,
'defaultInternalExpireDateEnforced' => $defaultInternalExpireDateEnforced,
diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php
index aad999f939a..a6d2d13a2ad 100644
--- a/lib/private/Template/JSResourceLocator.php
+++ b/lib/private/Template/JSResourceLocator.php
@@ -69,7 +69,7 @@ class JSResourceLocator extends ResourceLocator {
|| $this->appendScriptIfExist($this->serverroot, "dist/$app-$scriptName")
|| $this->appendScriptIfExist($appRoot, $script, $appWebRoot)
|| $this->cacheAndAppendCombineJsonIfExist($this->serverroot, $script . '.json')
- || $this->cacheAndAppendCombineJsonIfExist($appRoot, $script . '.json', $appWebRoot)
+ || $this->cacheAndAppendCombineJsonIfExist($appRoot, $script . '.json', $app)
|| $this->appendScriptIfExist($this->serverroot, $theme_dir . 'core/' . $script)
|| $this->appendScriptIfExist($this->serverroot, 'core/' . $script)
|| (strpos($scriptName, '/') === -1 && ($this->appendScriptIfExist($this->serverroot, $theme_dir . "dist/core-$scriptName")
diff --git a/lib/private/Template/Template.php b/lib/private/Template/Template.php
new file mode 100644
index 00000000000..ee85562091f
--- /dev/null
+++ b/lib/private/Template/Template.php
@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace OC\Template;
+
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
+use OC\TemplateLayout;
+use OCP\App\AppPathNotFoundException;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Defaults;
+use OCP\Server;
+use OCP\Template\ITemplate;
+use OCP\Template\TemplateNotFoundException;
+use OCP\Util;
+
+class Template extends Base implements ITemplate {
+ private string $path;
+ private array $headers = [];
+
+ /**
+ * @param string $app app providing the template
+ * @param string $name of the template file (without suffix)
+ * @param TemplateResponse::RENDER_AS_* $renderAs If $renderAs is set, will try to
+ * produce a full page in the according layout.
+ * @throws TemplateNotFoundException
+ */
+ public function __construct(
+ protected string $app,
+ string $name,
+ private string $renderAs = TemplateResponse::RENDER_AS_BLANK,
+ bool $registerCall = true,
+ ) {
+ $theme = \OC_Util::getTheme();
+
+ $requestToken = ($registerCall ? Util::callRegister() : '');
+ $cspNonce = Server::get(ContentSecurityPolicyNonceManager::class)->getNonce();
+
+ // fix translation when app is something like core/lostpassword
+ $parts = explode('/', $app);
+ $l10n = Util::getL10N($parts[0]);
+
+ [$path, $template] = $this->findTemplate($theme, $app, $name);
+
+ $this->path = $path;
+
+ parent::__construct(
+ $template,
+ $requestToken,
+ $l10n,
+ Server::get(Defaults::class),
+ $cspNonce,
+ );
+ }
+
+
+ /**
+ * find the template with the given name
+ *
+ * Will select the template file for the selected theme.
+ * Checking all the possible locations.
+ *
+ * @param string $name of the template file (without suffix)
+ * @return array{string,string} Directory path and filename
+ * @throws TemplateNotFoundException
+ */
+ protected function findTemplate(string $theme, string $app, string $name): array {
+ // Check if it is a app template or not.
+ if ($app !== '') {
+ try {
+ $appDir = Server::get(IAppManager::class)->getAppPath($app);
+ } catch (AppPathNotFoundException) {
+ $appDir = false;
+ }
+ $dirs = $this->getAppTemplateDirs($theme, $app, \OC::$SERVERROOT, $appDir);
+ } else {
+ $dirs = $this->getCoreTemplateDirs($theme, \OC::$SERVERROOT);
+ }
+ $locator = new TemplateFileLocator($dirs);
+ return $locator->find($name);
+ }
+
+ /**
+ * Add a custom element to the header
+ * @param string $tag tag name of the element
+ * @param array $attributes array of attributes for the element
+ * @param string $text the text content for the element. If $text is null then the
+ * element will be written as empty element. So use "" to get a closing tag.
+ */
+ public function addHeader(string $tag, array $attributes, ?string $text = null): void {
+ $this->headers[] = [
+ 'tag' => $tag,
+ 'attributes' => $attributes,
+ 'text' => $text
+ ];
+ }
+
+ /**
+ * Process the template
+ *
+ * This function process the template. If $this->renderAs is set, it
+ * will produce a full page.
+ */
+ public function fetchPage(?array $additionalParams = null): string {
+ $data = parent::fetchPage($additionalParams);
+
+ if ($this->renderAs) {
+ $page = Server::get(TemplateLayout::class)->getPageTemplate($this->renderAs, $this->app);
+
+ if (is_array($additionalParams)) {
+ foreach ($additionalParams as $key => $value) {
+ $page->assign($key, $value);
+ }
+ }
+
+ // Add custom headers
+ $headers = '';
+ foreach (\OC_Util::$headers as $header) {
+ $headers .= '<' . Util::sanitizeHTML($header['tag']);
+ if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) {
+ $headers .= ' defer';
+ }
+ foreach ($header['attributes'] as $name => $value) {
+ $headers .= ' ' . Util::sanitizeHTML($name) . '="' . Util::sanitizeHTML($value) . '"';
+ }
+ if ($header['text'] !== null) {
+ $headers .= '>' . Util::sanitizeHTML($header['text']) . '</' . Util::sanitizeHTML($header['tag']) . '>';
+ } else {
+ $headers .= '/>';
+ }
+ }
+
+ $page->assign('headers', $headers);
+ $page->assign('content', $data);
+ return $page->fetchPage($additionalParams);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Include template
+ *
+ * @return string returns content of included template
+ *
+ * Includes another template. use <?php echo $this->inc('template'); ?> to
+ * do this.
+ */
+ public function inc(string $file, ?array $additionalParams = null): string {
+ return $this->load($this->path . $file . '.php', $additionalParams);
+ }
+}
diff --git a/lib/private/Template/TemplateFileLocator.php b/lib/private/Template/TemplateFileLocator.php
index 38583d158a3..11a568b5b21 100644
--- a/lib/private/Template/TemplateFileLocator.php
+++ b/lib/private/Template/TemplateFileLocator.php
@@ -1,29 +1,31 @@
<?php
+declare(strict_types=1);
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
+
namespace OC\Template;
-class TemplateFileLocator {
- protected $dirs;
- private $path;
+use OCP\Template\TemplateNotFoundException;
+class TemplateFileLocator {
/**
* @param string[] $dirs
*/
- public function __construct($dirs) {
- $this->dirs = $dirs;
+ public function __construct(
+ private array $dirs,
+ ) {
}
/**
- * @param string $template
- * @return string
- * @throws \Exception
+ * @return array{string,string} Directory path and filename
+ * @throws TemplateNotFoundException
*/
- public function find($template) {
+ public function find(string $template): array {
if ($template === '') {
throw new \InvalidArgumentException('Empty template name');
}
@@ -31,14 +33,9 @@ class TemplateFileLocator {
foreach ($this->dirs as $dir) {
$file = $dir . $template . '.php';
if (is_file($file)) {
- $this->path = $dir;
- return $file;
+ return [$dir,$file];
}
}
- throw new \Exception('template file not found: template:' . $template);
- }
-
- public function getPath() {
- return $this->path;
+ throw new TemplateNotFoundException('template file not found: template:' . $template);
}
}
diff --git a/lib/private/Template/TemplateManager.php b/lib/private/Template/TemplateManager.php
new file mode 100644
index 00000000000..34da4deac72
--- /dev/null
+++ b/lib/private/Template/TemplateManager.php
@@ -0,0 +1,169 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Template;
+
+use OCP\App\IAppManager;
+use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IRequest;
+use OCP\Server;
+use OCP\Template\ITemplate;
+use OCP\Template\ITemplateManager;
+use OCP\Template\TemplateNotFoundException;
+use Psr\Log\LoggerInterface;
+
+class TemplateManager implements ITemplateManager {
+ public function __construct(
+ private IAppManager $appManager,
+ private IEventDispatcher $eventDispatcher,
+ ) {
+ }
+
+ /**
+ * @param TemplateResponse::RENDER_AS_* $renderAs
+ * @throws TemplateNotFoundException if the template cannot be found
+ */
+ public function getTemplate(string $app, string $name, string $renderAs = TemplateResponse::RENDER_AS_BLANK, bool $registerCall = true): ITemplate {
+ return new Template($app, $name, $renderAs, $registerCall);
+ }
+
+ /**
+ * Shortcut to print a simple page for guests
+ * @param string $application The application we render the template for
+ * @param string $name Name of the template
+ * @param array $parameters Parameters for the template
+ */
+ public function printGuestPage(string $application, string $name, array $parameters = []): void {
+ $content = $this->getTemplate($application, $name, $name === 'error' ? $name : 'guest');
+ foreach ($parameters as $key => $value) {
+ $content->assign($key, $value);
+ }
+ $content->printPage();
+ }
+
+ /**
+ * Print a fatal error page and terminates the script
+ * @param string $error_msg The error message to show
+ * @param string $hint An optional hint message - needs to be properly escape
+ */
+ public function printErrorPage(string $error_msg, string $hint = '', int $statusCode = 500): never {
+ if ($this->appManager->isEnabledForUser('theming') && !$this->appManager->isAppLoaded('theming')) {
+ $this->appManager->loadApp('theming');
+ }
+
+ if ($error_msg === $hint) {
+ // If the hint is the same as the message there is no need to display it twice.
+ $hint = '';
+ }
+ $errors = [['error' => $error_msg, 'hint' => $hint]];
+
+ http_response_code($statusCode);
+ try {
+ // Try rendering themed html error page
+ $response = new TemplateResponse(
+ '',
+ 'error',
+ ['errors' => $errors],
+ TemplateResponse::RENDER_AS_ERROR,
+ $statusCode,
+ );
+ $event = new BeforeTemplateRenderedEvent(false, $response);
+ $this->eventDispatcher->dispatchTyped($event);
+ print($response->render());
+ } catch (\Throwable $e1) {
+ $logger = \OCP\Server::get(LoggerInterface::class);
+ $logger->error('Rendering themed error page failed. Falling back to un-themed error page.', [
+ 'app' => 'core',
+ 'exception' => $e1,
+ ]);
+
+ try {
+ // Try rendering unthemed html error page
+ $content = $this->getTemplate('', 'error', 'error', false);
+ $content->assign('errors', $errors);
+ $content->printPage();
+ } catch (\Exception $e2) {
+ // If nothing else works, fall back to plain text error page
+ $logger->error("$error_msg $hint", ['app' => 'core']);
+ $logger->error('Rendering un-themed error page failed. Falling back to plain text error page.', [
+ 'app' => 'core',
+ 'exception' => $e2,
+ ]);
+
+ header('Content-Type: text/plain; charset=utf-8');
+ print("$error_msg $hint");
+ }
+ }
+ die();
+ }
+
+ /**
+ * print error page using Exception details
+ */
+ public function printExceptionErrorPage(\Throwable $exception, int $statusCode = 503): never {
+ $debug = false;
+ http_response_code($statusCode);
+ try {
+ $debug = (bool)Server::get(\OC\SystemConfig::class)->getValue('debug', false);
+ $serverLogsDocumentation = Server::get(\OC\SystemConfig::class)->getValue('documentation_url.server_logs', '');
+ $request = Server::get(IRequest::class);
+ $content = $this->getTemplate('', 'exception', 'error', false);
+ $content->assign('errorClass', get_class($exception));
+ $content->assign('errorMsg', $exception->getMessage());
+ $content->assign('errorCode', $exception->getCode());
+ $content->assign('file', $exception->getFile());
+ $content->assign('line', $exception->getLine());
+ $content->assign('exception', $exception);
+ $content->assign('debugMode', $debug);
+ $content->assign('serverLogsDocumentation', $serverLogsDocumentation);
+ $content->assign('remoteAddr', $request->getRemoteAddress());
+ $content->assign('requestID', $request->getId());
+ $content->printPage();
+ } catch (\Exception $e) {
+ try {
+ $logger = Server::get(LoggerInterface::class);
+ $logger->error($exception->getMessage(), ['app' => 'core', 'exception' => $exception]);
+ $logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
+ } catch (\Throwable $e) {
+ // no way to log it properly - but to avoid a white page of death we send some output
+ $this->printPlainErrorPage($e, $debug);
+
+ // and then throw it again to log it at least to the web server error log
+ throw $e;
+ }
+
+ $this->printPlainErrorPage($e, $debug);
+ }
+ die();
+ }
+
+ /**
+ * @psalm-taint-escape has_quotes
+ * @psalm-taint-escape html
+ */
+ private function fakeEscapeForPlainText(string $str): string {
+ return $str;
+ }
+
+ private function printPlainErrorPage(\Throwable $exception, bool $debug = false): void {
+ header('Content-Type: text/plain; charset=utf-8');
+ print("Internal Server Error\n\n");
+ print("The server encountered an internal error and was unable to complete your request.\n");
+ print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
+ print("More details can be found in the server log.\n");
+
+ if ($debug) {
+ print("\n");
+ print($exception->getMessage() . ' ' . $exception->getFile() . ' at ' . $exception->getLine() . "\n");
+ print($this->fakeEscapeForPlainText($exception->getTraceAsString()));
+ }
+ }
+}
diff --git a/lib/private/Template/functions.php b/lib/private/Template/functions.php
new file mode 100644
index 00000000000..402a7491e03
--- /dev/null
+++ b/lib/private/Template/functions.php
@@ -0,0 +1,299 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
+use OCP\Files\IMimeTypeDetector;
+use OCP\IDateTimeFormatter;
+use OCP\IURLGenerator;
+use OCP\Server;
+use OCP\Util;
+
+/**
+ * @param string $string
+ */
+function p($string): void {
+ print(Util::sanitizeHTML($string));
+}
+
+/**
+ * Prints a <link> tag for loading css
+ * @param string $href the source URL, ignored when empty
+ * @param string $opts, additional optional options
+ */
+function emit_css_tag($href, $opts = ''): void {
+ $s = '<link rel="stylesheet"';
+ if (!empty($href)) {
+ $s .= ' href="' . $href . '"';
+ }
+ if (!empty($opts)) {
+ $s .= ' ' . $opts;
+ }
+ print_unescaped($s . ">\n");
+}
+
+/**
+ * Prints all tags for CSS loading
+ * @param array $obj all the script information from template
+ */
+function emit_css_loading_tags($obj): void {
+ foreach ($obj['cssfiles'] as $css) {
+ emit_css_tag($css);
+ }
+ foreach ($obj['printcssfiles'] as $css) {
+ emit_css_tag($css, 'media="print"');
+ }
+}
+
+/**
+ * Prints a <script> tag with nonce and defer depending on config
+ * @param string $src the source URL, ignored when empty
+ * @param string $script_content the inline script content, ignored when empty
+ * @param string $content_type the type of the source (e.g. 'module')
+ */
+function emit_script_tag(string $src, string $script_content = '', string $content_type = ''): void {
+ $nonceManager = Server::get(ContentSecurityPolicyNonceManager::class);
+
+ $defer_str = ' defer';
+ $type = $content_type !== '' ? ' type="' . $content_type . '"' : '';
+
+ $s = '<script nonce="' . $nonceManager->getNonce() . '"';
+ if (!empty($src)) {
+ // emit script tag for deferred loading from $src
+ $s .= $defer_str . ' src="' . $src . '"' . $type . '>';
+ } elseif ($script_content !== '') {
+ // emit script tag for inline script from $script_content without defer (see MDN)
+ $s .= ">\n" . $script_content . "\n";
+ } else {
+ // no $src nor $src_content, really useless empty tag
+ $s .= '>';
+ }
+ $s .= '</script>';
+ print_unescaped($s . "\n");
+}
+
+/**
+ * Print all <script> tags for loading JS
+ * @param array $obj all the script information from template
+ */
+function emit_script_loading_tags($obj): void {
+ foreach ($obj['jsfiles'] as $jsfile) {
+ $fileName = explode('?', $jsfile, 2)[0];
+ $type = str_ends_with($fileName, '.mjs') ? 'module' : '';
+ emit_script_tag($jsfile, '', $type);
+ }
+ if (!empty($obj['inline_ocjs'])) {
+ emit_script_tag('', $obj['inline_ocjs']);
+ }
+}
+
+/**
+ * Prints an unsanitized string - usage of this function may result into XSS.
+ * Consider using p() instead.
+ * @param string $string the string which will be printed as it is
+ */
+function print_unescaped($string): void {
+ print($string);
+}
+
+/**
+ * Shortcut for adding scripts to a page
+ * All scripts are forced to be loaded after core since
+ * they are coming from a template registration.
+ * Please consider moving them into the relevant controller
+ *
+ * @deprecated 24.0.0 - Use \OCP\Util::addScript
+ *
+ * @param string $app the appname
+ * @param string|string[] $file the filename,
+ * if an array is given it will add all scripts
+ */
+function script($app, $file = null): void {
+ if (is_array($file)) {
+ foreach ($file as $script) {
+ Util::addScript($app, $script, 'core');
+ }
+ } else {
+ Util::addScript($app, $file, 'core');
+ }
+}
+
+/**
+ * Shortcut for adding styles to a page
+ * @param string $app the appname
+ * @param string|string[] $file the filename,
+ * if an array is given it will add all styles
+ */
+function style($app, $file = null): void {
+ if (is_array($file)) {
+ foreach ($file as $f) {
+ Util::addStyle($app, $f);
+ }
+ } else {
+ Util::addStyle($app, $file);
+ }
+}
+
+/**
+ * Shortcut for adding vendor styles to a page
+ * @param string $app the appname
+ * @param string|string[] $file the filename,
+ * if an array is given it will add all styles
+ * @deprecated 32.0.0
+ */
+function vendor_style($app, $file = null): void {
+ if (is_array($file)) {
+ foreach ($file as $f) {
+ OC_Util::addVendorStyle($app, $f);
+ }
+ } else {
+ OC_Util::addVendorStyle($app, $file);
+ }
+}
+
+/**
+ * Shortcut for adding translations to a page
+ * @param string $app the appname
+ * if an array is given it will add all styles
+ */
+function translation($app): void {
+ Util::addTranslations($app);
+}
+
+/**
+ * make \OCP\IURLGenerator::linkTo available as a simple function
+ * @param string $app app
+ * @param string $file file
+ * @param array $args array with param=>value, will be appended to the returned url
+ * @return string link to the file
+ *
+ * For further information have a look at \OCP\IURLGenerator::linkTo
+ */
+function link_to($app, $file, $args = []) {
+ return Server::get(IURLGenerator::class)->linkTo($app, $file, $args);
+}
+
+/**
+ * @param string $key
+ * @return string url to the online documentation
+ */
+function link_to_docs($key) {
+ return Server::get(IURLGenerator::class)->linkToDocs($key);
+}
+
+/**
+ * make \OCP\IURLGenerator::imagePath available as a simple function
+ * @param string $app app
+ * @param string $image image
+ * @return string link to the image
+ *
+ * For further information have a look at \OCP\IURLGenerator::imagePath
+ */
+function image_path($app, $image) {
+ return Server::get(IURLGenerator::class)->imagePath($app, $image);
+}
+
+/**
+ * make mimetypeIcon available as a simple function
+ * @param string $mimetype mimetype
+ * @return string link to the image
+ */
+function mimetype_icon($mimetype) {
+ return Server::get(IMimeTypeDetector::class)->mimeTypeIcon($mimetype);
+}
+
+/**
+ * make preview_icon available as a simple function
+ * Returns the path to the preview of the image.
+ * @param string $path path of file
+ * @return string link to the preview
+ */
+function preview_icon($path) {
+ return Server::get(IURLGenerator::class)->linkToRoute('core.Preview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path]);
+}
+
+/**
+ * @param string $path
+ * @param string $token
+ * @return string
+ */
+function publicPreview_icon($path, $token) {
+ return Server::get(IURLGenerator::class)->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 'token' => $token]);
+}
+
+/**
+ * make Util::humanFileSize available as a simple function
+ * @param int $bytes size in bytes
+ * @return string size as string
+ * @deprecated use Util::humanFileSize instead
+ *
+ * For further information have a look at Util::humanFileSize
+ */
+function human_file_size($bytes) {
+ return Util::humanFileSize($bytes);
+}
+
+/**
+ * Strips the timestamp of its time value
+ * @param int $timestamp UNIX timestamp to strip
+ * @return int timestamp without time value
+ */
+function strip_time($timestamp) {
+ $date = new \DateTime("@{$timestamp}");
+ $date->setTime(0, 0, 0);
+ return (int)$date->format('U');
+}
+
+/**
+ * Formats timestamp relatively to the current time using
+ * a human-friendly format like "x minutes ago" or "yesterday"
+ * @param int $timestamp timestamp to format
+ * @param int|null $fromTime timestamp to compare from, defaults to current time
+ * @param bool|null $dateOnly whether to strip time information
+ * @return string timestamp
+ */
+function relative_modified_date($timestamp, $fromTime = null, $dateOnly = false): string {
+ $formatter = Server::get(IDateTimeFormatter::class);
+
+ if ($dateOnly) {
+ return $formatter->formatDateSpan($timestamp, $fromTime);
+ }
+ return $formatter->formatTimeSpan($timestamp, $fromTime);
+}
+
+/**
+ * @param array $options
+ * @param string[]|string $selected
+ * @param array $params
+ */
+function html_select_options($options, $selected, $params = []): string {
+ if (!is_array($selected)) {
+ $selected = [$selected];
+ }
+ if (isset($params['combine']) && $params['combine']) {
+ $options = array_combine($options, $options);
+ }
+ $value_name = $label_name = false;
+ if (isset($params['value'])) {
+ $value_name = $params['value'];
+ }
+ if (isset($params['label'])) {
+ $label_name = $params['label'];
+ }
+ $html = '';
+ foreach ($options as $value => $label) {
+ if ($value_name && is_array($label)) {
+ $value = $label[$value_name];
+ }
+ if ($label_name && is_array($label)) {
+ $label = $label[$label_name];
+ }
+ $select = in_array($value, $selected) ? ' selected="selected"' : '';
+ $html .= '<option value="' . Util::sanitizeHTML($value) . '"' . $select . '>' . Util::sanitizeHTML($label) . '</option>' . "\n";
+ }
+ return $html;
+}