N°6208 - Router: Add mechanism to generate complete route URL from its code

This commit is contained in:
Molkobain
2023-04-19 16:53:35 +02:00
parent 866d77dd14
commit e3252da5a9
12 changed files with 203 additions and 6 deletions

View File

@@ -24,6 +24,7 @@ use Combodo\iTop\Application\UI\DisplayBlock\BlockChartAjaxBars\BlockChartAjaxBa
use Combodo\iTop\Application\UI\DisplayBlock\BlockChartAjaxPie\BlockChartAjaxPie;
use Combodo\iTop\Application\UI\DisplayBlock\BlockCsv\BlockCsv;
use Combodo\iTop\Application\UI\DisplayBlock\BlockList\BlockList;
use Combodo\iTop\Router\Router;
require_once(APPROOT.'/application/utils.inc.php');
@@ -1722,6 +1723,7 @@ 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
@@ -1893,7 +1895,7 @@ class MenuBlock extends DisplayBlock
if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Modify'] = array(
'label' => Dict::S('UI:Menu:Modify'),
'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#",
'url' => $oRouter->GenerateUrl('object.modify', ['class' => $sClass, 'id' => $id]) . "{$sContext}#",
) + $aActionParams;
}
if ($bIsDeleteAllowed) {

View File

@@ -605,6 +605,12 @@ class LogChannels
public const NOTIFICATIONS = 'notifications';
public const PORTAL = 'portal';
/**
* @var string
* @since 3.1.0
*/
public const ROUTER = 'router';
}

View File

