Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
BernhardBaumrock committed Jun 9, 2024
2 parents a840fae + 47302a0 commit 62a330e
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 41 deletions.
93 changes: 58 additions & 35 deletions RockFrontend.module.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class RockFrontend extends WireData implements Module, ConfigurableModule

private $addMarkup;

private $ajaxFolders = [];

/** @var WireData */
public $alfredCache;

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -261,7 +271,6 @@ public function init()


// others
$this->ajaxAddEndpoints();
$this->checkHealth();

// development helpers by rockmigrations
Expand All @@ -277,6 +286,7 @@ public function init()

public function ready()
{
$this->ajaxAddEndpoints();
$this->addAssets();
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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!
Expand All @@ -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;
}
}
}

Expand Down Expand Up @@ -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 .= "<div><a href=$url>$url</a></div>";
}
$f = new InputfieldMarkup();
Expand Down
16 changes: 10 additions & 6 deletions docs/ajax/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

RockFrontend ❤️ HTMX, and the latest version significantly simplifies the process of adding endpoints to your HTMX powered websites.

<div class='uk-alert uk-alert-warning'>Always make sure to properly sanitize user input and also make sure to protect your endpoints from unauthorized access!</div>

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.
Expand All @@ -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**
<div class='uk-alert'>Visit /ajax/your-endpoint-name in the browser</div>

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:

<img src=ajax.png class=blur>

## 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');
```

0 comments on commit 62a330e

Please sign in to comment.