diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index fbfa54a71..4157fb8b3 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -25,10 +25,13 @@ use DeprecatedCallsLog; use Dict; use ExecutionKPI; use IssueLog; +use LogChannels; use MetaModel; use Symfony\Component\HttpFoundation\Response; use UserRights; use utils; +use const APPROOT; +use const MODULESROOT; /** @@ -740,17 +743,20 @@ class WebPage implements Page * @return void * @uses WebPage::$a_linked_scripts * @since 3.2.0 N°6935 $sLinkedScriptAbsUrl MUST be an absolute URL + * @deprecated 3.2.0 N°7315 Use {@see static::LinkScriptFromXXX()} instead */ public function add_linked_script($sLinkedScriptAbsUrl) { - // Ensure there is actually an URI + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); + + // Ensure there is actually a URI if (utils::IsNullOrEmptyString(trim($sLinkedScriptAbsUrl))) { return; } // Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior if (false === stripos($sLinkedScriptAbsUrl, "://")) { - IssueLog::Warning("Linked script added to page with a non absolute URL, which may lead to it not being loaded and causing javascript errors.", null, [ + IssueLog::Warning("Linked script added to page via deprecated API with a non absolute URL, it may lead to it not being loaded and causing javascript errors.", null, [ "linked_script_url" => $sLinkedScriptAbsUrl, "request_uri" => $_SERVER['REQUEST_URI'], ]); @@ -759,6 +765,101 @@ class WebPage implements Page $this->a_linked_scripts[$sLinkedScriptAbsUrl] = $sLinkedScriptAbsUrl; } + /** + * Use to link JS files from the iTop package (e.g. `/js/*`) + * + * @param string $sFileRelPath Rel. path from iTop app. root of the JS file to link (e.g. `js/utils.js`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromAppRoot(string $sFileRelPath): void + { + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sFileRelPath))) { + return; + } + + if (false === utils::RealPath(APPROOT . $sFileRelPath, APPROOT)) { + IssueLog::Warning("Linked script added to page with a path from outside app directory, it will be ignored.", LogChannels::CONSOLE, [ + "linked_script_url" => $sFileRelPath, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); + + // Ensure app root url ends with a slash as it is not guaranteed by the API + if (strcmp(substr($sFileRelPath, -1), '/') !== 0) { + $sAppRootUrl .= '/'; + } + + $sFileAbsURI = $sAppRootUrl . $sFileRelPath; + $this->a_linked_scripts[$sFileAbsURI] = $sFileAbsURI; + } + + /** + * Use to link JS files from any module + * + * @param string $sFileRelPath Rel. path from current environment (e.g. `/env-production/`) of the JS file to link (e.g. `some-module/asset/js/some-file.js`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromModule(string $sFileRelPath): void + { + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sFileRelPath))) { + return; + } + + if (false === utils::RealPath(MODULESROOT . $sFileRelPath, MODULESROOT)) { + IssueLog::Warning("Linked script added to page with a path from outside current env. directory, it will be ignored.", LogChannels::CONSOLE, [ + "linked_script_url" => $sFileRelPath, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + $sFileAbsURI = utils::GetAbsoluteUrlModulesRoot() . $sFileRelPath; + $this->a_linked_scripts[$sFileAbsURI] = $sFileAbsURI; + } + + /** + * Use to link JS files from any URI, typically an external server + * + * @param string $sFileAbsURI Abs. URI of the JS file to link (e.g. `https://external.server.com/some-file.js`) + * Note: Any non-absolute URI will be ignored. + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromURI(string $sFileAbsURI): void + { + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sFileAbsURI))) { + return; + } + + // Check if URI is absolute ("://" do allow any protocol), otherwise ignore the file + if (false === stripos($sFileAbsURI, "://")) { + IssueLog::Warning("Linked script added to page with a non absolute URI, it will be ignored.", LogChannels::CONSOLE, [ + "linked_script_url" => $sFileAbsURI, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + $this->a_linked_scripts[$sFileAbsURI] = $sFileAbsURI; + } + /** * Initialize compatibility linked scripts for the page * diff --git a/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php b/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php new file mode 100644 index 000000000..659d41a02 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php @@ -0,0 +1,20 @@ +RequireOnceUnitTestFile("./WebPageMock.php"); + } + + /** + * @dataProvider LinkScriptMethodsProvider + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScript() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromAppRoot() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromModule() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromURI() + * + * @param string $sMethodName + * @param string $sInputURI + * @param int $iExpectedCount + * + * @return void + * @throws \ReflectionException + */ + public function testLinkScriptMethods(string $sMethodName, string $sInputURI, int $iExpectedCount): void + { + $oPage = new WebPageMock(''); + + $this->InvokeNonPublicMethod(WebPage::class, "EmptyLinkedScripts", $oPage); + $this->InvokeNonPublicMethod(WebPage::class, $sMethodName, $oPage, [$sInputURI]); + + $aLinkedScript = $this->GetNonPublicProperty($oPage, "a_linked_scripts"); + $this->assertEquals($iExpectedCount, count($aLinkedScript), "Linked scripts count should be $iExpectedCount"); + } + + public function LinkScriptMethodsProvider(): array + { + return [ + // LinkScriptFromAppRoot + "LinkScriptFromAppRoot: Empty URI should be ignored" => [ + "LinkScriptFromAppRoot", + "", + 0, + ], + "LinkScriptFromAppRoot: Relative URI of existing file should be completed / added" => [ + "LinkScriptFromAppRoot", + "js/utils.js", + 1, + ], + "LinkScriptFromAppRoot: Relative URI of NON existing file should be ignored" => [ + "LinkScriptFromAppRoot", + "js/some-file.js", + 0, + ], + "LinkScriptFromAppRoot: Absolute URI should be ignored" => [ + "LinkScriptFromAppRoot", + "https://external.server/file.js", + 0, + ], + + // LinkScriptFromModule + "LinkScriptFromModule: Empty URI should be ignored" => [ + "LinkScriptFromModule", + "", + 0, + ], + "LinkScriptFromModule: Relative URI of existing file should be completed / added" => [ + "LinkScriptFromModule", + "itop-portal-base/portal/public/js/toolbox.js", + 1, + ], + "LinkScriptFromModule: Relative URI of NON existing file should be completed / added" => [ + "LinkScriptFromModule", + "some-module/asset/js/some-file.js", + 0, + ], + "LinkScriptFromModule: Absolute URI should be ignored" => [ + "LinkScriptFromModule", + "https://external.server/file.js", + 0, + ], + + // LinkScriptFromURI + "LinkScriptFromURI: Empty URI should be ignored" => [ + "LinkScriptFromURI", + "", + 0, + ], + "LinkScriptFromURI: Relative URI should be ignored" => [ + "LinkScriptFromURI", + "js/utils.js", + 0, + ], + "LinkScriptFromURI: Absolute URI should be added" => [ + "LinkScriptFromURI", + "https://external.server/file.js", + 1, + ], + ]; + } +} \ No newline at end of file