chore: copy ep_products and ep_theme from EP/website@9593cc8

Copied unchanged from public/typo3conf/ext/ of EP/website at
9593cc8631354f8a363d6609de8b7433f669a9a0 (Git-tracked files only).
History stays in the old repository. Only composer.json files are
added so the extensions install as path packages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
frommeandClaude Opus 5.5 committed 2026-10-21 14:01:20 +02:00
1 parent 59afaecea1
commit a46918de9d
778 files changed
+67000 -2

No files matched your search

@@ -0,0 +1,156 @@
<?php
namespace EP\EpTheme\Form;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
abstract class AbstractMappedFinisher extends AbstractFinisher
{
/**
* Resolves the 'mapping' option into remote name => local form element identifier.
*/
protected function parseMapping(): array
{
$mappings = [];
$mappingOption = (string)$this->parseOption('mapping');
foreach (GeneralUtility::trimExplode(',', $mappingOption, true) as $mapping) {
$parts = GeneralUtility::trimExplode(':', $mapping, true, 2);
if (2 !== count($parts)) {
continue;
}
[$remote, $local] = $parts;
if ('' === $remote || '' === $local) {
continue;
}
$mappings[$remote] = $local;
}
return $mappings;
}
/**
* The mapped values, keyed by the remote name the receiving side expects. Values pass through
* normalizeValue() just like the labeled ones do, so a date element arrives as a Y-m-d string
* instead of a serialized DateTime. A value that does not survive normalization keeps its key:
* the remote names make up the request shape and dropping one would change it.
*/
protected function buildPayload(): array
{
$values = [];
$formValues = $this->finisherContext->getFormValues();
foreach ($this->parseMapping() as $remote => $local) {
if (false === array_key_exists($local, $formValues)) {
continue;
}
$values[$remote] = $this->normalizeValue($formValues[$local], $local);
}
return $values;
}
/**
* Every submitted value that the mapping does not already cover, keyed by the element label
* as the editor sees it in the form editor. Forms built in the backend use generated
* identifiers (text-1, singleselect-2, ...) that carry no meaning and cannot be renamed
* without breaking existing finisher options, so the label is the only stable description
* of what was asked.
*
* The label is taken from the form definition, not through TranslationService: a translated
* label would change the payload keys with the site language, and these keys are stored.
*/
protected function buildLabeledValues(): array
{
$formValues = $this->finisherContext->getFormValues();
$skip = array_merge(
array_values($this->parseMapping()),
GeneralUtility::trimExplode(',', (string)$this->parseOption('exclude'), true)
);
// Keyed by identifier in form order, and contains only real form elements - iterating
// this instead of the form values keeps the collection in the order of the form and
// leaves out anything that is not an element the visitor filled in.
$elements = $this->finisherContext->getFormRuntime()->getFormDefinition()->getElements();
$collected = [];
foreach ($elements as $identifier => $element) {
if (true === in_array($identifier, $skip, true) || false === array_key_exists($identifier, $formValues)) {
continue;
}
$value = $this->normalizeValue($formValues[$identifier], $identifier);
if (null === $value || '' === $value || [] === $value) {
continue;
}
$label = trim($element->getLabel());
if ('' === $label) {
$label = $identifier;
}
if (true === array_key_exists($label, $collected)) {
$label .= ' (' . $identifier . ')';
}
$collected[$label] = $value;
}
return $collected;
}
/**
* @param mixed $value
*
* @return mixed null when the value has no JSON representation
*/
protected function normalizeValue($value, string $identifier)
{
if (true === is_array($value)) {
$normalized = [];
foreach ($value as $item) {
$item = $this->normalizeValue($item, $identifier);
if (null !== $item && '' !== $item) {
$normalized[] = $item;
}
}
return $normalized;
}
if ($value instanceof \DateTimeInterface) {
return $value->format('Y-m-d');
}
if ($value instanceof FileReference) {
$resource = $value->getOriginalResource();
return null !== $resource ? $resource->getName() : null;
}
if (true === is_object($value)) {
$this->logWarning('Form value skipped while collecting labeled values: unsupported type.', [
'element' => $identifier,
'class' => get_class($value),
]);
return null;
}
return $value;
}
protected function logWarning(string $message, array $context = []): void
{
GeneralUtility::makeInstance(LogManager::class)
->getLogger(static::class)
->warning($message, array_merge([
'finisher' => static::class,
], $context));
}
}
@@ -0,0 +1,74 @@
<?php
namespace EP\EpTheme\Form;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
class AddReferrerFinisher extends AbstractFinisher
{
protected function executeInternal()
{
$this->addCurrentUrlField();
$this->addReferrerField();
}
private function addReferrerField(): void
{
// Look for key 'referrer' in frontend session
$referrer = $GLOBALS['TSFE']->fe_user->getKey('ses', 'referrer');
if (null === $referrer) {
return;
}
// Add textfield to form containing referrer value
$field = $this->finisherContext
->getFormRuntime()
->getFormDefinition()
->getPageByIndex(0)
->createElement('referrer', 'Text');
$field->setDefaultValue($referrer);
$field->setDataType('string');
$field->setLabel('Referrer');
// Add finisher variable for potential later use
$this->finisherContext->getFinisherVariableProvider()->add(
$this->shortFinisherIdentifier,
'referrer',
$referrer
);
// Remove session key
$GLOBALS['TSFE']->fe_user->setKey('ses', 'referrer', null);
$GLOBALS['TSFE']->fe_user->storeSessionData();
}
private function addCurrentUrlField(): void
{
// Get URL of current page
$pageId = $GLOBALS['TSFE']->id;
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
$url = $site->getRouter()->generateUri($pageId);
// Add textfield to form containing url value
$field = $this->finisherContext
->getFormRuntime()
->getFormDefinition()
->getPageByIndex(0)
->createElement('pageurl', 'Text');
$field->setDefaultValue($url);
$field->setDataType('string');
$field->setLabel('URL');
// Add finisher variable for potential later use
$this->finisherContext->getFinisherVariableProvider()->add(
$this->shortFinisherIdentifier,
'pageurl',
$url
);
}
}
@@ -0,0 +1,83 @@
<?php
namespace EP\EpTheme\Form;
use EP\EpProducts\EpTeam\ApiClient;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class ApplicationApiFinisher extends AbstractMappedFinisher
{
/**
* @var ApiClient
*/
protected $apiClient;
/**
* @param ApiClient $apiClient
*/
public function injectApiClient(ApiClient $apiClient)
{
$this->apiClient = $apiClient;
}
protected function executeInternal()
{
try {
$payload = $this->buildPayload();
if ([] === $payload) {
$this->logWarning('Application form API finisher skipped: the mapping produced no values.');
return;
}
$formDefinition = $this->finisherContext->getFormRuntime()->getFormDefinition();
$formLabel = trim($formDefinition->getLabel());
$payload['form'] = $formDefinition->getIdentifier();
// The name the editor gave the form, so the receiving side can label a submission
// without keeping a list of identifiers. Taken from the definition rather than
// through TranslationService: a translated label would differ per site language.
$payload['formLabel'] = '' !== $formLabel ? $formLabel : $formDefinition->getIdentifier();
$payload['submittedAt'] = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM);
$payload = array_merge($payload, $this->resolvePageContext());
$payload['details'] = $this->buildLabeledValues();
$this->apiClient->submitApplicationForm($payload);
} catch (\Throwable $e) {
$this->logWarning('Application form API finisher request failed.', [
'exception' => $e,
]);
}
}
/**
* The page the form was submitted from. The URL is generated through the site router in the
* visitor's language, the same way AddReferrerFinisher does it, so it is the page's canonical
* URL rather than the submit request with its form arguments attached.
*/
private function resolvePageContext(): array
{
if (false === isset($GLOBALS['TSFE']) || 0 === (int)$GLOBALS['TSFE']->id) {
return [];
}
$pageUid = (int)$GLOBALS['TSFE']->id;
$context = ['pageUid' => $pageUid];
try {
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageUid);
$context['pageUrl'] = (string)$site->getRouter()->generateUri($pageUid, [
'_language' => $GLOBALS['TSFE']->getLanguage(),
]);
} catch (\Throwable $e) {
// An unroutable page must not cost the submission, so the uid is sent without a URL.
$this->logWarning('Application form API finisher could not resolve the page URL.', [
'pageUid' => $pageUid,
'exception' => $e,
]);
}
return $context;
}
}
@@ -0,0 +1,35 @@
<?php
namespace EP\EpTheme\Form;
use EP\EpProducts\MyEP\ApiClient;
class BpnApiFinisher extends AbstractMappedFinisher
{
/**
* @var ApiClient
*/
protected $apiClient;
/**
* @param ApiClient $apiClient
*/
public function injectApiClient(ApiClient $apiClient)
{
$this->apiClient = $apiClient;
}
protected function executeInternal()
{
try {
$values = $this->buildPayload();
if ([] !== $values) {
$this->apiClient->registerAddress($values);
}
} catch (\Throwable $e) {
$this->logWarning('Bpn API finisher request failed.', [
'exception' => $e,
]);
}
}
}
@@ -0,0 +1,40 @@
<?php
namespace EP\EpTheme\Form;
use Symfony\Component\HttpClient\HttpClient;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class WebhookFinisher extends AbstractMappedFinisher
{
protected function executeInternal()
{
try {
$url = trim((string)$this->parseOption('url'));
if ('' === $url) {
return;
}
$payload = $this->buildPayload();
if ([] === $payload) {
return;
}
$response = HttpClient::create()->request('POST', $url, [
'json' => $payload,
]);
$statusCode = $response->getStatusCode();
if ($statusCode < 200 || $statusCode >= 300) {
$this->logWarning('Webhook finisher returned an unexpected response status.', [
'url' => $url,
'statusCode' => $statusCode,
]);
}
} catch (\Throwable $e) {
$this->logWarning('Webhook finisher request failed.', [
'exception' => $e,
]);
}
}
}