2022-09-20 21:59:52 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Forms;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Http\Requests\AnswerFormRequest;
|
|
|
|
|
use App\Http\Resources\FormResource;
|
2024-04-15 15:12:36 +02:00
|
|
|
use App\Http\Resources\FormSubmissionResource;
|
2022-09-20 21:59:52 +02:00
|
|
|
use App\Jobs\Form\StoreFormSubmissionJob;
|
|
|
|
|
use App\Models\Forms\Form;
|
2023-01-10 14:52:14 +01:00
|
|
|
use App\Models\Forms\FormSubmission;
|
2025-02-01 22:52:06 +01:00
|
|
|
use App\Service\Forms\FormSubmissionProcessor;
|
2022-09-20 21:59:52 +02:00
|
|
|
use App\Service\Forms\FormCleaner;
|
|
|
|
|
use App\Service\WorkspaceHelper;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
2024-02-23 11:54:12 +01:00
|
|
|
use Illuminate\Support\Facades\Storage;
|
2023-01-10 14:52:14 +01:00
|
|
|
use Vinkla\Hashids\Facades\Hashids;
|
2025-04-28 17:33:55 +02:00
|
|
|
use Illuminate\Support\Str;
|
2022-09-20 21:59:52 +02:00
|
|
|
|
|
|
|
|
class PublicFormController extends Controller
|
|
|
|
|
{
|
2024-02-23 11:54:12 +01:00
|
|
|
public const FILE_UPLOAD_PATH = 'forms/?/submissions';
|
2022-09-20 21:59:52 +02:00
|
|
|
|
2024-02-23 11:54:12 +01:00
|
|
|
public const TMP_FILE_UPLOAD_PATH = 'tmp/';
|
2022-09-20 21:59:52 +02:00
|
|
|
|
|
|
|
|
public function show(Request $request, string $slug)
|
|
|
|
|
{
|
2023-02-19 13:11:50 +01:00
|
|
|
$form = Form::whereSlug($slug)->whereIn('visibility', ['public', 'closed'])->firstOrFail();
|
2022-09-20 21:59:52 +02:00
|
|
|
if ($form->workspace == null) {
|
|
|
|
|
// Workspace deleted
|
|
|
|
|
return $this->error([
|
2024-02-23 11:54:12 +01:00
|
|
|
'message' => 'Form not found.',
|
2022-09-20 21:59:52 +02:00
|
|
|
], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$formCleaner = new FormCleaner();
|
|
|
|
|
|
|
|
|
|
// Disable pro features if needed
|
2024-02-23 11:54:12 +01:00
|
|
|
$form->fill(
|
|
|
|
|
$formCleaner
|
2024-04-15 15:12:36 +02:00
|
|
|
->processForm($request, $form)
|
|
|
|
|
->performCleaning($form->workspace)
|
|
|
|
|
->getData()
|
2022-09-20 21:59:52 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Increase form view counter if not login
|
2024-04-15 15:12:36 +02:00
|
|
|
if (!Auth::check()) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$form->views()->create();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 09:58:29 +02:00
|
|
|
return (new FormResource($form))
|
|
|
|
|
->setCleanings($formCleaner->getPerformedCleanings());
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function listUsers(Request $request)
|
|
|
|
|
{
|
|
|
|
|
// Check that form has user field
|
|
|
|
|
$form = $request->form;
|
2024-04-15 15:12:36 +02:00
|
|
|
if (!$form->has_user_field) {
|
2022-09-20 21:59:52 +02:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use serializer
|
|
|
|
|
$workspace = $form->workspace;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
return (new WorkspaceHelper($workspace))->getAllUsers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function showAsset($assetFileName)
|
|
|
|
|
{
|
2024-04-15 15:12:36 +02:00
|
|
|
$path = FormController::ASSETS_UPLOAD_PATH . '/' . $assetFileName;
|
|
|
|
|
if (!Storage::exists($path)) {
|
2022-09-20 21:59:52 +02:00
|
|
|
return $this->error([
|
|
|
|
|
'message' => 'File not found.',
|
2024-02-23 11:54:12 +01:00
|
|
|
'file_name' => $assetFileName,
|
2022-09-20 21:59:52 +02:00
|
|
|
]);
|
|
|
|
|
}
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2024-08-05 12:06:20 +02:00
|
|
|
$internal_url = Storage::temporaryUrl($path, now()->addMinutes(5));
|
|
|
|
|
|
2024-09-18 19:20:52 +02:00
|
|
|
foreach (config('filesystems.disks.s3.temporary_url_rewrites') as $from => $to) {
|
2024-08-05 12:06:20 +02:00
|
|
|
$internal_url = str_replace($from, $to, $internal_url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->to($internal_url);
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
/**
|
|
|
|
|
* Handle partial form submissions
|
|
|
|
|
*
|
|
|
|
|
* @param Request $request
|
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
|
*/
|
|
|
|
|
private function handlePartialSubmissions(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$form = $request->form;
|
|
|
|
|
|
|
|
|
|
// Process submission data to extract submission ID
|
|
|
|
|
$submissionData = $this->processSubmissionIdentifiers($request, $request->all());
|
|
|
|
|
|
|
|
|
|
// Validate that at least one field has a value
|
|
|
|
|
$hasValue = false;
|
|
|
|
|
foreach ($submissionData as $key => $value) {
|
|
|
|
|
if (Str::isUuid($key) && !empty($value)) {
|
|
|
|
|
$hasValue = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!$hasValue) {
|
|
|
|
|
return $this->error([
|
|
|
|
|
'message' => 'At least one field must have a value for partial submissions.'
|
|
|
|
|
], 422);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Explicitly mark this as a partial submission
|
|
|
|
|
$submissionData['is_partial'] = true;
|
|
|
|
|
|
|
|
|
|
// Use the same job as regular submissions to ensure consistent processing
|
|
|
|
|
$job = new StoreFormSubmissionJob($form, $submissionData);
|
|
|
|
|
$job->handle();
|
|
|
|
|
|
|
|
|
|
// Get the submission ID
|
|
|
|
|
$submissionId = $job->getSubmissionId();
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
'message' => 'Partial submission saved',
|
|
|
|
|
'submission_hash' => Hashids::encode($submissionId)
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-01 22:52:06 +01:00
|
|
|
public function answer(AnswerFormRequest $request, FormSubmissionProcessor $formSubmissionProcessor)
|
2022-09-20 21:59:52 +02:00
|
|
|
{
|
|
|
|
|
$form = $request->form;
|
2024-10-21 17:41:20 +02:00
|
|
|
$isFirstSubmission = ($form->submissions_count === 0);
|
2023-01-10 14:52:14 +01:00
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
// Handle partial submissions
|
|
|
|
|
$isPartial = $request->get('is_partial') ?? false;
|
|
|
|
|
if ($isPartial && $form->enable_partial_submissions && $form->is_pro) {
|
|
|
|
|
return $this->handlePartialSubmissions($request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get validated data (includes all metadata)
|
2024-09-18 19:20:52 +02:00
|
|
|
$submissionData = $request->validated();
|
|
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
// Process submission hash and ID
|
|
|
|
|
$submissionData = $this->processSubmissionIdentifiers($request, $submissionData);
|
|
|
|
|
|
|
|
|
|
// Create the job with all data (including metadata)
|
|
|
|
|
$job = new StoreFormSubmissionJob($form, $submissionData);
|
2025-02-01 22:52:06 +01:00
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
// Process the submission
|
2025-02-01 22:52:06 +01:00
|
|
|
if ($formSubmissionProcessor->shouldProcessSynchronously($form)) {
|
2023-01-10 14:52:14 +01:00
|
|
|
$job->handle();
|
2025-04-28 17:33:55 +02:00
|
|
|
$encodedSubmissionId = Hashids::encode($job->getSubmissionId());
|
2025-02-01 22:52:06 +01:00
|
|
|
// Update submission data with generated values for redirect URL
|
|
|
|
|
$submissionData = $job->getProcessedData();
|
2024-02-23 11:54:12 +01:00
|
|
|
} else {
|
2025-04-28 17:33:55 +02:00
|
|
|
$job->handle();
|
|
|
|
|
$encodedSubmissionId = Hashids::encode($job->getSubmissionId());
|
2023-01-10 14:52:14 +01:00
|
|
|
}
|
2022-09-20 21:59:52 +02:00
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
// Return the response
|
2022-09-20 21:59:52 +02:00
|
|
|
return $this->success(array_merge([
|
|
|
|
|
'message' => 'Form submission saved.',
|
2025-04-28 17:33:55 +02:00
|
|
|
'submission_id' => $encodedSubmissionId,
|
2024-10-22 10:34:29 +02:00
|
|
|
'is_first_submission' => $isFirstSubmission,
|
2025-02-01 22:52:06 +01:00
|
|
|
], $formSubmissionProcessor->getRedirectData($form, $submissionData)));
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
2023-01-10 14:52:14 +01:00
|
|
|
|
2025-04-28 17:33:55 +02:00
|
|
|
/**
|
|
|
|
|
* Processes submission identifiers to ensure consistent numeric format
|
|
|
|
|
*
|
|
|
|
|
* Takes a submission hash or string ID and converts it to a numeric submission_id.
|
|
|
|
|
* This allows submissions to be identified by either a hashed value or direct ID
|
|
|
|
|
* while ensuring consistent internal storage format.
|
|
|
|
|
*
|
|
|
|
|
* @param Request $request
|
|
|
|
|
* @param array $submissionData
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
private function processSubmissionIdentifiers(Request $request, array $submissionData): array
|
|
|
|
|
{
|
|
|
|
|
// Handle submission hash if present (convert to numeric submission_id)
|
|
|
|
|
$submissionHash = $request->get('submission_hash');
|
|
|
|
|
if ($submissionHash) {
|
|
|
|
|
$decodedHash = Hashids::decode($submissionHash);
|
|
|
|
|
if (!empty($decodedHash)) {
|
|
|
|
|
$submissionData['submission_id'] = (int)($decodedHash[0] ?? null);
|
|
|
|
|
}
|
|
|
|
|
unset($submissionData['submission_hash']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle string submission_id if present (convert to numeric)
|
|
|
|
|
if (isset($submissionData['submission_id']) && is_string($submissionData['submission_id']) && !is_numeric($submissionData['submission_id'])) {
|
|
|
|
|
$decodedId = Hashids::decode($submissionData['submission_id']);
|
|
|
|
|
if (!empty($decodedId)) {
|
|
|
|
|
$submissionData['submission_id'] = (int)($decodedId[0] ?? null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $submissionData;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-10 14:52:14 +01:00
|
|
|
public function fetchSubmission(Request $request, string $slug, string $submissionId)
|
|
|
|
|
{
|
2025-04-28 17:33:55 +02:00
|
|
|
// Decode the submission ID using the same approach as in processSubmissionIdentifiers
|
|
|
|
|
$decodedId = Hashids::decode($submissionId);
|
|
|
|
|
$submissionId = !empty($decodedId) ? (int)($decodedId[0]) : false;
|
|
|
|
|
|
2023-01-10 14:52:14 +01:00
|
|
|
$form = Form::whereSlug($slug)->whereVisibility('public')->firstOrFail();
|
2024-04-15 15:12:36 +02:00
|
|
|
if ($form->workspace == null || !$form->editable_submissions || !$submissionId) {
|
2023-01-10 14:52:14 +01:00
|
|
|
return $this->error([
|
|
|
|
|
'message' => 'Not allowed.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-13 09:48:28 +01:00
|
|
|
$submission = FormSubmission::find($submissionId);
|
|
|
|
|
if (!$submission) {
|
|
|
|
|
return $this->error([
|
|
|
|
|
'message' => 'Submission not found.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$submission = new FormSubmissionResource($submission);
|
2024-04-15 15:12:36 +02:00
|
|
|
$submission->publiclyAccessed();
|
2023-01-13 14:56:37 +01:00
|
|
|
|
2023-01-10 14:52:14 +01:00
|
|
|
if ($submission->form_id != $form->id) {
|
|
|
|
|
return $this->error([
|
|
|
|
|
'message' => 'Not allowed.',
|
|
|
|
|
], 403);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 15:12:36 +02:00
|
|
|
return $this->success($submission->toArray($request));
|
2023-01-10 14:52:14 +01:00
|
|
|
}
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|