diff options
author | Côme Chilliet <come.chilliet@nextcloud.com> | 2025-02-25 18:44:02 +0100 |
---|---|---|
committer | Côme Chilliet <91878298+come-nc@users.noreply.github.com> | 2025-03-06 15:49:25 +0100 |
commit | 71dc34c03c3415b639fe2236ef8456381adc5fcd (patch) | |
tree | 4f7034618a039701c4096e0384697983003dca03 /lib | |
parent | b44f1568f2dc97c746281d99e2342ad679e3d8a9 (diff) | |
download | nextcloud-server-71dc34c03c3415b639fe2236ef8456381adc5fcd.tar.gz nextcloud-server-71dc34c03c3415b639fe2236ef8456381adc5fcd.zip |
fix: Deprecate OC_Template, add proper template manager instead
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/base.php | 19 | ||||
-rw-r--r-- | lib/private/Template/Base.php | 27 | ||||
-rw-r--r-- | lib/private/Template/Template.php | 172 | ||||
-rw-r--r-- | lib/private/Template/TemplateManager.php | 162 | ||||
-rw-r--r-- | lib/private/legacy/OC_Template.php | 310 | ||||
-rw-r--r-- | lib/public/Template.php | 1 | ||||
-rw-r--r-- | lib/public/Template/ITemplateManager.php | 36 |
7 files changed, 400 insertions, 327 deletions
diff --git a/lib/base.php b/lib/base.php index 44b78ff713e..618b392e337 100644 --- a/lib/base.php +++ b/lib/base.php @@ -21,6 +21,7 @@ use OCP\IUserSession; use OCP\Security\Bruteforce\IThrottler; use OCP\Server; use OCP\Share; +use OCP\Template\ITemplateManager; use OCP\User\Events\UserChangedEvent; use OCP\User\Events\UserDeletedEvent; use OCP\Util; @@ -208,7 +209,7 @@ class OC { echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n"; exit; } else { - OC_Template::printErrorPage( + Server::get(ITemplateManager::class)->printErrorPage( $l->t('Cannot write into "config" directory!'), $l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' ' . $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' ' @@ -244,7 +245,7 @@ class OC { header('Retry-After: 120'); // render error page - $template = new OC_Template('', 'update.user', 'guest'); + $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest'); \OCP\Util::addScript('core', 'maintenance'); \OCP\Util::addStyle('core', 'guest'); $template->printPage(); @@ -300,7 +301,7 @@ class OC { $serverVersion = \OCP\Server::get(\OCP\ServerVersion::class); // render error page - $template = new OC_Template('', 'update.use-cli', 'guest'); + $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest'); $template->assign('productName', 'nextcloud'); // for now $template->assign('version', $serverVersion->getVersionString()); $template->assign('tooBig', $tooBig); @@ -327,7 +328,7 @@ class OC { /** @var \OC\App\AppManager $appManager */ $appManager = Server::get(\OCP\App\IAppManager::class); - $tmpl = new OC_Template('', 'update.admin', 'guest'); + $tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest'); $tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString()); $tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade); @@ -420,7 +421,7 @@ class OC { } catch (Exception $e) { Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]); //show the user a detailed error page - OC_Template::printExceptionErrorPage($e, 500); + Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500); die(); } @@ -659,7 +660,7 @@ class OC { if ($config->getSystemValueBool('debug', false)) { set_error_handler([$errorHandler, 'onAll'], E_ALL); if (\OC::$CLI) { - $exceptionHandler = ['OC_Template', 'printExceptionErrorPage']; + $exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage']; } } else { set_error_handler([$errorHandler, 'onError']); @@ -702,7 +703,7 @@ class OC { http_response_code(503); OC_Util::addStyle('guest'); try { - OC_Template::printGuestPage('', 'error', ['errors' => $errors]); + Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]); exit; } catch (\Exception $e) { // In case any error happens when showing the error page, we simply fall back to posting the text. @@ -781,7 +782,7 @@ class OC { // Check whether the sample configuration has been copied if ($systemConfig->getValue('copied_sample_config', false)) { $l = Server::get(\OCP\L10N\IFactory::class)->get('lib'); - OC_Template::printErrorPage( + Server::get(ITemplateManager::class)->printErrorPage( $l->t('Sample configuration detected'), $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'), 503 @@ -1094,7 +1095,7 @@ class OC { logger('core')->emergency($e->getMessage(), ['exception' => $e]); } $l = Server::get(\OCP\L10N\IFactory::class)->get('lib'); - OC_Template::printErrorPage( + Server::get(ITemplateManager::class)->printErrorPage( '404', $l->t('The page could not be found on the server.'), 404 diff --git a/lib/private/Template/Base.php b/lib/private/Template/Base.php index 602c8e6257e..35c2bf596ad 100644 --- a/lib/private/Template/Base.php +++ b/lib/private/Template/Base.php @@ -12,7 +12,7 @@ use Throwable; class Base { private $template; // The template - private $vars; // Vars + private array $vars = []; /** @var \OCP\IL10N */ private $l10n; @@ -59,11 +59,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/', @@ -105,42 +103,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; diff --git a/lib/private/Template/Template.php b/lib/private/Template/Template.php new file mode 100644 index 00000000000..78865ceee5c --- /dev/null +++ b/lib/private/Template/Template.php @@ -0,0 +1,172 @@ +<?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\TemplateLayout; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\Template\ITemplate; + +require_once __DIR__ . '/../legacy/template/functions.php'; + +class Template extends Base implements ITemplate { + /** @var string */ + private $renderAs; // Create a full page? + + /** @var string */ + private $path; // The path to the template + + /** @var array */ + private $headers = []; //custom headers + + /** @var string */ + protected $app; // app id + + /** + * Constructor + * + * @param string $app app providing the template + * @param string $name of the template file (without suffix) + * @param string $renderAs If $renderAs is set, will try to + * produce a full page in the according layout. For + * now, $renderAs can be set to "guest", "user" or + * "admin". + * @param bool $registerCall = true + */ + public function __construct( + $app, + $name, + $renderAs = TemplateResponse::RENDER_AS_BLANK, + $registerCall = true, + ) { + $theme = OC_Util::getTheme(); + + $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : ''; + $cspNonce = \OCP\Server::get(\OC\Security\CSP\ContentSecurityPolicyNonceManager::class)->getNonce(); + + $parts = explode('/', $app); // fix translation when app is something like core/lostpassword + $l10n = \OC::$server->getL10N($parts[0]); + /** @var \OCP\Defaults $themeDefaults */ + $themeDefaults = \OCP\Server::get(\OCP\Defaults::class); + + [$path, $template] = $this->findTemplate($theme, $app, $name); + + // Set the private data + $this->renderAs = $renderAs; + $this->path = $path; + $this->app = $app; + + parent::__construct( + $template, + $requestToken, + $l10n, + $themeDefaults, + $cspNonce, + ); + } + + + /** + * find the template with the given name + * @param string $name of the template file (without suffix) + * + * Will select the template file for the selected theme. + * Checking all the possible locations. + * @param string $theme + * @param string $app + * @return string[] + */ + protected function findTemplate($theme, $app, $name) { + // Check if it is a app template or not. + if ($app !== '') { + $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app)); + } else { + $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT); + } + $locator = new \OC\Template\TemplateFileLocator($dirs); + $template = $locator->find($name); + $path = $locator->getPath(); + return [$path, $template]; + } + + /** + * 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($tag, $attributes, $text = null) { + $this->headers[] = [ + 'tag' => $tag, + 'attributes' => $attributes, + 'text' => $text + ]; + } + + /** + * Process the template + * @return string + * + * This function process the template. If $this->renderAs is set, it + * will produce a full page. + */ + public function fetchPage($additionalParams = null) { + $data = parent::fetchPage($additionalParams); + + if ($this->renderAs) { + $page = new TemplateLayout($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 .= '<' . \OCP\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 .= ' ' . \OCP\Util::sanitizeHTML($name) . '="' . \OCP\Util::sanitizeHTML($value) . '"'; + } + if ($header['text'] !== null) { + $headers .= '>' . \OCP\Util::sanitizeHTML($header['text']) . '</' . \OCP\Util::sanitizeHTML($header['tag']) . '>'; + } else { + $headers .= '/>'; + } + } + + $page->assign('headers', $headers); + + $page->assign('content', $data); + return $page->fetchPage($additionalParams); + } + + return $data; + } + + /** + * Include template + * + * @param string $file + * @param array|null $additionalParams + * @return string returns content of included template + * + * Includes another template. use <?php echo $this->inc('template'); ?> to + * do this. + */ + public function inc($file, $additionalParams = null) { + return $this->load($this->path . $file . '.php', $additionalParams); + } +} diff --git a/lib/private/Template/TemplateManager.php b/lib/private/Template/TemplateManager.php new file mode 100644 index 00000000000..db8d83c1bf9 --- /dev/null +++ b/lib/private/Template/TemplateManager.php @@ -0,0 +1,162 @@ +<?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 Psr\Log\LoggerInterface; + +class TemplateManager { + public function __construct( + private IAppManager $appManager, + private IEventDispatcher $eventDispatcher, + ) { + } + + 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 = new Template($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 = new Template('', '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 = 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 = new Template('', '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 = \OCP\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/legacy/OC_Template.php b/lib/private/legacy/OC_Template.php index af363e0a41e..afa640632f8 100644 --- a/lib/private/legacy/OC_Template.php +++ b/lib/private/legacy/OC_Template.php @@ -5,209 +5,27 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ -use OC\TemplateLayout; -use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; -use OCP\AppFramework\Http\TemplateResponse; -use OCP\EventDispatcher\IEventDispatcher; -use Psr\Log\LoggerInterface; +use OCP\Server; +use OCP\Template\ITemplateManager; require_once __DIR__ . '/template/functions.php'; /** * This class provides the templates for ownCloud. + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ -class OC_Template extends \OC\Template\Base { - /** @var string */ - private $renderAs; // Create a full page? - - /** @var string */ - private $path; // The path to the template - - /** @var array */ - private $headers = []; //custom headers - - /** @var string */ - protected $app; // app id - - /** - * Constructor - * - * @param string $app app providing the template - * @param string $name of the template file (without suffix) - * @param string $renderAs If $renderAs is set, OC_Template will try to - * produce a full page in the according layout. For - * now, $renderAs can be set to "guest", "user" or - * "admin". - * @param bool $registerCall = true - */ - public function __construct($app, $name, $renderAs = TemplateResponse::RENDER_AS_BLANK, $registerCall = true) { - $theme = OC_Util::getTheme(); - - $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : ''; - $cspNonce = \OCP\Server::get(\OC\Security\CSP\ContentSecurityPolicyNonceManager::class)->getNonce(); - - $parts = explode('/', $app); // fix translation when app is something like core/lostpassword - $l10n = \OC::$server->getL10N($parts[0]); - /** @var \OCP\Defaults $themeDefaults */ - $themeDefaults = \OCP\Server::get(\OCP\Defaults::class); - - [$path, $template] = $this->findTemplate($theme, $app, $name); - - // Set the private data - $this->renderAs = $renderAs; - $this->path = $path; - $this->app = $app; - - parent::__construct( - $template, - $requestToken, - $l10n, - $themeDefaults, - $cspNonce, - ); - } - - - /** - * find the template with the given name - * @param string $name of the template file (without suffix) - * - * Will select the template file for the selected theme. - * Checking all the possible locations. - * @param string $theme - * @param string $app - * @return string[] - */ - protected function findTemplate($theme, $app, $name) { - // Check if it is a app template or not. - if ($app !== '') { - $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app)); - } else { - $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT); - } - $locator = new \OC\Template\TemplateFileLocator($dirs); - $template = $locator->find($name); - $path = $locator->getPath(); - return [$path, $template]; - } - - /** - * 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($tag, $attributes, $text = null) { - $this->headers[] = [ - 'tag' => $tag, - 'attributes' => $attributes, - 'text' => $text - ]; - } - - /** - * Process the template - * @return string - * - * This function process the template. If $this->renderAs is set, it - * will produce a full page. - */ - public function fetchPage($additionalParams = null) { - $data = parent::fetchPage($additionalParams); - - if ($this->renderAs) { - $page = new TemplateLayout($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 .= '<' . \OCP\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 .= ' ' . \OCP\Util::sanitizeHTML($name) . '="' . \OCP\Util::sanitizeHTML($value) . '"'; - } - if ($header['text'] !== null) { - $headers .= '>' . \OCP\Util::sanitizeHTML($header['text']) . '</' . \OCP\Util::sanitizeHTML($header['tag']) . '>'; - } else { - $headers .= '/>'; - } - } - - $page->assign('headers', $headers); - - $page->assign('content', $data); - return $page->fetchPage($additionalParams); - } - - return $data; - } - - /** - * Include template - * - * @param string $file - * @param array|null $additionalParams - * @return string returns content of included template - * - * Includes another template. use <?php echo $this->inc('template'); ?> to - * do this. - */ - public function inc($file, $additionalParams = null) { - return $this->load($this->path . $file . '.php', $additionalParams); - } - - /** - * Shortcut to print a simple page for users - * @param string $application The application we render the template for - * @param string $name Name of the template - * @param array $parameters Parameters for the template - * @return boolean|null - */ - public static function printUserPage($application, $name, $parameters = []) { - $content = new OC_Template($application, $name, 'user'); - foreach ($parameters as $key => $value) { - $content->assign($key, $value); - } - print $content->printPage(); - } - - /** - * Shortcut to print a simple page for admins - * @param string $application The application we render the template for - * @param string $name Name of the template - * @param array $parameters Parameters for the template - * @return bool - */ - public static function printAdminPage($application, $name, $parameters = []) { - $content = new OC_Template($application, $name, 'admin'); - foreach ($parameters as $key => $value) { - $content->assign($key, $value); - } - return $content->printPage(); - } - +class OC_Template extends \OC\Template\Template { /** * 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|string $parameters Parameters for the template * @return bool + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ public static function printGuestPage($application, $name, $parameters = []) { - $content = new OC_Template($application, $name, $name === 'error' ? $name : 'guest'); - foreach ($parameters as $key => $value) { - $content->assign($key, $value); - } - return $content->printPage(); + Server::get(ITemplateManager::class)->printGuestPage($application, $name, $parameters); + return true; } /** @@ -215,123 +33,21 @@ class OC_Template extends \OC\Template\Base { * @param string $error_msg The error message to show * @param string $hint An optional hint message - needs to be properly escape * @param int $statusCode - * @suppress PhanAccessMethodInternal + * @return never + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) { - if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) { - \OC_App::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); - \OC::$server->get(IEventDispatcher::class)->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 = new \OC_Template('', '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(); + Server::get(ITemplateManager::class)->printErrorPage($error_msg, $hint, $statusCode); } /** * print error page using Exception details * @param Exception|Throwable $exception * @param int $statusCode - * @return bool|string - * @suppress PhanAccessMethodInternal + * @return never + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ public static function printExceptionErrorPage($exception, $statusCode = 503) { - $debug = false; - http_response_code($statusCode); - try { - $debug = \OC::$server->getSystemConfig()->getValue('debug', false); - $serverLogsDocumentation = \OC::$server->getSystemConfig()->getValue('documentation_url.server_logs', ''); - $request = \OC::$server->getRequest(); - $content = new \OC_Template('', '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 = \OCP\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 - self::printPlainErrorPage($e, $debug); - - // and then throw it again to log it at least to the web server error log - throw $e; - } - - self::printPlainErrorPage($e, $debug); - } - die(); - } - - /** - * @psalm-taint-escape has_quotes - * @psalm-taint-escape html - */ - private static function fakeEscapeForPlainText(string $str): string { - return $str; - } - - private static 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(self::fakeEscapeForPlainText($exception->getTraceAsString())); - } + Server::get(ITemplateManager::class)->printExceptionErrorPage($exception, $statusCode); } } diff --git a/lib/public/Template.php b/lib/public/Template.php index 048697ffcc4..d9b3083319b 100644 --- a/lib/public/Template.php +++ b/lib/public/Template.php @@ -12,6 +12,7 @@ namespace OCP; * specific templates, add data and generate the html code * * @since 8.0.0 + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ class Template extends \OC_Template { /** diff --git a/lib/public/Template/ITemplateManager.php b/lib/public/Template/ITemplateManager.php new file mode 100644 index 00000000000..267439d4070 --- /dev/null +++ b/lib/public/Template/ITemplateManager.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Template; + +use OCP\AppFramework\Http\TemplateResponse; + +interface ITemplateManager { + public function getTemplate(string $app, string $name, string $renderAs = TemplateResponse::RENDER_AS_BLANK, bool $registerCall = true): ITemplate; + + /** + * Shortcut to print a simple page for guests + * @since 32.0.0 + */ + public function printGuestPage(string $application, string $name, array $parameters = []): void; + + /** + * Print a fatal error page and terminates the script + * @since 32.0.0 + * @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; + + /** + * print error page using Exception details + * @since 32.0.0 + */ + public function printExceptionErrorPage(\Throwable $exception, int $statusCode = 503): never; +} |