File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/FormFrontendController.php
<?php
namespace App\Http\Controllers\Refactor;
use App\Http\Controllers\Controller;
use App\Model\Form;
use App\Services\Forms\TermsAcceptanceService;
use App\Services\SubscriberService;
use Illuminate\Http\Request;
/**
* FormFrontendController — visitor-side pipeline (popup.js + iframe content +
* AJAX submit) for embedded subscribe forms.
*
* Replaces the legacy `App\Http\Controllers\FormController::frontend{Popup,
* Content,Save}` triplet which is left orphaned for emergency revert (a
* single-line route swap in `routes/web.php` reverts the whole change).
*
* URL surface is preserved exactly:
* GET /frontend/forms/{uid}/popup.js → popup($uid)
* GET /frontend/forms/{uid}/content → content($uid)
* POST /frontend/forms/{uid}/save → save($uid)
*
* so existing customer embed snippets keep working without a re-paste.
*
* Behaviour is identical to the legacy controller — this is a pure namespace
* + view-folder migration. UX upgrades (universal `label.error` + jQuery
* validate `showErrors()` instead of `alert()`) ride on the refactor views
* (`resources/views/refactor/forms/frontend/`), NOT the legacy ones.
*/
class FormFrontendController extends Controller
{
public function popup(Request $request, $uid)
{
$form = Form::findByUid($uid);
if (!$form) {
abort(404);
}
$this->language($form);
$content = view('refactor.forms.frontend.popup', ['form' => $form]);
return response($content)
->header('Access-Control-Allow-Origin', '*')
->header('Content-Type', 'application/javascript');
}
public function content(Request $request, $uid)
{
$form = Form::findByUid($uid);
if (!$form) {
abort(404);
}
$this->language($form);
// Preview-mode hook preserved from legacy: when ?preview=1 is set, pull
// the in-flight builder draft out of the session (stashed by
// Refactor\FormController::previewSave) instead of the saved JSON.
if ($request->preview) {
$html = $request->session()->get('form-preview-content-' . $form->uid);
$rendered = $form->renderedContent($html);
} else {
$rendered = $form->renderedContent();
}
$content = view('refactor.forms.frontend.content', [
'form' => $form,
'content' => $rendered,
]);
return response($content)
->header('Access-Control-Allow-Origin', '*');
}
public function save(Request $request, $uid)
{
$form = Form::findByUid($uid);
if (!$form) {
abort(404);
}
$this->language($form);
// Compliance gate — pre-flight, BEFORE optInFromPopupForm (which
// persists the subscriber inside MailList::subscribe). The service
// walks the saved JSON to enforce every TermsOfServiceElement with
// required=true and keeps tampered POSTs from creating subscribers.
// Lives in the service layer per the FormRequest → DTO → Service →
// Controller layering rule.
$termsErrors = app(TermsAcceptanceService::class)->check($form, $request);
if (!empty($termsErrors)) {
return response()->json($termsErrors, 400);
}
try {
[$validator, $subscriber] = app(SubscriberService::class)
->optInFromPopupForm($form, $request);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
if ($validator->fails()) {
// Returns Laravel MessageBag shape `{ FIELD: [msg, ...] }` — the
// visitor-side .fail() handler in content.blade.php hands this to
// jQuery validate's `validator.showErrors()` to surface inline
// labels (same UX as the client-side gate).
return response()->json($validator->errors(), 400);
}
// Plugin opt-in extension point (W3 messenger plugin: SMS / WhatsApp /
// Telegram double opt-in confirmation pages). First non-null return
// wins; null means no plugin took the redirect. See
// `docs/messenger/DESIGN.md §9.8` for the contract.
$response = ['message' => 'OK'];
$redirectUrl = collect(\App\Library\Facades\Hook::collect('form.post_optin_redirect', [$subscriber, $request]))
->first(fn ($value) => is_string($value) && $value !== '');
if (is_string($redirectUrl)) {
$response['redirect_url'] = $redirectUrl;
}
return response()->json($response);
}
private function language(Form $form): void
{
if ($form->customer && $form->customer->language) {
\App::setLocale($form->customer->language->code);
\Carbon\Carbon::setLocale($form->customer->language->code);
}
}
}