Table of Contents

  1. Ext: sg_forms
  2. FormRequiringAction

Ext: sg_forms

License: GNU GPL, Version 2

Repository: https://gitlab.sgalinski.de/typo3/sg_forms

Please report bugs here: https://gitlab.sgalinski.de/typo3/sg_forms

About

This extension provides additional functionality for the forms extension

The backend Form Submissions module provides:

  • a form overview with submission counts
  • grouped submission list view per form (paginated)
  • single submission detail view
  • in-context actions (back, export CSV, delete)
  • filter-aware empty states
  • CSV exports with contextual filenames (sgforms-*.csv)
  • per-form configurable visible columns in submission list
  • "new since last visit" indicators in form overview
  • language-aware frontend page links in submission detail views
  • translation-aware submission grouping by main form identifier
  • restoring a deleted main form definition also restores related deleted translation files

Legacy submissions without a stored language context continue to link to the default language version of the frontend page in the submission detail view.

Database Schema

The internal submission table is declared in TCA. ext_tables.sql remains the authoritative source for its existing primary key, parent index, and legacy signed integer columns until the project-wide schema migration is verified.

Setup

Setup custom forms storage

All you need to be able to store the form yaml files in our project theme is to create the following folder: * web/typo3conf/ext/project_theme/Resources/Private/Forms*

Don't forget to include the Typoscript in your template!

Invisible Recaptcha

You need to integrate lib.settings.reCaptchaSiteKey and lib.settings.reCaptchaSecretKey in your page template, to get the invisible recaptcha working.

Important

You also have to include the JavaScript of the FormRequiringAction JavaScript module introduced in V5. Check how to use it in its documentation.

ALTCHA Proof of work

ALTCHA PoW is automatically added when checking the "Use ALTCHA" checkbox inside the form settings. The widget fetches challenges from the uncached GET /api/public/v1/forms/altcha/challenge endpoint, so cached form markup does not reuse expired challenge payloads. Failed requests use RFC 7807 Problem JSON.

ALTCHA Spam Filter

ALTCHA is automatically added if you have API key. You need to add API Key and API Url in your page template, to get the ALTCHA Spam Filter working. API Key and url can be set in

PDF Finisher

The PdfFinisher can fill an existing PDF form with submitted form values and optionally attach it to admin/user mails. Filling PDF forms requires mikehaertl/php-pdftk and a working pdftk binary on the server.

For local DDEV environments, make sure pdftk is installed in the web container via .ddev/config.yaml:

webimage_extra_packages: [ build-essential, python3, pdftk]

In the form editor:

  1. Add the Fill PDF with form values finisher.
  2. Select a PDF template file (sys_file with AcroForm fields).
  3. Click Load PDF fields to import all PDF field names into the mapping table.
  4. Map each imported PDF field to a TYPO3 form field identifier (Text, Textarea, Email, Checkbox, LinkedCheckbox, RadioButton, SingleSelect, MultiSelect, CountrySelect) or to a virtual value (CURRENT_DATE, CURRENT_TIME, CURRENT_DATETIME, CURRENT_TIMESTAMP).
  5. Optionally enable Start generated PDF download on confirmation page (one-time, deletes file after first access) to automatically trigger the PDF download after submit.
  6. Flatten generated PDF (make it non-editable) is enabled by default. Disable it if you want to keep output fields editable/highlighted.

For checkbox/radio/select mappings, the PDF field name and PDF export value definitions must match the TYPO3 values you submit (for example the selected radio option value). The editor currently does not enforce type compatibility, so integrators/editors need to ensure the mapping is semantically correct.

Generated PDFs are stored in fileadmin/sg_forms/Temp/PdfAttachments. The frontend download link uses a temporary token (default TTL: 1 hour) and is consumed on first valid access. On this first access, the generated PDF is streamed and then deleted. Integrators should still schedule cleanup for this directory, because generated PDFs can remain there when no frontend download is rendered/clicked, for example when PDFs are only attached to emails. Schedule the dedicated form submission PDF cleanup command for this:

vendor/bin/typo3 sgforms:cleanup:form-submission-pdfs --retention-period=24

The retention period is specified in hours. Adjust it to the project's retention requirements. Form Framework upload folders in 1:/form_uploads/ are cleaned up by TYPO3 core's form:cleanup:uploads command.

The one-time download URL is GET /api/public/v1/forms/pdf-download?token={token}. Invalid or expired tokens return a 404 RFC 7807 Problem JSON response; valid downloads are streamed with no-store cache headers.

Extension Configuration

Toggle Translation Grouping

