View source
<?php
namespace Drupal\yamlform;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Form\FormState;
use Drupal\Core\Url;
use Drupal\yamlform\Utility\YamlFormArrayHelper;
use Drupal\yamlform\Utility\YamlFormElementHelper;
class YamlFormEntityElementsValidator {
use StringTranslationTrait;
protected $yamlform;
protected $elementsRaw;
protected $originalElementsRaw;
protected $elements;
protected $originalElements;
public function validate(YamlFormInterface $yamlform) {
$this->yamlform = $yamlform;
$this->elementsRaw = $yamlform
->getElementsRaw();
$this->originalElementsRaw = $yamlform
->getElementsOriginalRaw();
if ($message = $this
->validateRequired()) {
return [
$message,
];
}
if ($message = $this
->validateYaml()) {
return [
$message,
];
}
$this->elements = Yaml::decode($this->elementsRaw);
$this->originalElements = Yaml::decode($this->originalElementsRaw);
if ($message = $this
->validateArray()) {
return [
$message,
];
}
if ($messages = $this
->validateDuplicateNames()) {
return $messages;
}
if ($messages = $this
->validateProperties()) {
return $messages;
}
if ($messages = $this
->validateSubmissions()) {
return $messages;
}
if ($messages = $this
->validateHierarchy()) {
return $messages;
}
if ($message = $this
->validateRendering()) {
return [
$message,
];
}
return NULL;
}
protected function validateRequired() {
return empty($this->elementsRaw) ? $this
->t('Elements are required') : NULL;
}
protected function validateYaml() {
try {
Yaml::decode($this->elementsRaw);
return NULL;
} catch (\Exception $exception) {
return $this
->t('Elements are not valid. @message', [
'@message' => $exception
->getMessage(),
]);
}
}
protected function validateArray() {
if (!is_array($this->elements)) {
return $this
->t('Elements are not valid. YAML must contain an associative array of elements.');
}
return NULL;
}
protected function validateDuplicateNames() {
$duplicate_names = [];
$this
->getDuplicateNamesRecursive($this->elements, $duplicate_names);
if ($duplicate_names = array_filter($duplicate_names)) {
$messages = [];
foreach ($duplicate_names as $duplicate_name => $duplicate_count) {
$line_numbers = $this
->getLineNumbers('/^\\s*(["\']?)' . preg_quote($duplicate_name, '/') . '\\1\\s*:/');
$t_args = [
'%name' => $duplicate_name,
'@lines' => $this
->formatPlural(count($line_numbers), $this
->t('line'), $this
->t('lines')),
'@line_numbers' => YamlFormArrayHelper::toString($line_numbers),
];
$messages[] = $this
->t('Elements contain a duplicate element name %name found on @lines @line_numbers.', $t_args);
}
return $messages;
}
return NULL;
}
protected function getDuplicateNamesRecursive(array $elements, array &$names) {
foreach ($elements as $key => &$element) {
if (Element::property($key) || !is_array($element)) {
continue;
}
if (isset($element['#type'])) {
if (!isset($names[$key])) {
$names[$key] = 0;
}
else {
++$names[$key];
}
}
$this
->getDuplicateNamesRecursive($element, $names);
}
}
protected function validateProperties() {
$ignored_properties = YamlFormElementHelper::getIgnoredProperties($this->elements);
if ($ignored_properties) {
$messages = [];
foreach ($ignored_properties as $ignored_property) {
$line_numbers = $this
->getLineNumbers('/^\\s*(["\']?)' . preg_quote($ignored_property, '/') . '\\1\\s*:/');
$t_args = [
'%property' => $ignored_property,
'@lines' => $this
->formatPlural(count($line_numbers), $this
->t('line'), $this
->t('lines')),
'@line_numbers' => YamlFormArrayHelper::toString($line_numbers),
];
$messages[] = $this
->t('Elements contain an unsupported %property property found on @lines @line_numbers.', $t_args);
}
return $messages;
}
return NULL;
}
protected function validateSubmissions() {
if (!$this->yamlform
->hasSubmissions()) {
return NULL;
}
$element_keys = [];
if ($this->elements) {
$this
->getElementKeysRecursive($this->elements, $element_keys);
}
$original_element_keys = [];
if ($this->originalElements) {
$this
->getElementKeysRecursive($this->originalElements, $original_element_keys);
}
if ($missing_element_keys = array_diff_key($original_element_keys, $element_keys)) {
$messages = [];
foreach ($missing_element_keys as $missing_element_key) {
$items = [];
$items[] = $this
->t('<a href=":href">Delete all submissions</a> to this form.', [
':href' => $this->yamlform
->toUrl('results-clear')
->toString(),
]);
if (\Drupal::moduleHandler()
->moduleExists('yamlform_ui')) {
$items[] = $this
->t('<a href=":href">Delete this individual element</a> using the form UI.', [
':href' => Url::fromRoute('entity.yamlform_ui.element.delete_form', [
'yamlform' => $this->yamlform
->id(),
'key' => $missing_element_key,
])
->toString(),
]);
}
else {
$items[] = $this
->t('<a href=":href">Enable the YAML Form UI module</a> and safely delete this element.', [
':href' => Url::fromRoute('system.modules_list')
->toString(),
]);
}
$items[] = $this
->t("Hide this element by setting its <code>'#access'</code> property to <code>false</code>.");
$build = [
'message' => [
'#markup' => $this
->t('The %key element can not be removed because the %title form has <a href=":href">results</a>.', [
'%title' => $this->yamlform
->label(),
'%key' => $missing_element_key,
':href' => $this->yamlform
->toUrl('results-submissions')
->toString(),
]),
],
'items' => [
'#theme' => 'item_list',
'#items' => $items,
],
];
$messages[] = \Drupal::service('renderer')
->renderPlain($build);
}
return $messages;
}
return NULL;
}
protected function getElementKeysRecursive(array $elements, array &$names) {
foreach ($elements as $key => &$element) {
if (Element::property($key) || !is_array($element)) {
continue;
}
if (isset($element['#type'])) {
$names[$key] = $key;
}
$this
->getElementKeysRecursive($element, $names);
}
}
protected function validateHierarchy() {
$elements = $this->yamlform
->getElementsInitializedAndFlattened();
$messages = [];
foreach ($elements as $key => $element) {
$element_manager = \Drupal::service('plugin.manager.yamlform.element');
$plugin_id = $element_manager
->getElementPluginId($element);
$yamlform_element = $element_manager
->createInstance($plugin_id, $element);
$t_args = [
'%title' => !empty($element['#title']) ? $element['#title'] : $key,
'@type' => $yamlform_element
->getTypeName(),
];
if ($yamlform_element
->isRoot() && !empty($element['#yamlform_parent_key'])) {
$messages[] = $this
->t('The %title (@type) is a root element that can not be used as child to another element', $t_args);
}
elseif (!$yamlform_element
->isContainer($element) && !empty($element['#yamlform_children'])) {
$messages[] = $this
->t('The %title (@type) is a form element that can not have any child elements.', $t_args);
}
}
return $messages;
}
protected function validateRendering() {
set_error_handler('_yamlform_entity_element_validate_rendering_error_handler');
try {
$entity_type_manager = \Drupal::service('entity_type.manager');
$form_builder = \Drupal::service('form_builder');
$yamlform_submission = $entity_type_manager
->getStorage('yamlform_submission')
->create([
'yamlform' => $this->yamlform,
]);
$form_object = $entity_type_manager
->getFormObject('yamlform_submission', 'default');
$form_object
->setEntity($yamlform_submission);
$form_state = (new FormState())
->setFormState([]);
$form_builder
->buildForm($form_object, $form_state);
$message = NULL;
} catch (\Exception $exception) {
$message = $exception
->getMessage();
}
set_error_handler('_drupal_error_handler');
if ($message) {
$build = [
'title' => [
'#markup' => $this
->t('Unable to render elements, please view the below message(s) and the error log.'),
],
'items' => [
'#theme' => 'item_list',
'#items' => [
$message,
],
],
];
return \Drupal::service('renderer')
->renderPlain($build);
}
return $message;
}
protected function getLineNumbers($pattern) {
$lines = explode(PHP_EOL, $this->elementsRaw);
$line_numbers = [];
foreach ($lines as $index => $line) {
if (preg_match($pattern, $line)) {
$line_numbers[] = $index + 1;
}
}
return $line_numbers;
}
}