Add support for EntryPoint /ep extension

This commit is contained in:
Matt Lorimer 2025-07-21 17:17:16 +01:00 committed by Jack Anderson
parent 1d0dba0d82
commit 783d60bac8
10 changed files with 369 additions and 2 deletions

View file

@ -20,6 +20,7 @@ services:
$copyLegacyAssetPaths: '%legacy.copy_asset_paths%' $copyLegacyAssetPaths: '%legacy.copy_asset_paths%'
$legacyApiPaths: '%legacy.api_paths%' $legacyApiPaths: '%legacy.api_paths%'
$legacyApiPathFiles: '%legacy.api_path_files%' $legacyApiPathFiles: '%legacy.api_path_files%'
$legacyEntrypointPaths: '%legacy.entrypoint_paths%'
$legacyEntrypointFiles: '%legacy.entrypoint_files%' $legacyEntrypointFiles: '%legacy.entrypoint_files%'
$exposedUserPreferences: '%legacy.exposed_user_preferences%' $exposedUserPreferences: '%legacy.exposed_user_preferences%'
$userPreferencesKeyMap: '%legacy.user_preferences_key_map%' $userPreferencesKeyMap: '%legacy.user_preferences_key_map%'

View file

@ -99,6 +99,7 @@ return static function (ContainerConfigurator $containerConfig) {
['path' => '^/api', 'roles' => 'PUBLIC_ACCESS'], ['path' => '^/api', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/api/graphql', 'roles' => 'PUBLIC_ACCESS'], ['path' => '^/api/graphql', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/api/graphql/graphiql*', 'roles' => 'PUBLIC_ACCESS'], ['path' => '^/api/graphql/graphiql*', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/ep', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/', 'roles' => 'PUBLIC_ACCESS'] ['path' => '^/', 'roles' => 'PUBLIC_ACCESS']
]; ];

View file

@ -16,3 +16,8 @@ engine_controllers:
namespace: App\Engine\Controller namespace: App\Engine\Controller
type: attribute type: attribute
entrypoint_controllers:
resource:
path: ../core/backend/EntryPoint/Controller/
namespace: App\EntryPoint\Controller
type: attribute

View file

@ -1,4 +1,7 @@
parameters: parameters:
legacy.entrypoint_paths:
ep: index.php
legacy.entrypoint_files: legacy.entrypoint_files:
'campaign_tracker.php': 'campaign_tracker.php':
file: campaign_tracker.php file: campaign_tracker.php

View file