By default, the backend module groups translated form definitions under their parent form. If you want to disable this behavior and see all form definitions as separate entries (e.g., in multi-site setups with separate language handling), you can toggle this behavior in the extension configuration of sg_forms in the TYPO3 Backend.

The setting groupTranslations can be found in the extension configuration under the "general" category.

Search in Form Manager

The overridden Form Manager view includes a search field above the form list. It filters forms by:

  • form name (label)
  • form persistenceIdentifier

FormRequiringAction

This module allows a form requiring an action before it can be submitted.

Example together with sg_cookie_optin and Invisible ReCaptcha V2

import FormRequiringAction from 'FormRequiringAction';

// ...

document.querySelectorAll('[data-recaptcha-enable="1"]').forEach((_element) => {
	const form = _element.closest('form');

	// eslint-disable-next-line no-new
	new FormRequiringAction(form, {
		// Only show the template if external content isn't allowed
		//
		// Should be changed to only !FormRequiringAction.SgCookieOptin.isExternalContentAllowed()
		// when SgCookieOptin supports changing the consent via its API.
		// As this is currently not possible, we simply persist the state in the session storage.
		//
		// For the initial check we can still check if external content is allowed via SgCookieOptin.
		// Default value of the isExternalContentAllowed function's argument is 'iframes',
		// change to 'recaptcha' if you have a recaptcha group inside SgCookieOptin.
		showTemplate: !(
			FormRequiringAction.SgCookieOptin.isExternalContentAllowed('iframes') ||
			Boolean(sessionStorage.getItem('form-requiring-action--hidden'))
		),
		// Include ReCaptcha on the page
		handleAccept: (_form, isInitialPageLoad) => {
			if (!isInitialPageLoad) {
				// Set the value in the session storage to keep the template hidden after accepting once.
				sessionStorage.setItem('form-requiring-action--hidden', 'true');
			}

			FormRequiringAction.InvisibleRecaptchaV2.handleAccept(
				_form,
				_element.dataset.recaptchaSitekey,
			);
		},
	});
});

Example together with Usercentrics and Invisible ReCaptcha V2

// Event listener only necessary for CMP v2 (https://docs.usercentrics.com/#/browser-cmp)
document.addEventListener('UC_UI_INITIALIZED', () => {
	// Store the Usercentrics Recaptcha template ID
	const recaptchaTemplateId = FormRequiringAction.Usercentrics.templateId.recaptcha;

	document.querySelectorAll('[data-recaptcha-enable="1"]').forEach((_element) => {
		const form = _element.closest('form');

		new FormRequiringAction(form, {
			// Only show the template if external content isn't allowed
			showTemplate:
				!FormRequiringAction.Usercentrics.isExternalContentOfTypeAllowed(
					recaptchaTemplateId,
				),
			// Show the Usercentrics review modal when clicking on the "form-requiring-action__review" button
			handleReview: () => {
				FormRequiringAction.Usercentrics.showInfoModal(recaptchaTemplateId);
			},
			// Allow the content by Usercentrics and include ReCaptcha on the page
			handleAccept: (_form) => {
				FormRequiringAction.Usercentrics.allowExternalContent(recaptchaTemplateId);
				FormRequiringAction.InvisibleRecaptchaV2.handleAccept(
					_form,
					_element.dataset.recaptchaSitekey,
				);
			},
		});
	});

	// Automatically load ReCaptcha when it's allowed directly in the Usercentrics settings
	FormRequiringAction.Usercentrics.acceptOnExternalContentAllowed(recaptchaTemplateId);
});

Standalone usage

  1. Call FormRequiringAction.onReady(); from inside your main.js or similar file.
  • Alternatively add the following to your HTML:
<script data-ignore="1">
	// Loading hasn't finished yet
	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', FormRequiringAction.onReady);
	} else {
		// DOMContentLoaded has already fired
		FormRequiringAction.onReady();
	}
</script>
  1. Example for Google Invisible ReCaptcha v2

Add the following HTML to your page:

<script data-ignore="1">
	window.addEventListener('form-requiring-action--ready', () => {
		new FormRequiringAction('#contactForm-1595', {
			template: `<div class="form-requiring-action__template alert alert-info"><strong>We need your consent to embed reCAPTCHA! </strong><br><span>We use reCAPTCHA to check your entered information. This service may collect data about your activity. Please <span class="form-requiring-action__accept">accept</span> the service to proceed.</span></div>`,
			handleAccept: (form) => {
				FormRequiringAction.InvisibleRecaptchaV2.handleAccept(form, 'your-site-key');
			},
			handleSubmitNoAccept: () => {
				// Show an alert with error message
				window.alert('This form requires Google ReCaptcha. Please allow loading it.');
			},
		});
	});
</script>