@@ -438,6 +438,8 @@ return array(
'Combodo\\iTop\\Renderer\\FieldRenderer' => $baseDir . '/sources/Renderer/FieldRenderer.php',
'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Router\\Exception\\RouterException' => $baseDir . '/sources/Router/Exception/RouterException.php',
'Combodo\\iTop\\Router\\Router' => $baseDir . '/sources/Router/Router.php',
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => $baseDir . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => $baseDir . '/sources/Service/Base/iDataPostProcessor.php',

View File

@@ -803,6 +803,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Renderer\\FieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FieldRenderer.php',
'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Router/Exception/RouterException.php',
'Combodo\\iTop\\Router\\Router' => __DIR__ . '/../..' . '/sources/Router/Router.php',
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => __DIR__ . '/../..' . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Base/iDataPostProcessor.php',

View File

@@ -11,6 +11,7 @@ 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\Router\Router;
use DBObject;
use Dict;
use MetaModel;
@@ -99,6 +100,7 @@ class ObjectSummary extends ObjectDetails
*/
private function ComputeActions()
{
$oRouter = Router::GetInstance();
$oDetailsButton = null;
if(UserRights::IsActionAllowed($this->sClassName, UR_ACTION_MODIFY)) {
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
@@ -111,7 +113,7 @@ class ObjectSummary extends ObjectDetails
'_blank'
);
$oModifyButton = ButtonUIBlockFactory::MakeLinkNeutral(
$sRootUrl.'pages/UI.php?route=object.modify&class='.$this->sClassName.'&id='.$this->sObjectId,
$oRouter->GenerateUrl('object.modify', ['class' => $this->sClassName, 'id' => $this->sObjectId]),
Dict::S('UI:Menu:Modify'),
'fas fa-external-link-alt',
'_blank',

View File

@@ -18,6 +18,7 @@ use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper;
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Router\Router;
use Combodo\iTop\Service\Base\ObjectRepository;
use CoreCannotSaveObjectException;
use DeleteException;
@@ -62,6 +63,7 @@ class ObjectController extends AbstractController
$sStateCode = utils::ReadParam('state', '');
$bCheckSubClass = utils::ReadParam('checkSubclass', true);
$oAppContext = new ApplicationContext();
$oRouter = Router::GetInstance();
if ($this->IsHandlingXmlHttpRequest()) {
$oPage = new AjaxPage('');
@@ -181,7 +183,7 @@ JS;
} else {
if ($this->IsHandlingXmlHttpRequest()) {
$oClassForm = cmdbAbstractObject::DisplayFormBlockSelectClassToCreate($sClass, MetaModel::GetName($sClass), $oAppContext, $aPossibleClasses, ['state' => $sStateCode]);
$sCurrentUrl = utils::GetAbsoluteUrlAppRoot().'/pages/UI.php?route=object.new';
$sCurrentUrl = $oRouter->GenerateUrl('object.new');
$oClassForm->SetOnSubmitJsCode(
<<<JS
let me = this;

View File

@@ -11,6 +11,7 @@ use cmdbAbstractObject;
use Combodo\iTop\Application\Helper\LegacyFormHelper;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Router\Router;
use Combodo\iTop\Service\Base\ObjectRepository;
use Exception;
use JsonPage;
@@ -131,7 +132,8 @@ class LinkSetController extends AbstractController
if (!$this->IsHandlingXmlHttpRequest()) {
throw new CoreException('LinksetController can only be called in ajax.');
}
$oRouter = Router::GetInstance();
$oPage = new AjaxPage('');
$sProposedRealClass = utils::ReadParam('class', '', false, 'class');
@@ -231,7 +233,7 @@ JS
'att_code' => $sAttCode,
'host_class' => $sClass,
'host_id' => $sId]);
$sCurrentUrl = utils::GetAbsoluteUrlAppRoot().'/pages/UI.php?route=linkset.create_linked_object';
$sCurrentUrl = $oRouter->GenerateUrl('linkset.create_linked_object');
$oClassForm->SetOnSubmitJsCode(
<<<JS
let me = this;

View File

@@ -0,0 +1,22 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Router\Exception;
/**
* Class RouteNotFoundException
*
* Means that a said route (eg. "object.modify") could not be found
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Router\Exception
* @since 3.1.0
* @internal
*/
class RouteNotFoundException extends RouterException
{
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Router\Exception;
use Exception;
/**
* Class RouterException
*
* Base router exception class in case we need to catch all kind of router exceptions (see derived exceptions)
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Router\Exception
* @since 3.1.0
* @internal
*/
class RouterException extends Exception
{
}

View File

@@ -6,6 +6,7 @@
namespace Combodo\iTop\Router;
use Combodo\iTop\Router\Exception\RouteNotFoundException;
use ReflectionClass;
use ReflectionMethod;
use utils;
@@ -89,6 +90,37 @@ class Router
// Don't do anything, we don't want to be initialized
}
/**
* @param string $sRoute Code of the route to generate the URL for (eg. "object.modify" => "https://itop/pages/UI.php?route=object.modify")
* @param array $aParams Parameters to add to the URL query string, they will be URL-encoded automatically.
* Note that only scalars and arrays are supported.
* (eg. ["foo" => "bar", "some_array" => [1, 2, 3]] will be append to the URL as "&foo=bar&some_array[]=1&some_array[]=2&some_array[]=3")
* @param bool $bAbsoluteUrl Whether the URL should be absolute (include the app root URL) or not
*
* @return string Absolute or relative URL to access $sRoute
* @throws \Exception
*/
public function GenerateUrl(string $sRoute, array $aParams = [], bool $bAbsoluteUrl = true): string
{
// Stop if route cannot be found, it will ease DX and troubleshooting
if (false === $this->CanDispatchRoute($sRoute)) {
throw new RouteNotFoundException('Could not find route "'.$sRoute.'"');
}
// Prepare base URL
$sUrl = $bAbsoluteUrl ? utils::GetAbsoluteUrlAppRoot() : '';
// Add route URL
$sUrl .= 'pages/UI.php?route=' . $sRoute;
// Add parameters and url encode them
if (count($aParams) > 0) {
$sUrl .= '&' . http_build_query($aParams);
}
return $sUrl;
}
/**
* @param string $sRoute
*

View File

@@ -6,6 +6,7 @@
use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Router\Router;
/**
* Class SummaryCardService
@@ -25,7 +26,8 @@ class SummaryCardService {
*/
public static function GetHyperlinkMarkup(string $sObjClass, $sObjKey): string
{
$sRoute = utils::GetAbsoluteUrlAppRoot()."/pages/ajax.render.php?route=object.summary&obj_key=$sObjKey&obj_class=$sObjClass";
$oRouter = Router::GetInstance();
$sRoute = $oRouter->GenerateUrl("object.summary", ["obj_class" => $sObjClass, "obj_key" => $sObjKey]);
return
<<<HTML
data-tooltip-content="$sRoute"

View File

@@ -4,8 +4,12 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\useCombodo\iTop\Router;
use Combodo\iTop\Router\Exception\RouteNotFoundException;
use Combodo\iTop\Router\Router;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use utils;
/**
* Class RouterTest
@@ -16,6 +20,101 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
*/
class RouterTest extends ItopTestCase
{
/**
* @covers \Combodo\iTop\Router\Router::GenerateUrl
* @dataProvider GenerateUrlProvider
*
* @param string $sExpectedUrl URL contains a <APP_ROOT_URL> placeholder that will be replaced with the real app root url at run time
* @param bool $bValid
* @param string $sRoute
* @param array $aParams
* @param bool $bAbsoluteUrl
*
* @return void
* @throws \Exception
*/
public function testGenerateUrl(string $sExpectedUrl, bool $bValid, string $sRoute, array $aParams, bool $bAbsoluteUrl = true): void
{
$oRouter = Router::GetInstance();
if (false === $bValid) {
$this->expectException(RouteNotFoundException::class);
}
$sTestedUrl = $oRouter->GenerateUrl($sRoute, $aParams, $bAbsoluteUrl);
$sExpectedUrl = str_ireplace('<APP_ROOT_URL>', utils::GetAbsoluteUrlAppRoot(), $sExpectedUrl);
$this->assertEquals($sTestedUrl, $sExpectedUrl, 'Generated URL does not match');
}
public function GenerateUrlProvider(): array
{
return [
'invalid route' => [
'',
false,
'foo.bar',
[],
true,
],
'relative route with no params' => [
'pages/UI.php?route=object.modify',
true,
'object.modify',
[],
false,
],
'absolute route with no params' => [
'<APP_ROOT_URL>pages/UI.php?route=object.modify',
true,
'object.modify',
[],
true,
],
'absolute route with scalar params' => [
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123',
true,
'object.modify',
[
'class' => 'Person',
'id' => 123
],
true,
],
'absolute route with 1 dimension array params' => [
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123&default%5Bname%5D=Castor&default%5Bfirst_name%5D=P%C3%A8re',
true,
'object.modify',
[
'class' => 'Person',
'id' => 123,
'default' => [
'name' => 'Castor',
'first_name' => 'Père',
],
],
true,
],
'absolute route with 2 dimensions array params' => [
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123&default%5Bname%5D=Castor&default%5Bfirst_name%5D=P%C3%A8re&foo%5Bfirst%5D%5B0%5D=10&foo%5Bfirst%5D%5B1%5D=20&foo%5Bsecond%5D%5B0%5D=30&foo%5Bsecond%5D%5B1%5D=40',
true,
'object.modify',
[
'class' => 'Person',
'id' => 123,
'default' => [
'name' => 'Castor',
'first_name' => 'Père',
],
'foo' => [
'first' => ['10', '20'],
'second' => ['30', '40'],
],
],
true,
],
];
}
/**
* @dataProvider CanDispatchRouteProvider
* @covers \Combodo\iTop\Router\Router::CanDispatchRoute