@ -153,6 +153,16 @@ abstract class LegacyHandler
return true; return true;
} }
public function getProjectDir(): string
{
return $this->projectDir;
}
public function setProjectDir(string $projectDir): void
{
$this->projectDir = $projectDir;
}
/** /**
* Swap symfony session with legacy suite session * Swap symfony session with legacy suite session
* @param string $sessionName * @param string $sessionName

View file

@ -0,0 +1,58 @@
<?php
/**
* SuiteCRM is a customer relationship management program developed by SuiteCRM Ltd.
* Copyright (C) 2025 SuiteCRM Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUITECRM, SUITECRM DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
namespace App\EntryPoint\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class IndexController
* @package App\Controller
*/
class EntryPointController extends AbstractController
{
#[Route('/ep/{name}', name: 'generic_ep_route', methods: ["GET", "POST"], stateless: false)]
public function genericEntryPoint(): Response
{
$response = new Response();
$response->setStatusCode(Response::HTTP_NOT_FOUND);
return $response;
}
#[Route('/ep', name: 'empty_ep_route', methods: ["GET", "POST"], stateless: false)]
public function emptyEntryPoint(): Response
{
$response = new Response();
$response->setStatusCode(Response::HTTP_NOT_FOUND);
return $response;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* SuiteCRM is a customer relationship management program developed by SuiteCRM Ltd.
* Copyright (C) 2025 SuiteCRM Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUITECRM, SUITECRM DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
namespace App\EntryPoint\LegacyHandler;
use App\Engine\LegacyHandler\LegacyHandler;
use ControllerFactory;
use SugarController;
class EntryPointHandler extends LegacyHandler
{
public const HANDLER_KEY = 'entrypoint';
/**
* @inheritDoc
*/
public function getHandlerKey(): string
{
return self::HANDLER_KEY;
}
/**
* Get currency info array
* @param string $entryPoint
* @return array
*/
public function validateEntryPoint(string $entryPoint): array
{
$this->init();
/** @var SugarController $controller */
$controller = ControllerFactory::getController('home');
$valid = false;
$auth = true;
$authRequired = $controller->checkEntryPointRequiresAuth($entryPoint);
if ($authRequired && get_authenticated_user() === NULL) {
$auth = false;
}
if (!empty($controller->entry_point_registry[$entryPoint])) {
$valid = true;
}
$this->close();
return [
'valid' => $valid,
'auth' => $auth,
];
}
}

View file

@ -127,7 +127,10 @@ class Kernel extends BaseKernel
public function getLegacyRoute(Request $request): array public function getLegacyRoute(Request $request): array
{ {
if ($this->container->has('legacy.route.handler')) { if ($this->container->has('legacy.route.handler')) {
return $this->container->get('legacy.route.handler')->getLegacyRoute($request); $legacyRouteHandler = $this->container->get('legacy.route.handler');
$legacyRouteHandler->setCurrentDir(getcwd());
return $legacyRouteHandler->getLegacyRoute($request);
} }
return []; return [];

View file

@ -0,0 +1,159 @@
<?php
/**
* SuiteCRM is a customer relationship management program developed by SuiteCRM Ltd.
* Copyright (C) 2025 SuiteCRM Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUITECRM, SUITECRM DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
namespace App\Routes\Service;
use App\EntryPoint\LegacyHandler\EntryPointHandler;
use Symfony\Component\HttpFoundation\Request;
class LegacyEntryPointRedirectHandler extends LegacyRedirectHandler
{
private array $legacyEntrypointPaths;
private EntryPointHandler $entryPointHandler;
private string $currentDir;
/**
* LegacyEntryPointRedirectHandler constructor.
* @param array $legacyEntrypointPaths
* @param String $legacyPath
*/
public function __construct(EntryPointHandler $entryPointHandler, array $legacyEntrypointPaths, string $legacyPath)
{
parent::__construct($legacyPath);
$this->entryPointHandler = $entryPointHandler;
$this->legacyEntrypointPaths = $legacyEntrypointPaths;
}
/**
* Check if the given $request is a legacy entryPoint request
*
* @param Request $request
* @return bool
*/
public function isEntryPointRequest(Request $request): bool
{
return $this->inPathList($request, array_keys($this->legacyEntrypointPaths));
}
public function setCurrentDir(string $dir): void
{
$this->currentDir = $dir;
}
/**
* Check if the given $request is a valid legacy entryPoint
*
* @param Request $request
* @return array
*/
public function isValidEntryPoint(Request $request): array
{
$entryPoint = $this->getEntryPoint($request);
if (!empty($entryPoint)) {
$this->entryPointHandler->setProjectDir($this->currentDir);
return $this->entryPointHandler->validateEntryPoint($entryPoint['name']);
}
return ['valid' => false];
}
/**
* Convert given $request route
*
* @param Request $request
* @return string
*/
public function convert(Request $request): string
{
$legacyPath = parent::convert($request);
foreach ($this->legacyEntrypointPaths as $path => $replace) {
if ($this->inPath($request, $path)) {
return str_replace($path, $replace, $legacyPath);
}
}
return $legacyPath;
}
/**
* Convert given $request route
*
* @param Request $request
* @return array
*/
public function getIncludeFile(Request $request): array
{
$entryPoint = $this->getEntryPoint($request);
if (!empty($entryPoint)) {
$base = $_SERVER['BASE'] ?? $_SERVER['REDIRECT_BASE'] ?? '';
$scriptName = $base . '/legacy/' . $entryPoint['file'];
$requestUri = $base . '/legacy/' . $entryPoint['file'] . $entryPoint['params'];
$_REQUEST['entryPoint'] = $entryPoint['name'];
$info['dir'] = '';
$info['file'] = $entryPoint['file'];
$info['script-name'] = $scriptName;
$info['request-uri'] = $requestUri;
$info['access'] = true;
return $info;
}
return [
'dir' => '',
'file' => './index.php',
'access' => false
];
}
protected function getEntryPoint(Request $request): array
{
foreach ($this->legacyEntrypointPaths as $path => $file) {
if ($this->inPath($request, $path)) {
$epCheck = explode('/' . $path . '/', $request->getRequestUri());
if (!empty($epCheck[1])) {
$entryPoint = explode('?', $epCheck[1]);
return [
'name' => $entryPoint[0],
'params' => !empty($entryPoint[1]) ? '?' . $entryPoint[1] : '',
'path' => $path,
'file' => $file,
];
}
}
}
return [];
}
}

View file

@ -46,20 +46,26 @@ class LegacyRouteHandler
*/ */
private $legacyAssetRedirectHandler; private $legacyAssetRedirectHandler;
private LegacyEntryPointRedirectHandler $legacyEntryPointRedirectHandler;
private string $currentDir;
/** /**
* LegacyRedirectListener constructor. * LegacyRedirectListener constructor.
* @param LegacyApiRedirectHandler $legacyApiRedirectHandler * @param LegacyApiRedirectHandler $legacyApiRedirectHandler
* @param LegacyNonViewActionRedirectHandler $legacyNonViewActionRedirectHandler * @param LegacyNonViewActionRedirectHandler $legacyNonViewActionRedirectHandler
* @param LegacyAssetRedirectHandler $legacyAssetRedirectHandler * @param LegacyAssetRedirectHandler $legacyAssetRedirectHandler
* @param LegacyEntryPointRedirectHandler $legacyEntryPointRedirectHandler
*/ */
public function __construct( public function __construct(
LegacyApiRedirectHandler $legacyApiRedirectHandler, LegacyApiRedirectHandler $legacyApiRedirectHandler,
LegacyNonViewActionRedirectHandler $legacyNonViewActionRedirectHandler, LegacyNonViewActionRedirectHandler $legacyNonViewActionRedirectHandler,
LegacyAssetRedirectHandler $legacyAssetRedirectHandler LegacyAssetRedirectHandler $legacyAssetRedirectHandler,
LegacyEntryPointRedirectHandler $legacyEntryPointRedirectHandler
) { ) {
$this->legacyApiRedirectHandler = $legacyApiRedirectHandler; $this->legacyApiRedirectHandler = $legacyApiRedirectHandler;
$this->legacyNonViewActionRedirectHandler = $legacyNonViewActionRedirectHandler; $this->legacyNonViewActionRedirectHandler = $legacyNonViewActionRedirectHandler;
$this->legacyAssetRedirectHandler = $legacyAssetRedirectHandler; $this->legacyAssetRedirectHandler = $legacyAssetRedirectHandler;
$this->legacyEntryPointRedirectHandler = $legacyEntryPointRedirectHandler;
} }
/** /**
@ -79,6 +85,21 @@ class LegacyRouteHandler
return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request); return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request);
} }
if ($this->isLegacyEntryPointPath($request)) {
$entryPoint = $this->isValidLegacyEntryPoint($request);
if ($entryPoint['valid']) {
if ($entryPoint['auth']) {
return $this->legacyEntryPointRedirectHandler->getIncludeFile($request);
}
return [
'access' => false
];
}
return [];
}
if ($this->isLegacyApi($request)) { if ($this->isLegacyApi($request)) {
return $this->legacyApiRedirectHandler->getIncludeFile($request); return $this->legacyApiRedirectHandler->getIncludeFile($request);
} }
@ -99,6 +120,11 @@ class LegacyRouteHandler
return []; return [];
} }
public function setCurrentDir($dir) : void
{
$this->currentDir = $dir;
}
/** /**
* Check if it is legacy entry point * Check if it is legacy entry point
* @param Request $request * @param Request $request
@ -159,4 +185,26 @@ class LegacyRouteHandler
return $this->legacyNonViewActionRedirectHandler->isMatch($request); return $this->legacyNonViewActionRedirectHandler->isMatch($request);
} }
/**
* Check if it is EntryPoint Path view request
* @param Request $request
* @return bool
*/
protected function isLegacyEntryPointPath(Request $request): bool
{
return $this->legacyEntryPointRedirectHandler->isEntryPointRequest($request);
}
/**
* Check if it is EntryPoint Path view request
* @param Request $request
* @return array
*/
protected function isValidLegacyEntryPoint(Request $request): array
{
$this->legacyEntryPointRedirectHandler->setCurrentDir($this->currentDir);
return $this->legacyEntryPointRedirectHandler->isValidEntryPoint($request);
}
} }