From 47302a0f2122dabaeda4defc1625a0b16c6992af Mon Sep 17 00:00:00 2001 From: BernhardBaumrock Date: Sun, 9 Jun 2024 14:11:48 +0200 Subject: [PATCH] feat: add support for 3rd party ajax endpoints --- RockFrontend.module.php | 93 +++++++++++++++++++++++++---------------- docs/ajax/readme.md | 16 ++++--- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/RockFrontend.module.php b/RockFrontend.module.php index f70eed5..92bd17a 100644 --- a/RockFrontend.module.php +++ b/RockFrontend.module.php @@ -76,6 +76,8 @@ class RockFrontend extends WireData implements Module, ConfigurableModule private $addMarkup; + private $ajaxFolders = []; + /** @var WireData */ public $alfredCache; @@ -226,6 +228,14 @@ public function init() $this->layoutFolders->add($this->config->paths->templates); $this->layoutFolders->add($this->config->paths->assets); + // add ajax folders + // you can add custom endpoints in 3rd party modules in the same way + // see RockCommerce for an example + $this->addAjaxFolder( + 'ajax', + $this->wire->config->paths->templates . 'ajax' + ); + // Alfred require_once __DIR__ . "/Functions.php"; $this->createPermission( @@ -261,7 +271,6 @@ public function init() // others - $this->ajaxAddEndpoints(); $this->checkHealth(); // development helpers by rockmigrations @@ -277,6 +286,7 @@ public function init() public function ready() { + $this->ajaxAddEndpoints(); $this->addAssets(); } @@ -344,6 +354,13 @@ private function addAlfredMarkup(string $html, $skipAssets = false): string return $html; } + public function addAjaxFolder(string $url, string $folder): void + { + $url = '/' . trim($url, '/') . '/'; + $folder = '/' . trim(Paths::normalizeSeparators($folder), '/') . '/'; + $this->ajaxFolders[$url] = $folder; + } + private function addRockFrontendJS(): void { if (!$this->isEnabled('RockFrontend.js')) return; @@ -468,33 +485,36 @@ private function adjustBrightness($hexCode, $adjustPercent) protected function ajaxAddEndpoints(): void { - foreach ($this->ajaxEndpoints() as $base => $endpoint) { - $this->wire->addHook("/ajax/$base", function (HookEvent $event) use ($endpoint, $base) { - $isHtmx = isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST']; - $ajax = $this->wire->config->ajax || $isHtmx; - - // ajax or no ajax? - if (!$ajax) { - // show debug screen for pw superusers - if ($this->wire->user->isSuperuser()) { - return $this->ajaxDebug($endpoint, $base); - } else { - // guest and no ajax: no access! - if ($this->wire->modules->isInstalled('TracyDebugger')) { - Debugger::$showBar = false; - } - http_response_code(403); - return "Access Denied"; - } + foreach ($this->ajaxEndpoints() as $url => $endpoint) { + $this->wire->addHook($url, function (HookEvent $event) use ($endpoint, $url) { + $isAJAX = false; + if ($this->wire->config->ajax) $isAJAX = true; + if (!$isAJAX && $_SERVER['REQUEST_METHOD'] !== 'GET') $isAJAX = true; + if ( + !$isAJAX && + isset($_SERVER['HTTP_HX_REQUEST']) && + $_SERVER['HTTP_HX_REQUEST'] + ) $isAJAX = true; + + // render public endpoint (ajax response) + if ($isAJAX) return $this->ajaxPublic($endpoint); + + // show debug screen for pw superusers + if ($this->wire->user->isSuperuser()) { + return $this->ajaxDebug($endpoint, $url); } else { - // render public endpoint (ajax response) - return $this->ajaxPublic($endpoint); + // guest and no ajax: no access! + if ($this->wire->modules->isInstalled('TracyDebugger')) { + Debugger::$showBar = false; + } + http_response_code(403); + return "Access Denied"; } }); } } - private function ajaxDebug($endpoint, $base): string + private function ajaxDebug($endpoint, $url): string { // dont catch errors when debugging $raw = $this->ajaxResponse($endpoint); @@ -503,7 +523,7 @@ private function ajaxDebug($endpoint, $base): string // render html $markup = $this->render(__DIR__ . "/stubs/ajax-debug.latte", [ 'endpoint' => $endpoint, - 'ajaxUrl' => $this->ajaxUrl($base), + 'ajaxUrl' => $url, 'response' => $response, 'formatted' => $this->ajaxFormatted($raw, $endpoint), 'contenttype' => $this->contenttype, // must be after formatted! @@ -515,20 +535,24 @@ private function ajaxDebug($endpoint, $base): string private function ajaxEndpoints(): array { // scan for these extensions - // earlier listed extensions have priority - $extensions = ['latte', 'php']; + // later listed extensions have priority + $extensions = [ + 'php', + 'latte', + ]; // attach hook for every found endpoint $arr = []; foreach ($extensions as $ext) { - $endpoints = $this->wire->files->find( - $this->wire->config->paths->templates . "ajax", - ['extensions' => [$ext]] - ); - foreach ($endpoints as $endpoint) { - $base = pathinfo($endpoint, PATHINFO_FILENAME); - if (array_key_exists($base, $arr)) continue; - $arr[$base] = $endpoint; + $opt = ['extensions' => [$ext]]; + foreach ($this->ajaxFolders as $baseurl => $folder) { + $endpoints = $this->wire->files->find($folder, $opt); + foreach ($endpoints as $endpoint) { + $base = pathinfo($endpoint, PATHINFO_FILENAME); + $url = $baseurl . $base; + if (array_key_exists($base, $arr)) continue; + $arr[$url] = $endpoint; + } } } @@ -2877,8 +2901,7 @@ public function getModuleConfigInputfields($inputfields) private function configAjax(InputfieldWrapper $inputfields): void { $html = ''; - foreach ($this->ajaxEndpoints() as $base => $file) { - $url = $this->ajaxUrl($base); + foreach ($this->ajaxEndpoints() as $url => $file) { $html .= "
$url
"; } $f = new InputfieldMarkup(); diff --git a/docs/ajax/readme.md b/docs/ajax/readme.md index ec44a9e..3103b69 100644 --- a/docs/ajax/readme.md +++ b/docs/ajax/readme.md @@ -2,6 +2,8 @@ RockFrontend ❤️ HTMX, and the latest version significantly simplifies the process of adding endpoints to your HTMX powered websites. +
Always make sure to properly sanitize user input and also make sure to protect your endpoints from unauthorized access!
+ To create a new endpoint, all you need to do is place a PHP file within the `/site/templates/ajax/` directory. For example, adding a file named `foo.php` in this directory automatically generates an endpoint accessible via `/ajax/foo`. This endpoint can then be seamlessly utilized with HTMX or the `fetch()` method in JavaScript. Instead of cluttering your template or module code with `if($config->ajax) {...}`, you can encapsulate AJAX-specific logic within dedicated files in the `/site/templates/ajax/` directory. This approach not only simplifies the code but also enhances maintainability by segregating AJAX logic from the rest of your application logic. @@ -19,14 +21,16 @@ As you can see - to further simplify the process - if your endpoint returns an a ## Debugging Endpoints for Superusers -When logged in as a superuser, RockFrontend provides a straightforward UI for debugging your endpoints. - -**Visit /ajax/your-endpoint-name in the browser** +
Visit /ajax/your-endpoint-name in the browser
-This feature is particularly useful for quickly identifying and resolving issues, ensuring your HTMX endpoints function as expected: +When logged in as a superuser, RockFrontend provides a straightforward UI for debugging your endpoints. This feature is particularly useful for quickly identifying and resolving issues, ensuring your AJAX endpoints function as expected: -## Caution +## Adding Endpoints to 3rd party modules + +You can use the AJAX feature from other modules. An example is RockCommerce that serves ajax endpoints via RockFrontend. All you have to do is to add the AJAX folder to rockfrontend in the module's `init()` method: -Always make sure to properly sanitize user input and also make sure to protect your endpoints from unauthorized access. +```php +rockfrontend()->addAjaxFolder('rc-ajax', __DIR__ . '/ajax'); +```