diff --git a/Classes/Finisher/Forms/FormsFinisher.php b/Classes/Finisher/Forms/FormsFinisher.php index 55cfecdb1f00611203ec95b49e62e4d10e48c538..1a912ce744330af0aea9a751bd1a49ae33045e91 100644 --- a/Classes/Finisher/Forms/FormsFinisher.php +++ b/Classes/Finisher/Forms/FormsFinisher.php @@ -33,12 +33,14 @@ use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher; /** - * This finisher sends an email to one recipient + * This finisher sends an email via sg_mail after form submission and enables customization of mail markers */ class FormsFinisher extends AbstractFinisher { /** - * Executes this finisher + * + * Send email with the sgmail api to one or more recipients + * overwrites the mail markers with custom identifiers if provided * * @see AbstractFinisher::execute() * @@ -49,15 +51,49 @@ class FormsFinisher extends AbstractFinisher { */ protected function executeInternal() { $formValues = $this->finisherContext->getFormValues(); + $formRuntime = $this->finisherContext->getFormRuntime(); + + // check if all the necessary objects exist + if ($formRuntime !== NULL) { + $formDefinition = $formRuntime->getFormDefinition(); + } else { + return; + } + + if ($formRuntime === NULL) { + return; + } + + $markers = []; + foreach ($formValues as $identifier => $value) { + $formElement = $formDefinition->getElementByIdentifier($identifier); + if (!$formElement) { + $markers[$identifier] = $value; + continue; + } + $formElemenProperties = $formElement->getProperties(); + if (isset($formElemenProperties['markerName']) && \trim($formElemenProperties['markerName']) !== '') { + $markers[\trim($formElemenProperties['markerName'])] = $value; + } else { + $markers[$identifier] = $value; + } + } + + $templateName = $this->parseOption('template'); + if ($this->parseOption('template') === '') { + $templateName = $formDefinition->getIdentifier(); + } + + $ignoreMailQueue = (boolean) $this->parseOption('ignoreMailQueue'); + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); /** @var \SGalinski\SgMail\Service\MailTemplateService $mailTemplateService */ $mailTemplateService = $objectManager->get( - MailTemplateService::class, $this->parseOption('template'), $this->parseOption('extension'), $formValues + MailTemplateService::class, $templateName, $this->parseOption('extension'), $markers ); - $mailTemplateService->setIgnoreMailQueue(FALSE); + $mailTemplateService->setIgnoreMailQueue($ignoreMailQueue); $mailTemplateService->setLanguage($GLOBALS['TSFE']->config['config']['language']); - $mailTemplateService->setSubject($this->parseOption('subject')); $mailTemplateService->setToAddresses($this->parseOption('mailTo')); $mailTemplateService->setFromAddress($this->parseOption('mailFrom')); $mailTemplateService->setFromName($this->parseOption('userName')); diff --git a/Classes/Service/MailTemplateService.php b/Classes/Service/MailTemplateService.php index e3f893a77c92b5e259dc1a0ff3036a7d063fee7e..140c2490cb71233889f30c9890d72ca0c4a70109 100644 --- a/Classes/Service/MailTemplateService.php +++ b/Classes/Service/MailTemplateService.php @@ -60,6 +60,8 @@ class MailTemplateService { const DEFAULT_TEMPLATE_PATH = 'Resources/Private/Templates/SgMail/'; const CACHE_NAME = 'sg_mail_registerArrayCache'; const CACHE_LIFETIME_IN_SECONDS = 86400; + const REGISTER_FILE = 'Register.php'; + const CONFIG_PATH = 'Configuration/MailTemplates'; /** * @var array $toAddresses @@ -305,7 +307,13 @@ class MailTemplateService { if (file_exists($defaultTemplateFile)) { $defaultTemplateContent = file_get_contents($defaultTemplateFile); } else { - return FALSE; + // use configured default html template + $defaultTemplateFile = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultHtmlTemplate']; + if (file_exists($defaultTemplateFile)) { + $defaultTemplateContent = file_get_contents($defaultTemplateFile); + } else { + return FALSE; + } } } elseif (filter_var($template->getToAddress(), FILTER_VALIDATE_EMAIL)) { $this->setToAddresses(trim($template->getToAddress())); @@ -340,6 +348,8 @@ class MailTemplateService { /** @var StandaloneView $emailView */ $emailView = $this->objectManager->get(StandaloneView::class); $emailView->assignMultiple($this->markers); + $emailView->assign('all_fields', $this->getAllMarker($this->markers)); + if (NULL === $defaultTemplateContent) { $emailView->setTemplateSource(\trim($template->getSubject())); $subject = $emailView->render(); @@ -798,7 +808,7 @@ class MailTemplateService { foreach ($extensionList as $extensionName) { $extensionConfigDirectory = ExtensionManagementUtility::extPath($extensionName); - $extensionConfigDirectory .= '/Configuration/MailTemplates'; + $extensionConfigDirectory .= self::CONFIG_PATH; $configFiles = GeneralUtility::getFilesInDir($extensionConfigDirectory); foreach ($configFiles as $configFile) { @@ -806,36 +816,73 @@ class MailTemplateService { $extensionKey = $configArray['extension_key']; $templateKey = $configArray['template_key']; - // transform template directory name: your_templates => YourTemplates/ - $templateDirectoryParts = GeneralUtility::trimExplode('_', $configArray['template_key']); - $templateDirectory = ''; - foreach ($templateDirectoryParts as $part) { - $templateDirectory .= ucfirst($part); + if ($extensionKey === NULL || $templateKey === NULL) { + continue; } - $templateDirectory .= '/'; - $templatePath = ExtensionManagementUtility::extPath( - $extensionName - ) . self::DEFAULT_TEMPLATE_PATH . $templateDirectory; - if ($configArray['template_path']) { - $templatePath = $configArray['template_key']; - } + $registerArray = self::writeRegisterArrayEntry( + $registerArray, $extensionKey, $templateKey, $configArray + ); + } + } + + return $registerArray; + } + + /** + * writes a single entry into the register array + * + * @param array $registerArray + * @param string $extensionKey + * @param string $templateKey + * @param array $configArray + * @param bool $transformTemplateFolder + * @param string $storeTemplateExtension + * @return array + */ + private static function writeRegisterArrayEntry( + array $registerArray, $extensionKey, $templateKey, array $configArray, + $transformTemplateFolder = TRUE, $storeTemplateExtension = '' + ) { + // If it is not explicitly set in which extension the html should be located, use the extension set in the template settings + if ($storeTemplateExtension === '') { + $storeTemplateExtension = $extensionKey; + } + + // give the option to use the template key as folder name. this is used mainly with auto registering + $templateDirectory = $templateKey; - $description = $configArray['description']; - $subject = $configArray['subject']; - $marker = $configArray['markers']; - - $registerArray[$extensionKey][$templateKey] = [ - 'templatePath' => $templatePath, - 'description' => $description, - 'marker' => $marker, - 'extension' => $extensionKey, - 'templateName' => $templateKey, - 'subject' => $subject - ]; + // by default folders with underscore will be transformed to upper camelcase + if ($transformTemplateFolder) { + // transform template directory name: your_templates => YourTemplates/ + $templateDirectoryParts = GeneralUtility::trimExplode('_', $templateKey); + $templateDirectory = ''; + foreach ($templateDirectoryParts as $part) { + $templateDirectory .= ucfirst($part); } } + $templateDirectory .= '/'; + $templatePath = ExtensionManagementUtility::extPath($storeTemplateExtension) . self::DEFAULT_TEMPLATE_PATH + . $templateDirectory; + + if ($configArray['template_path']) { + $templatePath = $configArray[$templateKey]; + } + + $description = $configArray['description']; + $subject = $configArray['subject']; + $marker = $configArray['markers']; + + $registerArray[$extensionKey][$templateKey] = [ + 'templatePath' => $templatePath, + 'description' => $description, + 'marker' => $marker, + 'extension' => $extensionKey, + 'templateName' => $templateKey, + 'subject' => $subject + ]; + return $registerArray; } @@ -873,4 +920,33 @@ class MailTemplateService { public function setSubject(string $subject) { $this->subject = $subject; } + + /** + * Get a single variable containing a list of all markers + * + * @param array $markers + * @return string + */ + private function getAllMarker(array $markers): string { + $allMarker = ''; + + foreach ($markers as $key => $value) { + if (\is_string($value)) { + $allMarker .= $key . ': ' . $value . PHP_EOL; + } elseif (\is_array($value)) { + foreach ($value as $innerKey => $innerValue) { + $allMarker .= $key . '.' . $innerKey . ': ' . $innerValue . PHP_EOL; + } + } elseif (\is_bool($value)) { + $valueAsString = $value ? 'true' : 'false'; + $allMarker .= $key . ': ' . $valueAsString . PHP_EOL; + } elseif (\is_object($value)) { + if (method_exists($value, '__toString')) { + $allMarker .= $key . ': ' . $value->__toString() . PHP_EOL; + } + } + } + + return $allMarker; + } } diff --git a/Classes/XClass/Form/FormEditorController.php b/Classes/XClass/Form/FormEditorController.php new file mode 100644 index 0000000000000000000000000000000000000000..6e9969afa12fb79a884610fd5848a6013d2a5770 --- /dev/null +++ b/Classes/XClass/Form/FormEditorController.php @@ -0,0 +1,224 @@ +<?php + +namespace SGalinski\SgMail\XClass\Form; + +/*************************************************************** + * Copyright notice + * + * (c) sgalinski Internet Services (https://www.sgalinski.de) + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use SGalinski\SgMail\Service\MailTemplateService; +use SGalinski\SgMail\Service\TypoScriptSettingsService; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Form\Type\FormDefinitionArray; + +/** @noinspection LongInheritanceChainInspection */ + +/** + * Displays the form editor. Enables hooking into the save process of the form to handle automatic sg mail registration + */ +class FormEditorController extends \TYPO3\CMS\Form\Controller\FormEditorController { + + /** + * This contains type names of fields we dont care about in sgmail + * + * @var array + */ + const IGNORE_FIELDS = [ + 'StaticText', 'Hidden', 'GridRow' + ]; + + /** + * This contains the names of sgmail finishers, so we can identify if we need to generate a template registration + * + * @var array + */ + const MAIL_FINISHER = [ + 'MailToSenderFinisher', 'MailToReceiverFinisher' + ]; + + /** + * Add automatic SgMail Configuration + * + * @param string $formPersistenceIdentifier + * @param FormDefinitionArray $formDefinition + * @internal + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition) { + /** @noinspection PhpInternalEntityUsedInspection */ + parent::saveFormAction($formPersistenceIdentifier, $formDefinition); + + // immediately exit when no finisher is defined (that means no sgmail integration anyway) + if (!$formDefinition['finishers']) { + return; + } + + /** @var array $finishers */ + $finishers = $formDefinition['finishers']; + $extensionKey = ''; + $templateKey = ''; + + foreach ($finishers as $finisher) { + + // if one finisher prevents automatic registration, exit this function + if (!$finisher['options']['automaticRegistration']) { + return; + } + + if (!\in_array($finisher['identifier'], self::MAIL_FINISHER, TRUE)) { + continue; + } + + // retrieve the extension and template key and jump out of loop + $extensionKey = $finisher['options']['extension']; + $templateKey = $finisher['options']['template']; + break; + } + + // if no template key was explicitly set, use the form identifier as template key + if ($templateKey === '') { + $templateKey = $formDefinition['identifier']; + } + + // if there was no sg mail finisher or an missing key then simply exit the function + if ($extensionKey === '' || $templateKey === '') { + return; + } + + // parse yaml for form fields + $absoluteFilePath = GeneralUtility::getFileAbsFileName($formPersistenceIdentifier); + $parsedYaml = \yaml_parse_file($absoluteFilePath); + + $renderables = []; + foreach ($parsedYaml['renderables'] as $formPage) { + if (\is_array($formPage['renderables'])) { + foreach ($formPage['renderables'] as $row) { + if (!\is_array($row) || !$row['type']) { + continue; + } + + if ($row['type'] === 'GridRow') { + foreach ($row['renderables'] as $renderableInsideGridrow) { + $renderables[] = $renderableInsideGridrow; + } + + } elseif (!\in_array($row['type'], self::IGNORE_FIELDS, TRUE)) { + $renderables[] = $row; + } + } + } else { + return; + } + } + + // write the new Register.php file + $this->writeRegisterFile($renderables, $extensionKey, $templateKey); + + // call register function in mail template service class + MailTemplateService::registerExtensions(); + + // clear caches + $this->clearCaches(); + } + + /** + * Builds the register array and saves it to the configured location + * + * @param array $renderables + * @param string $extensionKey + * @param string $templateKey + */ + private function writeRegisterFile(array $renderables, $extensionKey, $templateKey) { + // get the location where automatic registrations should be stored + $configurationLocation = $this->getRegistrationPath(); + + $registerFolder = GeneralUtility::getFileAbsFileName( + $configurationLocation + ); + // create folder + GeneralUtility::mkdir($registerFolder); + + $registerFile = GeneralUtility::getFileAbsFileName( + $registerFolder . '/' . $templateKey . '.php' + ); + + // build the register array + $newRegisterArray = [ + 'extension_key' => $extensionKey, + 'template_key' => $templateKey, + 'description' => $templateKey, + 'subject' => $templateKey, + 'markers' => [] + ]; + + // add the markers for this template + foreach ($renderables as $element) { + $markerName = $element['identifier']; + // if markerName is explicitly set, override the registered identifier + if (isset($element['properties']['markerName']) && $element['properties']['markerName'] !== '') { + $markerName = $element['properties']['markerName']; + } + + $newRegisterArray['markers'][] = [ + 'marker' => $markerName, + 'type' => MailTemplateService::MARKER_TYPE_STRING, + 'value' => $element['label'], + 'description' => $element['label'] + ]; + } + + file_put_contents($registerFile, '<?php return ' . var_export($newRegisterArray, TRUE) . ';'); + } + + /** + * Clear the sgmail register cache + * + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + private function clearCaches() { + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $cacheManager = $objectManager->get(CacheManager::class); + + /** @var FrontendInterface $cache */ + $cache = $cacheManager->getCache(MailTemplateService::CACHE_NAME); + /** @var FrontendInterface $cache */ + $cache->flush(); + } + + /** + * Returns the path to the configured location where automatic mail template registrations should be + * + * @return string + */ + private function getRegistrationPath(): string { + // get typoscript settings from sg mail + /** @var TypoScriptSettingsService $typoScriptSettingsService */ + $typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class); + $tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail'); + + // get the location where automatic registrations should be stored + return 'EXT:' . $tsSettings['mail']['configurationLocation'] . '/' . MailTemplateService::CONFIG_PATH; + } +} diff --git a/Configuration/MailTemplates/Default.html b/Configuration/MailTemplates/Default.html new file mode 100644 index 0000000000000000000000000000000000000000..f8663358b012bcbbbc15b90b3daf3e298db6376d --- /dev/null +++ b/Configuration/MailTemplates/Default.html @@ -0,0 +1 @@ +{all_fields} diff --git a/Configuration/TypoScript/setup.ts b/Configuration/TypoScript/setup.ts index f24bac282d64514ab2d63c92ea814e5220ccdf0b..21ec80a94ec17fc5fab5f26e01f57a5927b7149a 100644 --- a/Configuration/TypoScript/setup.ts +++ b/Configuration/TypoScript/setup.ts @@ -15,6 +15,12 @@ module.tx_sgmail { # comma-separated list of additional cc addresses default.cc = + + # extension where automatic generated registrations are put. Will be appended with /Configuration/MailTemplates/. Make sure these folders exist! + configurationLocation = project_theme + + # default html template file, which serves as a fallback for all mail templates + defaultHtmlTemplate = EXT:sg_mail/Configuration/MailTemplates/Default.html } # default template language diff --git a/Configuration/Yaml/Forms/FinisherSetupBE.yaml b/Configuration/Yaml/Forms/FinisherSetupBE.yaml index 479dc640d77429c13cd973e6da05cc759d7eaafd..129d2501d08916c24bfc71dc55157bf146f6c7ed 100644 --- a/Configuration/Yaml/Forms/FinisherSetupBE.yaml +++ b/Configuration/Yaml/Forms/FinisherSetupBE.yaml @@ -12,9 +12,10 @@ TYPO3: predefinedDefaults: options: extension: 'sg_mail' - template: 'contact_user' + template: '' + automaticRegistration: '' + ignoreMailQueue: true mailTo: '' - subject: '' userName: '' replyTo: '' cc: '' @@ -27,10 +28,11 @@ TYPO3: predefinedDefaults: options: extension: 'sg_mail' - template: 'contact_admin' + template: '' + automaticRegistration: '' + ignoreMailQueue: true mailTo: '' mailFrom: '' - subject: '' userName: '' replyTo: '' cc: '' @@ -68,7 +70,16 @@ TYPO3: templateName: 'Inspector-TextEditor' label: 'Unique Template name' propertyPath: 'options.template' - enableFormelementSelectionButton: true + 125: + identifier: 'automaticRegistration' + templateName: 'Inspector-CheckboxEditor' + label: 'Automatic Registration (If selected, you can find your template in the "Mail Template" module after saving. Look for the extension and template key in the dropdown in the upper left corner of the template editor.' + propertyPath: 'options.automaticRegistration' + 127: + identifier: 'ignoreMailQueue' + templateName: 'Inspector-CheckboxEditor' + label: 'If selected, the mails are send immediately, otherwise the mails are added to the Mail Queue. See the Readme of the sg_mail extension for more Information.' + propertyPath: 'options.ignoreMailQueue' 130: identifier: 'mailTo' templateName: 'Inspector-TextEditor' @@ -83,17 +94,11 @@ TYPO3: templateName: 'Inspector-TextEditor' label: 'The email address of the website' propertyPath: 'options.mailFrom' - 150: - identifier: 'subject' - templateName: 'Inspector-TextEditor' - label: 'The subject of the E-Mail' - propertyPath: 'options.subject' 160: identifier: 'userName' templateName: 'Inspector-TextEditor' label: 'The name of the website user' propertyPath: 'options.userName' - enableFormelementSelectionButton: true propertyValidators: 10: 'NotEmpty' 20: 'FormElementIdentifierWithinCurlyBracesInclusive' @@ -129,7 +134,16 @@ TYPO3: templateName: 'Inspector-TextEditor' label: 'Template key' propertyPath: 'options.template' - enableFormelementSelectionButton: true + 125: + identifier: 'automaticRegistration' + templateName: 'Inspector-CheckboxEditor' + label: 'Automatic Registration (If selected, you can find your template in the "Mail Template" module after saving. Look for the extension and template key in the dropdown in the upper left corner of the template editor.' + propertyPath: 'options.automaticRegistration' + 127: + identifier: 'ignoreMailQueue' + templateName: 'Inspector-CheckboxEditor' + label: 'If selected, the mails are send immediately, otherwise the mails are added to the Mail Queue. See the Readme of the sg_mail extension for more Information.' + propertyPath: 'options.ignoreMailQueue' 130: identifier: 'mailTo' templateName: 'Inspector-TextEditor' @@ -140,11 +154,6 @@ TYPO3: templateName: 'Inspector-TextEditor' label: 'The email address of the website' propertyPath: 'options.mailFrom' - 150: - identifier: 'subject' - templateName: 'Inspector-TextEditor' - label: 'The subject of the E-Mail' - propertyPath: 'options.subject' 160: identifier: 'replyTo' templateName: 'Inspector-TextEditor' @@ -163,4 +172,4 @@ TYPO3: renderingOptions: translation: translationFile: - 90: 'EXT:project_theme/Resources/Private/Language/forms.xlf' \ No newline at end of file + 90: 'EXT:project_theme/Resources/Private/Language/forms.xlf' diff --git a/README.md b/README.md index 8894b84dad1bc3418cd6d5bf1572fbfb381d9b51..d405cbed7517550d90f4f0570117550c64d280c1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ TYPO3 version: >7.6 This extension provides an email templating service and mail queue functionality for all your TYPO3 extensions. It also supports Templates in various languages, which can be managed in the backend. -Additionally sg_mail provides a finisher class for the [Formhandler](https://typo3.org/extensions/repository/view/formhandler) extension, making it possible to manage its templates in the backend. +Additionally sg_mail comes with multiple features that help to integrate with [ext:form](https://docs.typo3.org/typo3cms/extensions/form/). sg_mail ist multi-site and multi-language ready. @@ -297,10 +297,31 @@ You can also tell the **MailTemplateService** to not ignore this mail when addin --- -#### FormhandlerFinisherService +### Ext:form Integration + +#### Template marker +This extension provides you the possibility to customize the identifier of the form input fields. These identifiers are used in the mail templates as marker names. +To change a fields identifier, select it in the form editor and enter your desired identifier in the field labeled "Email template marker name". After saving the form, the change identifier name will be overwritten. + +#### New mail finisher +sg_mail comes with two new finisher for your forms with the following settings regarding sg_mail integration: + +- **Template key**: If this field is empty, the form identifier will be used instead +- **Automatic Registration**: A sg_mail registration will automatically be created. See the **Automatic Registration** section for more information +- **Ignore Mail Queue**: Mails will get send immediately and not added to the mail queue + + +#### Automatic Registration +If you select to automatically register your forms with sg_mail, the default html template for the emails will be used. + +You can customize the TypoScript settings in the **setup.ts** in order to have control over automatically generated registration files: + + # extension where automatic generated registrations are put. Will be appended with /Configuration/MailTemplates/. Make sure these folders exist! + configurationLocation = your_extension_key + + # default html template file, which serves as a fallback for all mail templates + defaultHtmlTemplate = EXT:project_theme/Configuration/MailTemplates/Default.html -This class is an implementation of the **Typoheads\Formhandler\Finisher\AbstractFinisher** Class -that is used by the extension [formhandler extension](https://typo3.org/extensions/repository/view/formhandler). --- diff --git a/ext_localconf.php b/ext_localconf.php index 94cef014828ee608dc90ac9eb334a53c2c38c009..4e897404a33057c673d3cbb87358ff7371862d51 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -47,6 +47,11 @@ if (TYPO3_MODE === 'BE') { \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(file_get_contents($tsPath . 'setup.ts')); } +// register xclasses +$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Form\Controller\FormEditorController::class] = [ + 'className' => \SGalinski\SgMail\XClass\Form\FormEditorController::class, +]; + // Cache registration $cacheName = \SGalinski\SgMail\Service\MailTemplateService::CACHE_NAME; if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$cacheName])) {