diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index c729092df..7672347f3 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -26,7 +26,6 @@ use Combodo\iTop\Application\UI\DisplayBlock\BlockCsv\BlockCsv; use Combodo\iTop\Application\UI\DisplayBlock\BlockList\BlockList; use Combodo\iTop\Application\WebPage\iTopWebPage; use Combodo\iTop\Application\WebPage\WebPage; -use Combodo\iTop\Service\Router\Router; require_once(APPROOT.'/application/utils.inc.php'); @@ -1848,7 +1847,6 @@ class MenuBlock extends DisplayBlock */ public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId) { - $oRouter = Router::GetInstance(); $oRenderBlock = new UIContentBlock(); if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility @@ -2068,7 +2066,8 @@ class MenuBlock extends DisplayBlock if ($bIsModifyAllowed) { $aRegularActions['UI:Menu:Modify'] = array( 'label' => Dict::S('UI:Menu:Modify'), - 'url' => $oRouter->GenerateUrl('object.modify', ['class' => $sClass, 'id' => $id]) . "{$sContext}#", + // Can't use URL Generator (`$this->oUrlGenerator->generate("b_object_summary", [$sObjClass, $sObjKey])`) yet as we have to find how to inject it here + 'url' => "{$sRootUrl}app.php/object/modify/{$sClass}/{$id}{$sContext}#", ) + $aActionParams; } if ($bIsDeleteAllowed) { diff --git a/sources/Application/UI/Base/Layout/Object/ObjectSummary.php b/sources/Application/UI/Base/Layout/Object/ObjectSummary.php index d692e53be..0b2b1c7a7 100644 --- a/sources/Application/UI/Base/Layout/Object/ObjectSummary.php +++ b/sources/Application/UI/Base/Layout/Object/ObjectSummary.php @@ -11,7 +11,6 @@ use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\Popov use Combodo\iTop\Application\UI\Base\tUIContentAreas; use Combodo\iTop\Application\UI\Base\UIBlock; use Combodo\iTop\Core\MetaModel\FriendlyNameType; -use Combodo\iTop\Service\Router\Router; use DBObject; use Dict; use MetaModel; @@ -100,12 +99,11 @@ class ObjectSummary extends ObjectDetails */ private function ComputeActions() { - $oRouter = Router::GetInstance(); $oDetailsButton = null; // We can pass a DBObject to the UIBlock, so we check for the DisplayModifyForm method if(method_exists($this->oObject, 'DisplayModifyForm') && UserRights::IsActionAllowed($this->sClassName, UR_ACTION_MODIFY)) { $oPopoverMenu = new PopoverMenu(); - + $oDetailsAction = new URLPopupMenuItem( 'UI:Menu:View', Dict::S('UI:Menu:View'), @@ -113,12 +111,13 @@ class ObjectSummary extends ObjectDetails '_blank' ); $oModifyButton = ButtonUIBlockFactory::MakeLinkNeutral( - $oRouter->GenerateUrl('object.modify', ['class' => $this->sClassName, 'id' => $this->sObjectId]), + // Can't use URL Generator (`$this->oUrlGenerator->generate("b_object_summary", [$sObjClass, $sObjKey])`) yet as we have to find how to inject it here + "{$sRootUrl}app.php/object/modify/{$this->sClassName}/{$this->sObjectId}", Dict::S('UI:Menu:Modify'), 'fas fa-external-link-alt', '_blank', ); - + $oPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oDetailsAction))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT); $oDetailsButton = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oModifyButton, $oPopoverMenu); diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php index 3c45090ab..48f3901e6 100644 --- a/sources/Controller/Base/Layout/ObjectController.php +++ b/sources/Controller/Base/Layout/ObjectController.php @@ -30,7 +30,12 @@ use Combodo\iTop\Application\WebPage\iTopWebPage; use Combodo\iTop\Application\WebPage\JsonPage; use MetaModel; use SecurityException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Routing\Annotation\Route; use Combodo\iTop\Service\SummaryCard\SummaryCardService; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use UserRights; use utils; @@ -42,30 +47,47 @@ use utils; * @since 3.1.0 * @package Combodo\iTop\Controller\Base\Layout */ +#[Route("/object", name: "b_object_")] class ObjectController extends AbstractController { - public const ROUTE_NAMESPACE = 'object'; + /** @deprecated 3.2.0 N°6935 Replaced by Symfony route name prefix on the controller */ + //public const ROUTE_NAMESPACE = 'object'; /** - * @throws \CoreException - * @throws \MySQLHasGoneAwayException - * @throws \MySQLException - * @throws \DictExceptionMissingString - * @throws \CoreUnexpectedValue - * @throws \ConfigException - * @throws \ApplicationException - * @throws \MissingQueryArgument + * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $oUrlGenerator + * @since 3.2.0 N°6935 */ - public function OperationNew() + public function __construct( + protected UrlGeneratorInterface $oUrlGenerator + ) { + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * @param string $sClass Class of the datamodel object to create + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * @throws ApplicationException + */ + #[Route('/new/{sClass}', name: 'new')] + public function OperationNew(Request $oRequest, string $sClass): Response + { + // Retrieve parameters $bPrintable = utils::ReadParam('printable', '0') === '1'; - $sClass = utils::ReadParam('class', '', false, 'class'); $sStateCode = utils::ReadParam('state', ''); $bCheckSubClass = utils::ReadParam('checkSubclass', true); + + // Check parameters + if (false === MetaModel::IsValidClass($sClass)) { + throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist')); + } + $oAppContext = new ApplicationContext(); - $oRouter = Router::GetInstance(); - - if ($this->IsHandlingXmlHttpRequest()) { + + if ($oRequest->isXmlHttpRequest()) { $oPage = new AjaxPage(''); } else { $oPage = new iTopWebPage('', $bPrintable); @@ -73,7 +95,6 @@ class ObjectController extends AbstractController $this->AddRequiredForModificationJsFilesToPage($oPage); } - if (empty($sClass)) { throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); @@ -133,7 +154,7 @@ class ObjectController extends AbstractController // - Update flags with parameters set in URL FormHelper::UpdateFlagsFromContext($oObjToClone, $aFormExtraParams); - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aFormExtraParams['js_handlers'] = []; $aFormExtraParams['noRelations'] = true; $aFormExtraParams['hide_transitions'] = true; @@ -187,9 +208,9 @@ JS; cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObjToClone, array(), $aFormExtraParams); } else { - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $oClassForm = cmdbAbstractObject::DisplayFormBlockSelectClassToCreate($sClass, MetaModel::GetName($sClass), $oAppContext, $aPossibleClasses, ['state' => $sStateCode]); - $sCurrentUrl = $oRouter->GenerateUrl('object.new'); + $sCurrentUrl = $this->oUrlGenerator->generate('b_object_new'); $oClassForm->SetOnSubmitJsCode( << $sStateCode]); } } - return $oPage; + + return $oPage->GenerateResponse(); } /** - * @return iTopWebPage|AjaxPage Object edit form in its webpage + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * @param string $sClass Class of the datamodel object to modify + * @param string $sId ID of the object to modify + * + * @return \Symfony\Component\HttpFoundation\Response + * * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \CoreException * @throws \SecurityException * @throws \Exception */ - public function OperationModify() + #[Route('/modify/{sClass}/{sId}', name: 'modify')] + public function OperationModify(Request $oRequest, string $sClass, string $sId): Response { + // Retrieve and check parameters $bPrintable = utils::ReadParam('printable', '0') === '1'; - $sClass = utils::ReadParam('class', '', false, 'class'); - $sId = utils::ReadParam('id', ''); $sFormTitle = utils::ReadPostedParam('form_title', null, utils::ENUM_SANITIZATION_FILTER_STRING); // Check parameters + if (false === MetaModel::IsValidClass($sClass)) { + throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist')); + } + if (utils::IsNullOrEmptyString($sClass) || utils::IsNullOrEmptyString($sId)) { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); @@ -254,7 +285,7 @@ JS $aFormExtraParams['form_title'] = $sFormTitle; } - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $oPage = new AjaxPage(''); $aFormExtraParams['js_handlers'] = []; $aFormExtraParams['noRelations'] = true; @@ -321,21 +352,25 @@ JS; // Note: Code duplicated to the case 'apply_modify' in UI.php when a data integrity issue has been found $oObj->DisplayModifyForm($oPage, $aFormExtraParams); // wizard_container: Display the title above the form - return $oPage; + return $oPage->GenerateResponse(); } /** - * @return iTopWebPage|JsonPage Object edit form in its webpage + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * + * @return \Symfony\Component\HttpFoundation\Response + * * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \CoreException * @throws \SecurityException */ - public function OperationApplyNew() + #[Route('/apply_new', name: 'apply_new')] + public function OperationApplyNew(Request $oRequest): Response { $bPrintable = utils::ReadParam('printable', '0') === '1'; $aResult = []; - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $oPage = new JsonPage(); $oPage->SetOutputDataOnly(true); $aResult['success'] = false; @@ -367,7 +402,7 @@ JS; $sUser = UserRights::GetUser(); IssueLog::Error(__CLASS__.'::'.__METHOD__." : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:Error:ObjectAlreadyCreated')]; } else { $oErrorAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:Error:ObjectAlreadyCreated')); @@ -464,7 +499,7 @@ JS; } } else { // Nothing more to do - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['success'] = true; $aResult['data'] = ['object' => ObjectRepository::ConvertObjectToArray($oObj, $sClass)]; } else { @@ -476,7 +511,7 @@ JS; // Found issues, explain and give the user a second chance // $aIssues = $e->getIssues(); - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => $e->getHtmlMessage()]; } else { $sClassLabel = MetaModel::GetName($sClass); @@ -494,15 +529,18 @@ JS; } } } - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $oPage->SetData($aResult); } - return $oPage; + return $oPage->GenerateResponse(); } /** - * @return iTopWebPage|JsonPage + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * + * @return \Symfony\Component\HttpFoundation\Response + * * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \ConfigException @@ -511,10 +549,12 @@ JS; * @throws \DictExceptionMissingString * @throws \MySQLException */ - public function OperationApplyModify(){ + public function OperationApplyModify(Request $oRequest): Response + { $bPrintable = utils::ReadParam('printable', '0') === '1'; $aResult = []; - if ($this->IsHandlingXmlHttpRequest()) { + + if ($oRequest->isXmlHttpRequest()) { $oPage = new JsonPage(); $oPage->SetOutputDataOnly(true); $aResult['success'] = false; @@ -546,7 +586,7 @@ JS; { $bDisplayDetails = false; - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:ObjectDoesNotExist')]; } else { $oPage->set_title(Dict::S('UI:ErrorPageTitle')); @@ -572,7 +612,7 @@ JS; $sUser = UserRights::GetUser(); IssueLog::Error(__CLASS__.'::'.__METHOD__." : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:Error:ObjectAlreadyUpdated')]; } else { $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding @@ -598,7 +638,7 @@ JS; if (!$oObj->IsModified() && empty($aErrors)) { - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())]; } else { $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding @@ -646,7 +686,7 @@ JS; $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); $sSeverity = 'ok'; - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['success'] = true; } } @@ -656,7 +696,7 @@ JS; // $bDisplayDetails = false; $aIssues = $e->getIssues(); - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => $e->getHtmlMessage()]; } else { $oPage->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); @@ -667,7 +707,7 @@ JS; } catch (DeleteException $e) { - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())]; } else { // Say two things: @@ -706,7 +746,7 @@ JS; // Nothing more to do $sMessage = isset($sMessage) ? $sMessage : ''; $sSeverity = isset($sSeverity) ? $sSeverity : null; - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { ; } else { ReloadAndDisplay($oPage, $oObj, 'update', $sMessage, $sSeverity); @@ -724,27 +764,38 @@ JS; } } } - if ($this->IsHandlingXmlHttpRequest()) { + if ($oRequest->isXmlHttpRequest()) { $oPage->SetData($aResult); } - return $oPage; + return $oPage->GenerateResponse(); } - public function OperationSummary() { + /** + * @param \Symfony\Component\HttpFoundation\Request $oRequest + * @param string $sClass Class of the datamodel object to view + * @param string $sId ID of the datamodel object to view (note that friendlyname can also be used but is less efficient) + * + * @return \Symfony\Component\HttpFoundation\Response + */ + #[Route('/summary/{sClass}/{sId}', name: 'summary')] + public function OperationSummary(Request $oRequest, string $sClass, string $sId): Response + { $oPage = new AjaxPage(''); - $sClass = utils::ReadParam('obj_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); - $sObjectKey = utils::ReadParam('obj_key', 0, false); + // Check parameters + if (false === MetaModel::IsValidClass($sClass)) { + throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist')); + } // - Check if we are allowed to see/make summary for this class if(SummaryCardService::IsAllowedForClass($sClass)){ - if (is_numeric($sObjectKey)) + if (is_numeric($sId)) { - $oObj = MetaModel::GetObject($sClass, $sObjectKey, false /* MustBeFound */); + $oObj = MetaModel::GetObject($sClass, $sId, false /* MustBeFound */); } else { - $oObj = MetaModel::GetObjectByName($sClass, $sObjectKey, false /* MustBeFound */); + $oObj = MetaModel::GetObjectByName($sClass, $sId, false /* MustBeFound */); } if($oObj !== null) { @@ -758,7 +809,7 @@ JS; ->SetIsClosable(false) ); } - return $oPage; + return $oPage->GenerateResponse(); } /** diff --git a/sources/Service/SummaryCard/SummaryCardService.php b/sources/Service/SummaryCard/SummaryCardService.php index 46c4fd44a..97eb7824e 100644 --- a/sources/Service/SummaryCard/SummaryCardService.php +++ b/sources/Service/SummaryCard/SummaryCardService.php @@ -8,9 +8,10 @@ namespace Combodo\iTop\Service\SummaryCard; use appUserPreferences; use Combodo\iTop\Core\MetaModel\FriendlyNameType; -use Combodo\iTop\Service\Router\Router; use MetaModel; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use UserRights; +use utils; /** * Class SummaryCardService @@ -19,10 +20,22 @@ use UserRights; * * @since 3.1.0 */ -class SummaryCardService { +class SummaryCardService +{ + /** + * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $oUrlGenerator + * + * @since 3.2.0 Add constructor and dependency injection of $oUrlGenerator + */ + public function __construct( + protected UrlGeneratorInterface $oUrlGenerator + ) + { + } + /** - * @param $sObjClass + * @param string $sObjClass * @param $sObjKey * * @return string @@ -30,9 +43,9 @@ class SummaryCardService { */ public static function GetHyperlinkMarkup(string $sObjClass, $sObjKey): string { - $oRouter = Router::GetInstance(); - $sRoute = $oRouter->GenerateUrl("object.summary", ["obj_class" => $sObjClass, "obj_key" => $sObjKey]); - return + // Can't use URL Generator (`$this->oUrlGenerator->generate("b_object_summary", [$sObjClass, $sObjKey])`) yet as we have to find how to inject it here + $sRoute = utils::GetAbsoluteUrlAppRoot() . "app.php/object/summary/$sObjClass/$sObjKey"; + return <<