<?php declare(strict_types=1);
namespace App\EventSubscriber;
use App\Entity\PageInterface;
use App\Entity\FormPageInterface;
use Vich\UploaderBundle\Event\Event;
use Vich\UploaderBundle\Event\Events;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class PageTemplateFileSubscriber implements EventSubscriberInterface
{
private $filesystem;
private $privateTemplatesPath;
public function __construct(Filesystem $filesystem, ParameterBagInterface $params)
{
$this->filesystem = $filesystem;
$this->privateTemplatesPath = $params->get('private_templates_path');
}
public static function getSubscribedEvents(): array
{
return [
Events::POST_UPLOAD => ['onVichUploaderPostUpload']
];
}
/**
* Alters template files contents.
*
* @param Event $event
* @return void
*/
public function onVichUploaderPostUpload(Event $event)
{
$entity = $event->getObject();
$mappingName = $event->getMapping()->getMappingName();
$supportedMappingName = ['page_template', 'page_confirmation_template', 'page_lead_email'];
if (!$entity instanceof PageInterface || !in_array($mappingName, $supportedMappingName)) {
return;
}
switch ($mappingName) {
case 'page_template':
/** @var PageInterface $entity */
$this->processTemplate($entity, $entity->getTemplateFile(), $mappingName);
break;
case 'page_confirmation_template':
/** @var FormPageInterface $entity */
$this->processTemplate($entity, $entity->getConfirmationTemplateFile(), $mappingName);
break;
case 'page_lead_email':
/** @var FormPageInterface $entity */
$this->copyTemplate($entity, $entity->getLeadEmailTemplateFile());
break;
}
}
/**
* Twigifies template file.
*
* @param PageInterface $page
* @param File $file
* @param string $mappingName
* @return void
*/
protected function processTemplate(PageInterface $page, File $file, string $mappingName) {
$fileContents = file_get_contents($file->getRealPath());
// Add some custom code.
$meta = '';
$meta .= '<meta name="robots" content="noarchive, nofollow, noimageindex, noindex, noodp, nosnippet, notranslate, noydir">';
$meta .= "\n";
$meta .= '<meta name="desciption" content="{{ page.description }}">';
$pageHeadEndInclude = '{% include("inc/page_head_end.html.twig") %}';
$fileContents = preg_replace('/(<head[^>]*>)(.*?)(<\/head>)/is', "$1 $2 \n $meta \n $pageHeadEndInclude \n $3", $fileContents);
$pageBodyEndInclude = '{% include("inc/page_body_top.html.twig") %}';
$fileContents = preg_replace('/(<body[^>]*>)(.*?)(<\/body>)/is', "$1 \n $pageBodyEndInclude \n $2 $3", $fileContents);
// Add a csrf token related input.
// See https://symfony.com/doc/current/security/csrf.html#generating-and-checking-csrf-tokens-manually)
$csrfInput = '<input type="hidden" name="_token" value="{{ csrf_token(\''.$page->getMachineName().'\') }}">';
$fileContents = preg_replace('/(<form[^>]*>)(.*?)(<\/form>)/is', "$1 $2 \n $csrfInput \n $3", $fileContents);
// Make the page title dynamic.
$fileContents = preg_replace('/(<title>).*(<\/title>)/is', '${1}{{ page.title }}${2}', $fileContents);
// Restore assets related paths.
// The tricky thing is about managing srcset.
// $archiveFilenamePathParts = pathinfo($page->getAssetsArchiveFilename());
$assetsDirPath = 'uploads/assets/'.$page->getFilesystemPath(); //.'/'.$archiveFilenamePathParts['filename'];
$fileContents = preg_replace('/("|,\s)(\.\/)?(dist\/)?(images\/.*?)(\s|")/', "$1{{ asset('$assetsDirPath/$3$4') }}$5", $fileContents);
$fileContents = preg_replace('/(")(\.\/)?(dist\/)?(css\/[^"]*.css|js\/[^"]*.js)(")/', "$1{{ asset('$assetsDirPath/$3$4') }}$5", $fileContents);
// Add some custom code on the confirmation pages.
if ('page_confirmation_template' === $mappingName) {
$fileContents = preg_replace('/(<body[^>]*>)(.*?)(<\/body>)/s', "$1 $2 \n {% include('inc/confirm_body_end.html.twig') %} $3", $fileContents);
$fileContents = preg_replace('/(<div id="maniterm-data"[^>]*>)(.*?)(<\/div>)/s', "$1 $2 \n {% include('inc/maniterm_data.html.twig') %} $3", $fileContents);
} elseif ('page_confirmation_template' !== $mappingName && $page instanceof FormPageInterface) {
$this->setFormConfig($page, $fileContents);
}
$this->copyTemplate($page, $file, $fileContents);
}
/**
* Crawls the main FormPage template to set
* a form config. from fields and their attributes.
*
* @param FormPageInterface $page
* @param string $html
* @return void
*/
protected function setFormConfig(FormPageInterface $page, string $html) {
$formConfig = [];
$crawler = new Crawler($html);
// Parse HTML inputs.
foreach (['input', 'textarea'] as $tag) {
$inputs = $crawler->filter($tag);
/** @var \DOMElement $input */
foreach ($inputs as $input) {
$inputType = $input->getAttribute('type');
$inputName = $input->getAttribute('name');
if ('submit' === $inputType || '_token' === $inputName) {
continue;
}
// "radio" type must be rendered via ChoiceType.
if ($inputType === 'radio') {
$inputValue = $input->getAttribute('value');
if (isset($formConfig[$inputName])) {
$formConfig[$inputName]['choices'][$inputValue] = $inputValue;
}
else {
$formConfig[$inputName] = [
'type' => 'choice',
'required' => $input->hasAttribute('required'),
'expanded' => TRUE,
'novalidate' => $input->hasAttribute('data-chordflow-novalidate')
];
$formConfig[$inputName]['choices'][$inputValue] = $inputValue;
}
}
else {
$formConfig[$input->getAttribute('name')] = [
'type' => $inputType,
'required' => $input->hasAttribute('required'),
'pattern' => $input->getAttribute('pattern'),
];
}
}
}
// Parse HTML selects.
$selects = $crawler->filter('select');
/** @var \DOMElement $select */
foreach ($selects as $select) {
$selectName = $select->getAttribute('name');
$formConfig[$selectName] = [
'type' => 'choice',
'required' => $select->hasAttribute('required'),
];
/** @var \DOMElement $option */
foreach ($crawler->filter('[name="'.$selectName.'"]>option') as $option) {
$optionValue = $option->getAttribute('value');
!$optionValue ?: $formConfig[$selectName]['choices'][$optionValue] = $optionValue;
}
}
$page->setFormConfig($formConfig);
}
/**
* Copy the updated template file into private folder.
*
* @param PageInterface $page
* @param File $file
* @param string|null $newContent
* @return void
*/
private function copyTemplate(PageInterface $page, File $file, ?string $newContent = null): void
{
// Creates a file copy in template folder and dump content into the copy
$newPath = implode(DIRECTORY_SEPARATOR, [$this->privateTemplatesPath, $page->getFilesystemPath(), $file->getFilename()]);
$this->filesystem->copy($file->getPathname(), $newPath);
if ($newContent) {
$this->filesystem->dumpFile($newPath, $newContent);
}
}
}