Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
<?php
namespace Mautic\FormBundle\Helper;
use Mautic\CoreBundle\Helper\AbstractFormFieldHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\FormBundle\Entity\Field;
use Symfony\Component\Validator\Constraints\Blank;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\EqualTo;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Url;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class FormFieldHelper extends AbstractFormFieldHelper
{
private ValidatorInterface $validator;
private array $types = [
'captcha' => [
'constraints' => [
NotBlank::class => ['message' => 'mautic.form.submission.captcha.invalid'],
EqualTo::class => ['message' => 'mautic.form.submission.captcha.invalid'],
Blank::class => ['message' => 'mautic.form.submission.captcha.invalid'],
],
],
'checkboxgrp' => [],
'country' => [],
'date' => [],
'datetime' => [],
'email' => [
'filter' => 'email',
'constraints' => [
Email::class => ['message' => 'mautic.form.submission.email.invalid'],
],
],
'freetext' => [],
'freehtml' => [],
'hidden' => [],
'companyLookup' => [],
'number' => [
'filter' => 'float',
],
'slider' => [
'filter' => 'float',
],
'pagebreak' => [],
'password' => [],
'radiogrp' => [],
'select' => [],
'tel' => [],
'text' => [],
'textarea' => [],
'url' => [
'filter' => 'url',
'constraints' => [
Url::class => ['message' => 'mautic.form.submission.url.invalid'],
],
],
'file' => [],
];
public function __construct(Translator $translator, ?ValidatorInterface $validator = null)
{
$this->translator = $translator;
if (null === $validator) {
$validator = $validator = Validation::createValidator();
}
$this->validator = $validator;
parent::__construct();
}
/**
* Set the translation key prefix.
*/
public function setTranslationKeyPrefix(): void
{
$this->translationKeyPrefix = 'mautic.form.field.type.';
}
public function getTypes(): array
{
return $this->types;
}
public function getFieldFilter(string $type): string
{
return $this->types[$type]['filter'] ?? 'string';
}
/**
* @param Field $f
*/
public function validateFieldValue($type, $value, $f = null): array
{
$errors = [];
if (isset($this->types[$type]['constraints'])) {
foreach ($this->types[$type]['constraints'] as $constraint => $opts) {
// don't check empty values unless the constraint is NotBlank
if (NotBlank::class === $constraint && empty($value)) {
continue;
}
if ('captcha' == $type) {
$captcha = $f->getProperties()['captcha'];
if (empty($captcha) && Blank::class !== $constraint) {
// Used as a honeypot
$captcha = '';
} elseif (Blank::class === $constraint) {
continue;
}
if (EqualTo::class == $constraint) {
$opts['value'] = $captcha;
}
}
/** @var ConstraintViolationList $violations */
$violations = $this->validator->validate($value, new $constraint($opts));
if (count($violations)) {
/** @var ConstraintViolation $v */
foreach ($violations as $v) {
$transParameters = $v->getParameters();
if (null !== $f) {
$transParameters['%label%'] = '&quot;'.$f->getLabel().'&quot;';
}
$errors[] = $this->translator->trans($v->getMessage(), $transParameters, 'validators');
}
}
}
}
return $errors;
}
/**
* Search and replace the HTML of the form field with the value.
*/
public function populateField($field, $value, $formName, &$formHtml): void
{
$alias = $field->getAlias();
// Adds the "readonly" attribute to a field if it is configured as read-only with auto-fill enabled and a sanitized value exists.
$fieldAttributeReadOnly = fn ($field, $sanitizedValue) => ($field->isAutoFillReadOnly() && $sanitizedValue) ? ' readonly ' : '';
switch ($field->getType()) {
case 'text':
case 'number':
case 'email':
case 'hidden':
case 'tel':
case 'url':
case 'date':
case 'datetime':
if ('tel' === $field->getType()) {
$sanitizedValue = InputHelper::clean($value);
} else {
$sanitizedValue = $this->sanitizeValue($value);
}
if (preg_match('/<input(.*?)value="(.*?)"(.*?)id="mauticform_input_'.$formName.'_'.$alias.'"(.*?)\/?>/i', $formHtml, $match)) {
$replace = '<input'.$match[1].'id="mauticform_input_'.$formName.'_'.$alias.'"'.$match[3].'value="'.$sanitizedValue.'"'
.$match[4].$fieldAttributeReadOnly($field, $sanitizedValue).'/>';
$formHtml = str_replace($match[0], $replace, $formHtml);
}
break;
case 'textarea':
if (preg_match('/<textarea(.*?)id="mauticform_input_'.$formName.'_'.$alias.'"(.*?)>(.*?)<\/textarea>/i', $formHtml, $match)) {
$value = $this->sanitizeValue($value);
$replace = '<textarea'.$match[1].'id="mauticform_input_'.$formName.'_'.$alias.'"'.$match[2].$fieldAttributeReadOnly($field, $value).'>'.$value.'</textarea>';
$formHtml = str_replace($match[0], $replace, $formHtml);
}
break;
case 'checkboxgrp':
$isUrlEncoded = is_string($value) && str_contains($value, '%7C');
$separator = $isUrlEncoded ? urlencode('|') : '|';
if (is_string($value) && strrpos($value, $separator) > 0) {
$value = explode($separator, $value);
} elseif (!is_array($value)) {
$value = [$value];
}
foreach ($value as $val) {
$val = $this->sanitizeValue($val);
if (preg_match(
'/<input(.*?)id="mauticform_checkboxgrp_checkbox_'.$alias.'(.*?)"(.*?)value="'.$val.'"(.*?)\/?>/i',
$formHtml,
$match
)) {
$replace = '<input'.$match[1].'id="mauticform_checkboxgrp_checkbox_'.$alias.$match[2].'"'.$match[3].'value="'.$val.'"'
.$match[4].' checked />';
$formHtml = str_replace($match[0], $replace, $formHtml);
}
}
break;
case 'radiogrp':
$value = $this->sanitizeValue($value);
if (preg_match('/<input(.*?)id="mauticform_radiogrp_radio_'.$alias.'(.*?)"(.*?)value="'.$value.'"(.*?)\/?>/i', $formHtml, $match)) {
$replace = '<input'.$match[1].'id="mauticform_radiogrp_radio_'.$alias.$match[2].'"'.$match[3].'value="'.$value.'"'.$match[4]
.' checked />';
$formHtml = str_replace($match[0], $replace, $formHtml);
}
break;
case 'select':
case 'country':
$regex = '/<select\s*id="mauticform_input_'.$formName.'_'.$alias.'"(.*?)<\/select>/is';
if (preg_match($regex, $formHtml, $match)) {
$origText = $match[0];
$replace = str_replace(
'<option value="'.$this->sanitizeValue($value).'">',
'<option value="'.$this->sanitizeValue($value).'" selected="selected">',
$origText
);
$formHtml = str_replace($origText, $replace, $formHtml);
}
break;
}
}
/**
* @param string $value
*
* @return string
*/
public function sanitizeValue($value)
{
$valueType = gettype($value);
$value = str_replace(['"', '>', '<'], ['&quot;', '&gt;', '&lt;'], strip_tags(rawurldecode($value)));
// for boolean expect 0 or 1
if ('boolean' === $valueType) {
return (int) $value;
}
return $value;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Mautic\FormBundle\Helper;
use Mautic\CoreBundle\Exception\FileUploadException;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\FileUploader;
use Mautic\FormBundle\Crate\UploadFileCrate;
use Mautic\FormBundle\Entity\Field;
use Mautic\FormBundle\Entity\Form;
use Mautic\FormBundle\Entity\Submission;
class FormUploader
{
public function __construct(
private FileUploader $fileUploader,
private CoreParametersHelper $coreParametersHelper,
) {
}
/**
* @throws FileUploadException
*/
public function uploadFiles(UploadFileCrate $filesToUpload, Submission $submission): void
{
$uploadedFiles = [];
$result = $submission->getResults();
$alias = ''; // Only for IDE - will be overriden by foreach
try {
foreach ($filesToUpload as $fileFieldCrate) {
$field = $fileFieldCrate->getField();
$alias = $field->getAlias();
$uploadDir = $this->getUploadDir($field);
$fileName = $this->fileUploader->upload($uploadDir, $fileFieldCrate->getUploadedFile());
$result[$alias] = $fileName;
$uploadedFile = $uploadDir.DIRECTORY_SEPARATOR.$fileName;
$this->fixRotationJPG($uploadedFile);
$uploadedFiles[] =$uploadedFile;
}
$submission->setResults($result);
} catch (FileUploadException) {
foreach ($uploadedFiles as $filePath) {
$this->fileUploader->delete($filePath);
}
throw new FileUploadException($alias);
}
}
/**
* @param string $fileName
*/
public function getCompleteFilePath(Field $field, $fileName): string
{
$uploadDir = $this->getUploadDir($field);
return $uploadDir.DIRECTORY_SEPARATOR.$fileName;
}
public function deleteAllFilesOfFormField(Field $field): void
{
if (!$field->isFileType()) {
return;
}
$uploadDir = $this->getUploadDir($field);
$this->fileUploader->delete($uploadDir);
}
public function deleteFilesOfForm(Form $form): void
{
$formId = $form->getId() ?: $form->deletedId;
$formUploadDir = $this->getUploadDirOfForm($formId);
$this->fileUploader->delete($formUploadDir);
}
/**
* @todo Refactor code that result can be accessed normally and not only as a array of values
*/
public function deleteUploadedFiles(Submission $submission): void
{
$fields = $submission->getForm()->getFields();
foreach ($fields as $field) {
$this->deleteFileOfFormField($submission, $field);
}
}
private function deleteFileOfFormField(Submission $submission, Field $field): void
{
$alias = $field->getAlias();
$results = $submission->getResults();
if (!$field->isFileType() || empty($results[$alias])) {
return;
}
$fileName = $results[$alias];
$filePath = $this->getCompleteFilePath($field, $fileName);
$this->fileUploader->delete($filePath);
}
private function getUploadDir(Field $field): string
{
$fieldId = $field->getId();
$formUploadDir = $this->getUploadDirOfForm($field->getForm()->getId());
return $formUploadDir.DIRECTORY_SEPARATOR.$fieldId;
}
/**
* @throws \LogicException If formId is null
*/
private function getUploadDirOfForm(int $formId): string
{
$uploadDir = $this->coreParametersHelper->get('form_upload_dir');
return $uploadDir.DIRECTORY_SEPARATOR.$formId;
}
/**
* Fix iOS picture orientation after upload PHP
* https://stackoverflow.com/questions/22308921/fix-ios-picture-orientation-after-upload-php.
*/
private function fixRotationJPG($filename): void
{
if (IMAGETYPE_JPEG != exif_imagetype($filename)) {
return;
}
$exif = exif_read_data($filename);
if (empty($exif['Orientation'])) {
return;
}
$ort = $exif['Orientation']; /* STORES ORIENTATION FROM IMAGE */
$ort1 = $ort;
if (!empty($ort1)) {
$image = imagecreatefromjpeg($filename);
$ort = $ort1;
switch ($ort) {
case 3:
$image = imagerotate($image, 180, 0);
break;
case 6:
$image = imagerotate($image, -90, 0);
break;
case 8:
$image = imagerotate($image, 90, 0);
break;
}
}
imagejpeg($image, $filename, 90);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Mautic\FormBundle\Helper;
class PointActionHelper
{
public static function validateFormSubmit($eventDetails, $action): bool
{
$form = $eventDetails->getForm();
$formId = $form->getId();
$limitToForms = $action['properties']['forms'];
if (!empty($limitToForms) && !in_array($formId, $limitToForms)) {
// no points change
return false;
}
return true;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Mautic\FormBundle\Helper;
use Mautic\FormBundle\Model\FormModel;
class PropertiesAccessor
{
public function __construct(
private FormModel $formModel,
) {
}
/**
* @param mixed[] $field
*
* @return mixed[]
*/
public function getProperties(array $field)
{
$hasContactFieldMapped = !empty($field['mappedField']) && !empty($field['mappedObject']) && 'contact' === $field['mappedObject'];
if ('country' === $field['type'] || ($hasContactFieldMapped && !empty($field['properties']['syncList']))) {
return $this->formModel->getContactFieldPropertiesList((string) $field['mappedField']);
} elseif (!empty($field['properties'])) {
return $this->getOptionsListFromProperties($field['properties']);
}
return [];
}
/**
* @param string|mixed[] $options
*
* @return string[]
*/
public function getChoices($options): array
{
$choices = [];
if (is_array($options) && !isset($options[0]['value'])) {
return array_flip($options);
}
if (!is_array($options)) {
$options = explode('|', (string) $options);
}
foreach ($options as $option) {
if (is_array($option)) {
if (isset($option['label']) && isset($option['alias'])) {
$choices[$option['label']] = $option['alias'];
} elseif (isset($option['label']) && isset($option['value'])) {
$choices[$option['label']] = $option['value'];
} else {
foreach ($option as $opt) {
$choices[$opt] = $opt;
}
}
} else {
$choices[$option] = $option;
}
}
return $choices;
}
/**
* @param array<string,mixed> $properties
*
* @return mixed[]
*/
private function getOptionsListFromProperties(array $properties)
{
if (!empty($properties['list']['list'])) {
return $properties['list']['list'];
} elseif (!empty($properties['optionlist']['list'])) {
return $properties['optionlist']['list'];
}
return [];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Mautic\FormBundle\Helper;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\FormBundle\Model\FormModel;
class TokenHelper
{
public const REGEX = '/{form=(.*?)}/';
public function __construct(
protected FormModel $formModel,
protected CorePermissions $security,
) {
}
public function findFormTokens($content): array
{
$tokens = [];
preg_match_all(self::REGEX, $content, $matches);
if (count($matches[0])) {
foreach ($matches[1] as $k => $id) {
$token = $matches[0][$k];
if (isset($tokens[$token])) {
continue;
}
$form = $this->formModel->getEntity($id);
if (null !== $form
&& (
$form->isPublished(false)
|| $this->security->hasEntityAccess(
'form:forms:viewown', 'form:forms:viewother', $form->getCreatedBy()
)
)
) {
$formHtml = ($form->isPublished()) ? $this->formModel->getContent($form) :
'';
// pouplate get parameters
$this->formModel->populateValuesWithGetParameters($form, $formHtml);
$tokens[$token] = $formHtml;
} else {
$tokens[$token] = '';
}
}
}
return $tokens;
}
}