N°8771 - Add Symfony form component to iTop core (#760)

- Add Symfony Form Component
- Add Symfony CSRF security component
- Add iTop default form template
- Add Twig debug extension to Twig Environment
- Add iTop abstract controller facility to get form builder
- Add Twig filter to make trans an alias of dict_s filter
This commit is contained in:
Benjamin Dalsass
2025-10-10 16:02:25 +02:00
committed by GitHub
parent 82395727bf
commit 5dd450e9bf
605 changed files with 60106 additions and 12 deletions

View File

@@ -19,25 +19,38 @@
namespace Combodo\iTop\Application\TwigBase\Controller;
use Combodo\iTop\Application\WebPage\AjaxPage;
use ApplicationMenu;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\WebPage\AjaxPage;
use Combodo\iTop\Application\WebPage\ErrorPage;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Controller\AbstractController;
use Dict;
use Combodo\iTop\Application\WebPage\ErrorPage;
use Exception;
use ExecutionKPI;
use IssueLog;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use LoginWebPage;
use MetaModel;
use ReflectionClass;
use SetupPage;
use SetupUtils;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Forms;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Error\Error;
use Twig\Error\SyntaxError;
use Twig\RuntimeLoader\FactoryRuntimeLoader;
use utils;
use Combodo\iTop\Application\WebPage\WebPage;
use ZipArchive;
abstract class Controller extends AbstractController
@@ -81,6 +94,15 @@ abstract class Controller extends AbstractController
/** @var array contains same parameters as {@see iTopWebPage::SetBreadCrumbEntry()} */
private $m_aBreadCrumbEntry = [];
/** @var Request Request (from Symfony http_foundation component @link https://symfony.com/doc/current/components/http_foundation.html) */
private Request $oRequest;
/** @var FormFactoryBuilderInterface Factory form builder (from Symfony form component @link https://symfony.com/doc/current/components/form.html) */
private FormFactoryBuilderInterface $oFormFactoryBuilder;
/** @var CsrfTokenManager Csrf manager (from Symfony form component @link https://symfony.com/doc/current/security/csrf.html) */
private CsrfTokenManager $oCsrfTokenManager;
/**
* Controller constructor.
*
@@ -96,6 +118,24 @@ abstract class Controller extends AbstractController
$this->m_aDefaultParams = [];
$this->m_aBlockParams = [];
$this->SetModuleName($sModuleName);
// Initialize Symfony components
$this->InitSymfonyComponents($sViewPath, $sModuleName);
}
/**
* Init Symfony components.
*
* @param string $sViewPath
* @param string $sModuleName
*
* @return void
*/
private function InitSymfonyComponents(string $sViewPath, string $sModuleName): void
{
// Twig environment
$aAdditionalPaths[] = APPROOT.'lib/symfony/twig-bridge/Resources/views/Form';
$aAdditionalPaths[] = APPROOT.'templates';
if (strlen($sViewPath) > 0) {
$this->SetViewPath($sViewPath, $aAdditionalPaths);
if ($sModuleName != 'core') {
@@ -107,6 +147,17 @@ abstract class Controller extends AbstractController
}
}
}
// PHP Request object representation from PHP request globals
$this->oRequest = Request::createFromGlobals();
// Initialize the CSRF token manager
$this->oCsrfTokenManager = new CsrfTokenManager();
// Initialize the form factory builder to handle Request objects
$this->oFormFactoryBuilder = Forms::createFormFactoryBuilder()
->addExtension(new HttpFoundationExtension())
->addExtension(new CsrfExtension($this->oCsrfTokenManager));
}
/**
@@ -135,6 +186,14 @@ abstract class Controller extends AbstractController
public function SetViewPath($sViewPath, $aAdditionalPaths = [])
{
$oTwig = TwigHelper::GetTwigEnvironment($sViewPath, $aAdditionalPaths);
/** @link https://github.com/symfony/twig-bridge/blob/6.4/CHANGELOG.md#320 */
$formEngine = new TwigRendererEngine(['application/forms/itop_console_layout.html.twig'], $oTwig);
$oTwig->addRuntimeLoader(new FactoryRuntimeLoader([
FormRenderer::class => function () use ($formEngine): FormRenderer {
return new FormRenderer($formEngine, $this->oCsrfTokenManager);
},
]));
$oTwig->addExtension(new FormExtension());
$this->m_oTwig = $oTwig;
}
@@ -659,6 +718,44 @@ abstract class Controller extends AbstractController
$this->m_aBreadCrumbEntry = [$sId, $sLabel, $sDescription, $sUrl, $sIcon];
}
public function GetRequest(): Request
{
return $this->oRequest;
}
/**
* Get a form builder.
* This form builder can be used to create a form or to add fields to an existing form.
*
* @param string $type
* @param mixed|null $data
* @param array $options
*
* @return FormBuilderInterface
*/
public function GetFormBuilder(string $type = FormType::class, mixed $data = null, array $options = []): FormBuilderInterface
{
return $this->oFormFactoryBuilder->getFormFactory()->createBuilder($type, $data,$options);
}
/**
* Get a form.
* This form can be directly used in a twig template.
*
* @param string $type
* @param mixed|null $data
* @param array $options
*
* @return FormInterface
*/
public function GetForm(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface
{
if (is_null($data)) {
$data = $type::GetDefaultData();
}
return $this->GetFormBuilder($type, $data,$options)->getForm();
}
/**
* @param $aParams
* @param $sName

View File

@@ -11,12 +11,14 @@ use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\UIBlock;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Forms\Twig\Extension\FormCompatibilityExtension;
use Combodo\iTop\Renderer\BlockRenderer;
use CoreTemplateException;
use ExecutionKPI;
use IssueLog;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
use utils;
@@ -80,7 +82,10 @@ class TwigHelper
$oLoader->addPath($sAdditionalPath);
}
$oTwig = new Environment($oLoader);
// Create Twig environment
$oTwig = new Environment($oLoader, [
'debug' => utils::IsDevelopmentEnvironment(),
]);
Extension::RegisterTwigExtensions($oTwig);
if (!utils::IsDevelopmentEnvironment()) {
// Disable the cache in development environment
@@ -90,7 +95,9 @@ class TwigHelper
$oTwig->setCache($sCachePath);
}
$oTwig->addExtension(new DebugExtension());
$oTwig->addExtension(new UIBlockExtension());
$oTwig->addExtension(new FormCompatibilityExtension());
return $oTwig;
}

View File

@@ -84,6 +84,7 @@ class iTopComposer extends AbstractFolderAnalyzer
'symfony/event-dispatcher/Tests',
'symfony/filesystem/Tests',
'symfony/finder/Tests',
'symfony/form/Test',
'symfony/http-client-contracts/Test',
'symfony/http-foundation/Test',
'symfony/http-kernel/Tests',
@@ -91,6 +92,7 @@ class iTopComposer extends AbstractFolderAnalyzer
'symfony/mailer/Test',
'symfony/mime/Test',
'symfony/routing/Tests',
'symfony/security-core/Test',
'symfony/stopwatch/Tests',
'symfony/translation-contracts/Test',
'symfony/twig-bridge/Test',

View File

@@ -0,0 +1,47 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Forms\Twig\Extension;
use Dict;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
/**
* Extension to provide compatibility with Symfony/Twig standard functions
*
* @package Combodo\iTop\Forms\Twig\Extension
*/
class FormCompatibilityExtension extends AbstractExtension
{
/** @inheritdoc */
public function getFilters(): array
{
return [
// Alias of dict_s, to be compatible with Symfony/Twig standard
new TwigFilter('trans', function ($sStringCode, $aData = null, $sTransDomain = false) {
return Dict::S($sStringCode);
})
];
}
}