diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index dc7d7430f2..79b54b365f 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -506,6 +506,7 @@ 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance', 'UI:Error:MaintenanceTitle' => 'Maintenance', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)', + 'UI:Error:TwigController' => 'Internal error in form controller', 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 1b4f9e4f06..ff9e4f2ade 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -500,6 +500,7 @@ 'UI:Error:MaintenanceMode' => 'L\'application est en maintenance', 'UI:Error:MaintenanceTitle' => 'Maintenance', 'UI:Error:InvalidToken' => 'Erreur: l\'opération a déjà été effectuée (CSRF token not found)', + 'UI:Error:TwigController' => 'Erreur interne dans le contrôleur de formulaire', 'UI:Error:SMTP:UnknownVendor' => 'Le provider SMTP OAuth 2.0 %1$s n\'existe pas', 'UI:GroupBy:Count' => 'Nombre', 'UI:GroupBy:Count+' => 'Nombre d\'éléments', diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 397941d3c1..c338b2bcc1 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -144,8 +144,10 @@ 'Combodo\\iTop\\Application\\Search\\CriterionParser' => $baseDir . '/sources/Application/Search/criterionparser.class.inc.php', 'Combodo\\iTop\\Application\\Search\\SearchForm' => $baseDir . '/sources/Application/Search/searchform.class.inc.php', 'Combodo\\iTop\\Application\\Status\\Status' => $baseDir . '/sources/Application/Status/Status.php', + 'Combodo\\iTop\\Application\\TwigBase\\Controller\\AbstractProfilerExtension' => $baseDir . '/sources/Application/TwigBase/Controller/AbstractProfilerExtension.php', 'Combodo\\iTop\\Application\\TwigBase\\Controller\\Controller' => $baseDir . '/sources/Application/TwigBase/Controller/Controller.php', 'Combodo\\iTop\\Application\\TwigBase\\Controller\\PageNotFoundException' => $baseDir . '/application/exceptions/PageNotFoundException.php', + 'Combodo\\iTop\\Application\\TwigBase\\Controller\\iProfilerExtension' => $baseDir . '/sources/Application/TwigBase/Controller/iProfilerExtension.php', 'Combodo\\iTop\\Application\\TwigBase\\Twig\\Extension' => $baseDir . '/sources/Application/TwigBase/Twig/Extension.php', 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => $baseDir . '/sources/Application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockExtension' => $baseDir . '/sources/Application/TwigBase/UI/UIBlockExtension.php', @@ -2412,13 +2414,6 @@ 'Symfony\\Component\\Form\\SubmitButton' => $vendorDir . '/symfony/form/SubmitButton.php', 'Symfony\\Component\\Form\\SubmitButtonBuilder' => $vendorDir . '/symfony/form/SubmitButtonBuilder.php', 'Symfony\\Component\\Form\\SubmitButtonTypeInterface' => $vendorDir . '/symfony/form/SubmitButtonTypeInterface.php', - 'Symfony\\Component\\Form\\Test\\FormBuilderInterface' => $vendorDir . '/symfony/form/Test/FormBuilderInterface.php', - 'Symfony\\Component\\Form\\Test\\FormIntegrationTestCase' => $vendorDir . '/symfony/form/Test/FormIntegrationTestCase.php', - 'Symfony\\Component\\Form\\Test\\FormInterface' => $vendorDir . '/symfony/form/Test/FormInterface.php', - 'Symfony\\Component\\Form\\Test\\FormPerformanceTestCase' => $vendorDir . '/symfony/form/Test/FormPerformanceTestCase.php', - 'Symfony\\Component\\Form\\Test\\Traits\\RunTestTrait' => $vendorDir . '/symfony/form/Test/Traits/RunTestTrait.php', - 'Symfony\\Component\\Form\\Test\\Traits\\ValidatorExtensionTrait' => $vendorDir . '/symfony/form/Test/Traits/ValidatorExtensionTrait.php', - 'Symfony\\Component\\Form\\Test\\TypeTestCase' => $vendorDir . '/symfony/form/Test/TypeTestCase.php', 'Symfony\\Component\\Form\\Util\\FormUtil' => $vendorDir . '/symfony/form/Util/FormUtil.php', 'Symfony\\Component\\Form\\Util\\InheritDataAwareIterator' => $vendorDir . '/symfony/form/Util/InheritDataAwareIterator.php', 'Symfony\\Component\\Form\\Util\\OptionsResolverWrapper' => $vendorDir . '/symfony/form/Util/OptionsResolverWrapper.php', @@ -3031,7 +3026,6 @@ 'Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => $vendorDir . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php', 'Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => $vendorDir . '/symfony/security-core/Signature/ExpiredSignatureStorage.php', 'Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => $vendorDir . '/symfony/security-core/Signature/SignatureHasher.php', - 'Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => $vendorDir . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php', 'Symfony\\Component\\Security\\Core\\User\\AttributesBasedUserProviderInterface' => $vendorDir . '/symfony/security-core/User/AttributesBasedUserProviderInterface.php', 'Symfony\\Component\\Security\\Core\\User\\ChainUserChecker' => $vendorDir . '/symfony/security-core/User/ChainUserChecker.php', 'Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => $vendorDir . '/symfony/security-core/User/ChainUserProvider.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 31cd57336d..f237f3d1e1 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -525,8 +525,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Application\\Search\\CriterionParser' => __DIR__ . '/../..' . '/sources/Application/Search/criterionparser.class.inc.php', 'Combodo\\iTop\\Application\\Search\\SearchForm' => __DIR__ . '/../..' . '/sources/Application/Search/searchform.class.inc.php', 'Combodo\\iTop\\Application\\Status\\Status' => __DIR__ . '/../..' . '/sources/Application/Status/Status.php', + 'Combodo\\iTop\\Application\\TwigBase\\Controller\\AbstractProfilerExtension' => __DIR__ . '/../..' . '/sources/Application/TwigBase/Controller/AbstractProfilerExtension.php', 'Combodo\\iTop\\Application\\TwigBase\\Controller\\Controller' => __DIR__ . '/../..' . '/sources/Application/TwigBase/Controller/Controller.php', 'Combodo\\iTop\\Application\\TwigBase\\Controller\\PageNotFoundException' => __DIR__ . '/../..' . '/application/exceptions/PageNotFoundException.php', + 'Combodo\\iTop\\Application\\TwigBase\\Controller\\iProfilerExtension' => __DIR__ . '/../..' . '/sources/Application/TwigBase/Controller/iProfilerExtension.php', 'Combodo\\iTop\\Application\\TwigBase\\Twig\\Extension' => __DIR__ . '/../..' . '/sources/Application/TwigBase/Twig/Extension.php', 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => __DIR__ . '/../..' . '/sources/Application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockExtension' => __DIR__ . '/../..' . '/sources/Application/TwigBase/UI/UIBlockExtension.php', @@ -2793,13 +2795,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Form\\SubmitButton' => __DIR__ . '/..' . '/symfony/form/SubmitButton.php', 'Symfony\\Component\\Form\\SubmitButtonBuilder' => __DIR__ . '/..' . '/symfony/form/SubmitButtonBuilder.php', 'Symfony\\Component\\Form\\SubmitButtonTypeInterface' => __DIR__ . '/..' . '/symfony/form/SubmitButtonTypeInterface.php', - 'Symfony\\Component\\Form\\Test\\FormBuilderInterface' => __DIR__ . '/..' . '/symfony/form/Test/FormBuilderInterface.php', - 'Symfony\\Component\\Form\\Test\\FormIntegrationTestCase' => __DIR__ . '/..' . '/symfony/form/Test/FormIntegrationTestCase.php', - 'Symfony\\Component\\Form\\Test\\FormInterface' => __DIR__ . '/..' . '/symfony/form/Test/FormInterface.php', - 'Symfony\\Component\\Form\\Test\\FormPerformanceTestCase' => __DIR__ . '/..' . '/symfony/form/Test/FormPerformanceTestCase.php', - 'Symfony\\Component\\Form\\Test\\Traits\\RunTestTrait' => __DIR__ . '/..' . '/symfony/form/Test/Traits/RunTestTrait.php', - 'Symfony\\Component\\Form\\Test\\Traits\\ValidatorExtensionTrait' => __DIR__ . '/..' . '/symfony/form/Test/Traits/ValidatorExtensionTrait.php', - 'Symfony\\Component\\Form\\Test\\TypeTestCase' => __DIR__ . '/..' . '/symfony/form/Test/TypeTestCase.php', 'Symfony\\Component\\Form\\Util\\FormUtil' => __DIR__ . '/..' . '/symfony/form/Util/FormUtil.php', 'Symfony\\Component\\Form\\Util\\InheritDataAwareIterator' => __DIR__ . '/..' . '/symfony/form/Util/InheritDataAwareIterator.php', 'Symfony\\Component\\Form\\Util\\OptionsResolverWrapper' => __DIR__ . '/..' . '/symfony/form/Util/OptionsResolverWrapper.php', @@ -3412,7 +3407,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => __DIR__ . '/..' . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php', 'Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => __DIR__ . '/..' . '/symfony/security-core/Signature/ExpiredSignatureStorage.php', 'Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => __DIR__ . '/..' . '/symfony/security-core/Signature/SignatureHasher.php', - 'Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => __DIR__ . '/..' . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php', 'Symfony\\Component\\Security\\Core\\User\\AttributesBasedUserProviderInterface' => __DIR__ . '/..' . '/symfony/security-core/User/AttributesBasedUserProviderInterface.php', 'Symfony\\Component\\Security\\Core\\User\\ChainUserChecker' => __DIR__ . '/..' . '/symfony/security-core/User/ChainUserChecker.php', 'Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => __DIR__ . '/..' . '/symfony/security-core/User/ChainUserProvider.php', diff --git a/sources/Application/TwigBase/Controller/AbstractProfilerExtension.php b/sources/Application/TwigBase/Controller/AbstractProfilerExtension.php new file mode 100644 index 0000000000..cb6f9030b1 --- /dev/null +++ b/sources/Application/TwigBase/Controller/AbstractProfilerExtension.php @@ -0,0 +1,38 @@ +m_aLinkedScripts = []; - $this->m_aLinkedStylesheets = []; - $this->m_aSaas = []; - $this->m_aAjaxTabs = []; - $this->m_aDefaultParams = []; - $this->m_aBlockParams = []; + $this->aLinkedScripts = []; + $this->aLinkedStylesheets = []; + $this->aSaas = []; + $this->aAjaxTabs = []; + $this->aDefaultParams = []; + $this->aBlockParams = []; $this->SetModuleName($sModuleName); // Initialize Symfony components $this->InitSymfonyComponents($sViewPath, $sModuleName); + $this->InitDebugExtensions(); } /** @@ -136,11 +137,27 @@ private function InitSymfonyComponents(string $sViewPath, string $sModuleName): // Twig environment $aAdditionalPaths[] = APPROOT.'lib/symfony/twig-bridge/Resources/views/Form'; $aAdditionalPaths[] = APPROOT.'templates'; + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProfilerExtension::class) as $sExtension) { + /** @var \Combodo\iTop\Application\TwigBase\Controller\iProfilerExtension $oExtensionInstance */ + $oExtensionInstance = $sExtension::GetInstance(); + $path = $oExtensionInstance->GetTemplatesPath(); + if (is_string($path)) { + if (!in_array($path, $aAdditionalPaths)) { + $aAdditionalPaths[] = $path; + } + } else if (is_array($path)) { + foreach ($path as $sPath) { + if (!in_array($sPath, $aAdditionalPaths)) { + $aAdditionalPaths[] = $sPath; + } + } + } + } if (strlen($sViewPath) > 0) { $this->SetViewPath($sViewPath, $aAdditionalPaths); if ($sModuleName != 'core') { try { - $this->m_aDefaultParams = ['sIndexURL' => utils::GetAbsoluteUrlModulePage($this->m_sModule, 'index.php')]; + $this->aDefaultParams = ['sIndexURL' => utils::GetAbsoluteUrlModulePage($this->m_sModule, 'index.php')]; } catch (Exception $e) { IssueLog::Error($e->getMessage()); @@ -170,7 +187,7 @@ public function InitFromModule() $this->SetViewPath($sModulePath.'/view'); try { - $this->m_aDefaultParams = array('sIndexURL' => utils::GetAbsoluteUrlModulePage($this->m_sModule, 'index.php')); + $this->aDefaultParams = array('sIndexURL' => utils::GetAbsoluteUrlModulePage($this->m_sModule, 'index.php')); } catch (Exception $e) { @@ -194,7 +211,7 @@ public function SetViewPath($sViewPath, $aAdditionalPaths = []) }, ])); $oTwig->addExtension(new FormExtension()); - $this->m_oTwig = $oTwig; + $this->oTwig = $oTwig; } /** @@ -226,10 +243,7 @@ public function HandleOperation() try { $this->CheckAccess(); - $this->m_sOperation = utils::ReadParam('operation', $this->m_sDefaultOperation); - - $oKPI = new ExecutionKPI(); - $oKPI->ComputeAndReport('Starting operation '.$this->m_sOperation); + $this->m_sOperation = utils::ReadParam('operation', $this->sDefaultOperation); if ($this->CallOperation(utils::ToCamelCase($this->m_sOperation))) { return; @@ -264,7 +278,7 @@ public function HandleAjaxOperation() try { $this->CheckAccess(); - $this->m_sOperation = utils::ReadParam('operation', $this->m_sDefaultOperation); + $this->m_sOperation = utils::ReadParam('operation', $this->sDefaultOperation); if ($this->CallOperation(utils::ToCamelCase($this->m_sOperation))) { return; @@ -320,41 +334,41 @@ public function DisplayPageNotFound() */ protected function CheckAccess() { - if ($this->m_bCheckDemoMode && MetaModel::GetConfig()->Get('demo_mode')) + if ($this->bCheckDemoMode && MetaModel::GetConfig()->Get('demo_mode')) { throw new Exception("Sorry, iTop is in demonstration mode: this feature is disabled."); } $sExecModule = utils::ReadParam('exec_module', ""); - $sConfiguredAccessTokenValue = empty($this->m_sAccessTokenConfigParamId) ? "" : trim(MetaModel::GetConfig()->GetModuleSetting($sExecModule, $this->m_sAccessTokenConfigParamId)); + $sConfiguredAccessTokenValue = empty($this->sAccessTokenConfigParamId) ? "" : trim(MetaModel::GetConfig()->GetModuleSetting($sExecModule, $this->sAccessTokenConfigParamId)); if (empty($sExecModule) || empty($sConfiguredAccessTokenValue)){ - LoginWebPage::DoLogin($this->m_bMustBeAdmin); + LoginWebPage::DoLogin($this->bMustBeAdmin); } else { //token mode without login required //N°7147 - Error HTTP 500 due to access_token not URL decoded - $sPassedToken = utils::ReadPostedParam($this->m_sAccessTokenConfigParamId, null, false, 'raw_data'); + $sPassedToken = utils::ReadPostedParam($this->sAccessTokenConfigParamId, null, false, 'raw_data'); if (is_null($sPassedToken)){ - $sPassedToken = utils::ReadParam($this->m_sAccessTokenConfigParamId, null, false, 'raw_data'); + $sPassedToken = utils::ReadParam($this->sAccessTokenConfigParamId, null, false, 'raw_data'); } $sDecodedPassedToken = urldecode($sPassedToken); if ($sDecodedPassedToken !== $sConfiguredAccessTokenValue){ - $sMsg = "Invalid token passed under '$this->m_sAccessTokenConfigParamId' http param to reach '$sExecModule' page."; + $sMsg = "Invalid token passed under '$this->sAccessTokenConfigParamId' http param to reach '$sExecModule' page."; IssueLog::Error($sMsg, null, [ 'sHtmlDecodedToken' => $sDecodedPassedToken, - 'conf param ID' => $this->m_sAccessTokenConfigParamId + 'conf param ID' => $this->sAccessTokenConfigParamId ] ); throw new Exception("Invalid token"); } } - if (!empty($this->m_sMenuId)) + if (!empty($this->sMenuId)) { - ApplicationMenu::CheckMenuIdEnabled($this->m_sMenuId); + ApplicationMenu::CheckMenuIdEnabled($this->sMenuId); } } @@ -364,7 +378,7 @@ protected function CheckAccess() */ private function GetDefaultParameters() { - return $this->m_aDefaultParams; + return $this->aDefaultParams; } /** @@ -374,7 +388,7 @@ private function GetDefaultParameters() */ public function DisableInDemoMode() { - $this->m_bCheckDemoMode = true; + $this->bCheckDemoMode = true; } /** @@ -384,7 +398,7 @@ public function DisableInDemoMode() */ public function AllowOnlyAdmin() { - $this->m_bMustBeAdmin = true; + $this->bMustBeAdmin = true; } /** @@ -406,7 +420,7 @@ public function AllowOnlyAdmin() */ public function SetAccessTokenConfigParamId(string $m_sAccessTokenConfigParamId): void { - $this->m_sAccessTokenConfigParamId = trim($m_sAccessTokenConfigParamId) ?? ""; + $this->sAccessTokenConfigParamId = trim($m_sAccessTokenConfigParamId) ?? ""; } /** @@ -418,7 +432,7 @@ public function SetAccessTokenConfigParamId(string $m_sAccessTokenConfigParamId) */ public function SetMenuId($sMenuId) { - $this->m_sMenuId = $sMenuId; + $this->sMenuId = $sMenuId; } /** @@ -430,7 +444,7 @@ public function SetMenuId($sMenuId) */ public function SetDefaultOperation($sDefaultOperation) { - $this->m_sDefaultOperation = $sDefaultOperation; + $this->sDefaultOperation = $sDefaultOperation; } /** @@ -481,42 +495,49 @@ public function DisplayPage($aParams = array(), $sTemplateName = null, $sPageTyp } $aParams = array_merge($this->GetDefaultParameters(), $aParams); $this->CreatePage($sPageType); - $sHTMLContent = $this->RenderTemplate($aParams, $sTemplateName, 'html'); + $sHTMLContent = $this->RenderTemplate($aParams, $sTemplateName, 'html', $sErrorMsg); if ($sHTMLContent !== false) { $this->AddToPage($sHTMLContent); } - $sJSScript = $this->RenderTemplate($aParams, $sTemplateName, 'js'); + $sJSScript = $this->RenderTemplate($aParams, $sTemplateName, 'js', $sErrorMsg); if ($sJSScript !== false) { $this->AddScriptToPage($sJSScript); } - $sReadyScript = $this->RenderTemplate($aParams, $sTemplateName, 'ready.js'); + $sReadyScript = $this->RenderTemplate($aParams, $sTemplateName, 'ready.js', $sErrorMsg); if ($sReadyScript !== false) { $this->AddReadyScriptToPage($sReadyScript); } - $sStyle = $this->RenderTemplate($aParams, $sTemplateName, 'css'); + $sStyle = $this->RenderTemplate($aParams, $sTemplateName, 'css', $sErrorMsg); if ($sStyle !== false) { $this->AddStyleToPage($sStyle); } if ($sHTMLContent === false && $sJSScript === false && $sReadyScript === false && $sStyle === false) { - IssueLog::Error("Missing TWIG template for $sTemplateName"); + if (utils::IsNullOrEmptyString($sErrorMsg)) { + $sErrorMsg = "Missing TWIG template for $sTemplateName"; + } + IssueLog::Error($sErrorMsg); + $this->AddToPage($this->oTwig->render('application/forms/itop_error.html.twig', ['sControllerError' => $sErrorMsg])); } - if (!empty($this->m_aAjaxTabs)) { - $this->m_oPage->AddTabContainer('TwigBaseTabContainer'); - $this->m_oPage->SetCurrentTabContainer('TwigBaseTabContainer'); + + $this->ManageDebugExtensions($aParams); + + if (!empty($this->aAjaxTabs)) { + $this->oPage->AddTabContainer('TwigBaseTabContainer'); + $this->oPage->SetCurrentTabContainer('TwigBaseTabContainer'); } - foreach ($this->m_aAjaxTabs as $sTabCode => $aTabData) { + foreach ($this->aAjaxTabs as $sTabCode => $aTabData) { $this->AddAjaxTabToPage($sTabCode, $aTabData['label'], $aTabData['url'], $aTabData['cache']); } - foreach ($this->m_aLinkedScripts as $sLinkedScript) { + foreach ($this->aLinkedScripts as $sLinkedScript) { $this->AddLinkedScriptToPage($sLinkedScript); } - foreach ($this->m_aLinkedStylesheets as $sLinkedStylesheet) { + foreach ($this->aLinkedStylesheets as $sLinkedStylesheet) { $this->AddLinkedStylesheetToPage($sLinkedStylesheet); } - foreach ($this->m_aSaas as $sSaasRelPath) { + foreach ($this->aSaas as $sSaasRelPath) { $this->AddSaasToPage($sSaasRelPath); } - foreach ($this->m_aBlockParams as $sKey => $value) { + foreach ($this->aBlockParams as $sKey => $value) { $this->SetBlockParamToPage($sKey, $value); } $this->OutputPage(); @@ -646,7 +667,7 @@ final protected function SendFileContent($sFilePath, $sDownloadArchiveName = nul */ public function AddLinkedScript($sScript) { - $this->m_aLinkedScripts[] = $sScript; + $this->aLinkedScripts[] = $sScript; } /** @@ -659,7 +680,7 @@ public function AddLinkedScript($sScript) */ public function AddLinkedStylesheet($sStylesheet) { - $this->m_aLinkedStylesheets[] = $sStylesheet; + $this->aLinkedStylesheets[] = $sStylesheet; } /** @@ -671,7 +692,7 @@ public function AddLinkedStylesheet($sStylesheet) */ public function AddSaas($sSaasRelPath) { - $this->m_aSaas[] = $sSaasRelPath; + $this->aSaas[] = $sSaasRelPath; } /** @@ -690,7 +711,7 @@ public function AddAjaxTab($sCode, $sURL, $bCache = true, $sLabel = null) if (is_null($sLabel)) { $sLabel = Dict::S($sCode); } - $this->m_aAjaxTabs[$sCode] = array('label' => $sLabel, 'url' => $sURL, 'cache' => $bCache); + $this->aAjaxTabs[$sCode] = array('label' => $sLabel, 'url' => $sURL, 'cache' => $bCache); } /** @@ -699,7 +720,7 @@ public function AddAjaxTab($sCode, $sURL, $bCache = true, $sLabel = null) */ public function SetBlockParams(array $aBlockParams) { - $this->m_aBlockParams = $aBlockParams; + $this->aBlockParams = $aBlockParams; } /** @@ -707,7 +728,7 @@ public function SetBlockParams(array $aBlockParams) * @see Controller::SetBreadCrumbEntry() to set breadcrumb content (by default will be title) */ public function DisableBreadCrumb() { - $this->m_bIsBreadCrumbEnabled = false; + $this->bIsBreadCrumbEnabled = false; } /** @@ -715,7 +736,7 @@ public function DisableBreadCrumb() { * @see iTopWebPage::SetBreadCrumbEntry() */ public function SetBreadCrumbEntry($sId, $sLabel, $sDescription, $sUrl = '', $sIcon = '') { - $this->m_aBreadCrumbEntry = [$sId, $sLabel, $sDescription, $sUrl, $sIcon]; + $this->aBreadCrumbEntry = [$sId, $sLabel, $sDescription, $sUrl, $sIcon]; } public function GetRequest(): Request @@ -764,67 +785,79 @@ public function GetForm(string $type = FormType::class, mixed $data = null, arra * @return string|false * @throws \Exception */ - private function RenderTemplate($aParams, $sName, $sTemplateFileExtension) + private function RenderTemplate(array $aParams, string $sName, string $sTemplateFileExtension, string &$sErrorMsg = null): string|false { - if (empty($this->m_oTwig)) + $sTemplateFile = $sName.'.'.$sTemplateFileExtension.'.twig'; + if (empty($this->oTwig)) { throw new Exception('Not initialized. Call Controller::InitFromModule() or Controller::SetViewPath() before any display'); } try { - return $this->m_oTwig->render($sName.'.'.$sTemplateFileExtension.'.twig', $aParams); + return $this->oTwig->render($sTemplateFile, $aParams); } catch (SyntaxError $e) { IssueLog::Error($e->getMessage().' - file: '.$e->getFile().'('.$e->getLine().')'); + return $this->oTwig->render('application/forms/itop_error.html.twig', ['sControllerError' => $e->getMessage()]); } - catch (Error $e) { - if (strpos($e->getMessage(), 'Unable to find template') === false) + catch (Exception $e) { + $sExceptionMessage = $e->getMessage(); + if (str_contains($sExceptionMessage, 'at line')) { + IssueLog::Error($sExceptionMessage); + return $this->oTwig->render('application/forms/itop_error.html.twig', ['sControllerError' => $sExceptionMessage]); + } + if (!str_contains($sExceptionMessage, 'Unable to find template')) { - IssueLog::Error($e->getMessage()); + IssueLog::Error($sExceptionMessage); } + if (is_null($sErrorMsg)) { + $sErrorMsg = ''; + } + $sErrorMsg .= $sExceptionMessage."\n"; } return false; } /** - * @param $sPageType + * @param string $sPageType * * @throws \Exception */ - private function CreatePage($sPageType) + private function CreatePage(string $sPageType): void { switch ($sPageType) { case self::ENUM_PAGE_TYPE_HTML: - $this->m_oPage = new iTopWebPage($this->GetOperationTitle(), false); - $this->m_oPage->add_http_headers(); + $this->oPage = new iTopWebPage($this->GetOperationTitle(), false); + $this->oPage->add_http_headers(); - if ($this->m_bIsBreadCrumbEnabled) { - if (count($this->m_aBreadCrumbEntry) > 0) { - list($sId, $sTitle, $sDescription, $sUrl, $sIcon) = $this->m_aBreadCrumbEntry; - $this->m_oPage->SetBreadCrumbEntry($sId, $sTitle, $sDescription, $sUrl, $sIcon); + if ($this->bIsBreadCrumbEnabled) { + if (count($this->aBreadCrumbEntry) > 0) { + list($sId, $sTitle, $sDescription, $sUrl, $sIcon) = $this->aBreadCrumbEntry; + $this->oPage->SetBreadCrumbEntry($sId, $sTitle, $sDescription, $sUrl, $sIcon); } } else { - $this->m_oPage->DisableBreadCrumb(); + $this->oPage->DisableBreadCrumb(); } break; case self::ENUM_PAGE_TYPE_BASIC_HTML: - $this->m_oPage = new WebPage($this->GetOperationTitle()); + $this->oPage = new WebPage($this->GetOperationTitle()); break; case self::ENUM_PAGE_TYPE_AJAX: - $this->m_oPage = new AjaxPage($this->GetOperationTitle()); + $this->oPage = new AjaxPage($this->GetOperationTitle()); break; case self::ENUM_PAGE_TYPE_SETUP: - $this->m_oPage = new SetupPage($this->GetOperationTitle()); + $this->oPage = new SetupPage($this->GetOperationTitle()); break; } - $this->m_oTwig->addGlobal('UIBlockParent', [$this->m_oPage]); - $this->m_oTwig->addGlobal('oPage', $this->m_oPage); + $this->oTwig->addGlobal('UIBlockParent', [$this->oPage]); + $this->oTwig->addGlobal('oPage', $this->oPage); + $this->oTwig->addGlobal('debug', utils::IsDevelopmentEnvironment()); } /** @@ -853,42 +886,42 @@ public function GetOperation(): string */ private function AddToPage($sContent) { - $this->m_oPage->add($sContent); + $this->oPage->add($sContent); } private function AddReadyScriptToPage($sScript) { - $this->m_oPage->add_ready_script($sScript); + $this->oPage->add_ready_script($sScript); } private function AddScriptToPage($sScript) { - $this->m_oPage->add_script($sScript); + $this->oPage->add_script($sScript); } private function AddLinkedScriptToPage($sLinkedScript) { - $this->m_oPage->LinkScriptFromURI($sLinkedScript); + $this->oPage->LinkScriptFromURI($sLinkedScript); } private function AddLinkedStylesheetToPage($sLinkedStylesheet) { - $this->m_oPage->LinkStylesheetFromURI($sLinkedStylesheet); + $this->oPage->LinkStylesheetFromURI($sLinkedStylesheet); } private function AddStyleToPage($sStyle) { - $this->m_oPage->add_style($sStyle); + $this->oPage->add_style($sStyle); } private function AddSaasToPage($sSaasRelPath) { - $this->m_oPage->add_saas($sSaasRelPath); + $this->oPage->add_saas($sSaasRelPath); } private function AddAjaxTabToPage($sCode, $sTitle, $sURL, $bCache) { - $this->m_oPage->AddAjaxTab($sCode, $sURL, $bCache, $sTitle); + $this->oPage->AddAjaxTab($sCode, $sURL, $bCache, $sTitle); } /** @@ -898,7 +931,7 @@ private function AddAjaxTabToPage($sCode, $sTitle, $sURL, $bCache) */ private function SetBlockParamToPage(string $sKey, $value) { - $this->m_oPage->SetBlockParam($sKey, $value); + $this->oPage->SetBlockParam($sKey, $value); } /** @@ -906,6 +939,49 @@ private function SetBlockParamToPage(string $sKey, $value) */ private function OutputPage() { - $this->m_oPage->output(); + $this->oPage->output(); + } + + private function InitDebugExtensions() + { + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProfilerExtension::class) as $sExtension) { + /** @var \Combodo\iTop\Application\TwigBase\Controller\iProfilerExtension $oExtensionInstance */ + $oExtensionInstance = $sExtension::GetInstance(); + $oExtensionInstance->Init(); + } + } + + /** + * @param array $aParams + * + * @return void + * @throws \ReflectionException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + private function ManageDebugExtensions(array $aParams): void + { + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProfilerExtension::class) as $sExtension) { + /** @var \Combodo\iTop\Application\TwigBase\Controller\iProfilerExtension $oExtensionInstance */ + $oExtensionInstance = $sExtension::GetInstance(); + if ($oExtensionInstance->IsEnabled()) { + $sDebugTemplate = $oExtensionInstance->GetDebugTemplate(); + $aDebugParams = $oExtensionInstance->GetDebugParams($aParams); + $aLinkedScripts = $oExtensionInstance->GetLinkedScripts(); + if (is_array($aLinkedScripts)) { + $this->aLinkedScripts = array_merge($this->aLinkedScripts, $aLinkedScripts); + } + $aLinkedStylesheets = $oExtensionInstance->GetLinkedStylesheets(); + if (is_array($aLinkedStylesheets)) { + $this->aLinkedStylesheets = array_merge($this->aLinkedStylesheets, $aLinkedStylesheets); + } + $aSaas = $oExtensionInstance->GetSaas(); + if (is_array($aSaas)) { + $this->aSaas = array_merge($this->aSaas, $aSaas); + } + $this->AddToPage($this->oTwig->render($sDebugTemplate, $aDebugParams)); + } + } } } diff --git a/sources/Application/TwigBase/Controller/iProfilerExtension.php b/sources/Application/TwigBase/Controller/iProfilerExtension.php new file mode 100644 index 0000000000..ebc5f1bb32 --- /dev/null +++ b/sources/Application/TwigBase/Controller/iProfilerExtension.php @@ -0,0 +1,20 @@ +iCurrentMemory = $iCurrentMemory; $this->iPeakMemory = $iPeakMemory; $this->aData = $aData; + $this->fDuration = sprintf('%01.4f', $this->fStopTime - $this->fStartTime); } /** @@ -74,12 +77,11 @@ public static function GetCSVHeader() */ public function GetCSV() { - $fDuration = sprintf('%01.4f', $this->fStopTime - $this->fStartTime); $sType = $this->RemoveQuotes($this->sType); $sOperation = $this->RemoveQuotes($this->sOperation); $sArguments = $this->RemoveQuotes($this->sArguments); $sExtension = $this->RemoveQuotes($this->sExtension); - return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$fDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory"; + return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$this->fDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory"; } private function RemoveQuotes(string $sEntry): string diff --git a/templates/application/forms/itop_error.html.twig b/templates/application/forms/itop_error.html.twig new file mode 100644 index 0000000000..9d6e832c5c --- /dev/null +++ b/templates/application/forms/itop_error.html.twig @@ -0,0 +1,6 @@ +{# @copyright Copyright (C) 2010-2025 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +{% if sControllerError %} + {% UIAlert ForDanger { sTitle:'UI:Error:TwigController'|dict_s, sContent:sControllerError } %}{% EndUIAlert %} +{% endif %} diff --git a/templates/base/components/input/select/select.html.twig b/templates/base/components/input/select/select.html.twig index 5fd21518cc..a53d14c055 100644 --- a/templates/base/components/input/select/select.html.twig +++ b/templates/base/components/input/select/select.html.twig @@ -5,7 +5,7 @@ {% include "base/components/input/inputlabel.html.twig" %} {% endif %}